Goでデザインパターン (Iteratorパターン)

Goデザインパターン

Iteratorパターンとは

何らかのデータを集約しているオブジェクトの各要素に対して順番にアクセスするためのデザインパターン。
例えば 配列arrの要素を順番に取得する場合、 arr[0], arr[1],…arr[i],… のようにアクセスする。この変数iの働きを抽象化し、一般化したデザインパターンになる。
集約オブジェクトから「要素を走査する責務」を切り出すことで、下記のようなメリットが得られる。

  • 集約オブジェクトを利用する側は集約オブジェクトが保持するデータ型を意識せずデータを走査できる。(利用側に共通の走査方法を提供できる)
    →集約オブジェクトと利用側の結合度を弱めることができる。
  • 複数の走査方法を集約オブジェクトに対して簡単に提供できる。
    (逆順に走査する、一つ飛ばしで走査する等)

クラス図

実装

インターフェース

package main

type Aggregate interface {
	Iterator() Iterator
}
package main

type Iterator interface {
	HasNext() bool
	Next() interface{}
}

構造体

package main

type Book struct {
	name string
}

func (b *Book) GetName() string {
	return b.name
}

func NewBook(name string) *Book {
	book := Book{name: name}
	return &book
}
package main

type BookShelf struct {
	books []Book
	last  int
}

func NewBookShelf() *BookShelf {
	bs := &BookShelf{last: 0, books: []Book{}}
	return bs
}

func (b *BookShelf) GetBookAt(index int) Book {
	return b.books[index]
}

func (b *BookShelf) AppendBook(book Book) {
	b.books = append(b.books, book)
	b.last++
}

func (b *BookShelf) GetLength() int {
	return b.last
}

func (b *BookShelf) Iterator() Iterator {
	return NewBookShelfIterator(*b)
}
package main

type BookShelfIterator struct {
	bookShelf BookShelf
	index int
}

func NewBookShelfIterator(bookShelf BookShelf) *BookShelfIterator {
	b := &BookShelfIterator{bookShelf: bookShelf, index: 0}
	return b
}

func (b *BookShelfIterator) HasNext() bool {
	if b.index < b.bookShelf.GetLength() {
		return true
	} else {
		return false
	}
}

func (b *BookShelfIterator) Next() interface{} {
	book := b.bookShelf.GetBookAt(b.index)
	if b.index < b.bookShelf.GetLength() {
		b.index++
	}
	return book
}

動作確認

package main

import (
	"fmt"
)

func main() {
	bs := NewBookShelf()
	bs.AppendBook(*NewBook("Anime"))
	bs.AppendBook(*NewBook("Bike"))
	bs.AppendBook(*NewBook("Candy"))
	bs.AppendBook(*NewBook("Days"))

	it := bs.Iterator()
	for it.HasNext() {
		book := (it.Next()).(Book)
		fmt.Println(book.GetName())
	}
}
Anime
Bike 
Candy
Days

補足

上記のBookShelfではBookをスライス(books []Book)で保持している。これをmapで保持するようにすると以下のようになる。

実装

package main

type MapBookShelf struct {
	books map[string]Book
	keys []string
	last  int
}

func NewMapBookShelf() *MapBookShelf {
	bs := &MapBookShelf{last: 0, books: map[string]Book{}}
	return bs
}

func (b *MapBookShelf) GetBookAt(index int) Book {
	key := b.keys[index]
	return b.books[key]
}

func (b *MapBookShelf) AppendBook(book Book) {
	name := book.GetName()
	b.books[name] = book
	b.keys = append(b.keys, name)
	b.last++
}

func (b *MapBookShelf) GetLength() int {
	return b.last
}

func (b *MapBookShelf) Iterator() Iterator {
	return NewMapBookShelfIterator(*b)
}
package main

type MapBookShelfIterator struct {
	mapBookShelf MapBookShelf
	index int
}

func NewMapBookShelfIterator(bookShelf MapBookShelf) *MapBookShelfIterator {
	b := &MapBookShelfIterator{mapBookShelf: bookShelf, index: 0}
	return b
}

func (b *MapBookShelfIterator) HasNext() bool {
	if b.index < b.mapBookShelf.GetLength() {
		return true
	} else {
		return false
	}
}

func (b *MapBookShelfIterator) Next() interface{} {
	book := b.mapBookShelf.GetBookAt(b.index)
	if b.index < b.mapBookShelf.GetLength() {
		b.index++
	}
	return book
}

実行結果

package main

import (
	"fmt"
)

func main() {
	bs := NewBookShelf()
	bs.AppendBook(*NewBook("Anime"))
	bs.AppendBook(*NewBook("Bike"))
	bs.AppendBook(*NewBook("Candy"))
	bs.AppendBook(*NewBook("Days"))

	// ここで利用しているのはIteratorオブジェクトのHasNext(), Next()のみ
	// → 集合体であるBookShelfの実装に依存せずに要素の走査を行っている
	it := bs.Iterator()
	for it.HasNext() {
		book := (it.Next()).(Book)
		fmt.Println(book.GetName())
	}

	mbs := NewMapBookShelf()
	mbs.AppendBook(*NewBook("Anime (Map)"))
	mbs.AppendBook(*NewBook("Bike (Map)"))
	mbs.AppendBook(*NewBook("Candy (Map)"))
	mbs.AppendBook(*NewBook("Days (Map)"))

	// MapBookShelfでもBookShelfと同様の方法で集合体の走査ができている
	mit := mbs.Iterator()
	for mit.HasNext() {
		book := (mit.Next()).(Book)
		fmt.Println(book.GetName())
	}
}
Anime
Bike       
Candy      
Days       
Anime (Map)
Bike (Map) 
Candy (Map)
Days (Map)

データ保持をスライスからmapに変更しても同じアクセス方法でデータの走査をできることが確認できる。

コメント

タイトルとURLをコピーしました