概要
デザインパターンの1つであるIteratorパターンをGoで実装してみます。
Iteratorパターンとは
Iteratorパターンは、任意のオブジェクトを集約して列挙しながら参照する時によく用いられるデザインパターンです。
forで回せないような複雑なリストを列挙したい場合などに、列挙処理を内部に隠蔽して順次に参照できるようになる利点があります。
Goでイテレーターが使われている処理
例えばdatabase/sqlの内部実装でイテレーターが使われています。
sql.Drivers.Rowsにイテレーターが実装されていて、上流レイヤであるsql.Rowsでmutexなどを使って処理をラップしているようです。
sql.Rows
https://golang.org/src/database/sql/sql.go?s=75693:76415#L2672
sql.Drivers.Rows
https://golang.org/src/database/sql/sql.go?s=75693:76415#L2672
実装例
Iteratorパターンでは、以下の概念のオブジェクトを実装します。
- Items(アイテムの集合体)
- Item(アイテム単体)
- Iterator
一般的にIteratorは以下の2つのメソッドを持ちます。
- HasNext() -> 次の要素が存在するか。
- Next() -> インデックスをインクリメントする。
さて、以上のことを踏まえてここからは音楽プレーヤを例に解説します。
プレイリストをMusics、中身の単体のアイテムをMusicとしつつ、
Musicsを管理するイテレータ構造体を作成して、Musicsの中に処理を隠蔽する構成で作っていこうと思います。
まずはMusicの定義。
package main
type Music struct {
Id int64
Title string
Artist string
}
func (m Music) String() string {
return fmt.Sprintf("ID: %d, Title: %s, Artist: %s", m.Id, m.Title, m.Artist)
}
次に、Musicの集合体を定義する前にMusicsが保持するIteratorを先に作成します。
type MusicsIterator struct {
// MusicのAggregate
Musics *Musics
// 現在のインデックス
Idx int64
}
func (m *MusicsIterator) HasNext() bool {
if m.Idx < m.Musics.GetSize() {
return true
}
return false
}
func (m *MusicsIterator) Next() *Music {
item := m.Musics.GetItemAt(m.Idx)
m.Idx += 1
return item
}
最後に、Musicの具象AggragateクラスであるMusicsを定義します。
type Musics struct {
// 外に解放しない
items []*Music
iterator *MusicsIterator
}
func NewMusics() *Musics {
m := &Musics{
iterator: &MusicsIterator{},
}
m.iterator.Musics = m
return m
}
func (m *Musics) GetItemAt(idx int64) *Music {
if m.GetSize() <= idx {
return nil
}
return m.items[idx]
}
// AggregatorのアクションとしてIterationさせたいので
// Iteratorの処理をラップ。
func (m *Musics) HasNext() bool {
return m.iterator.HasNext()
}
// ここも同じ
func (m *Musics) Next() *Music {
return m.iterator.Next()
}
// わかりやすくするためにsliceにappendしていますが
// リストの場合などはポインタで連結して再代入。
func (m *Musics) Append(music *Music) {
m.items = append(m.items, music)
}
// ここもわかりやすくするために単純にlen(xx)で済ませていますが
// リストの場合などはノードをたどる処理が入ったりするかも。
func (m *Musics) GetSize() int64 {
return int64(len(m.items))
}
さて、準備が整ったので実際にイテレーションの動作を確かめてみましょう。
package main
import (
"fmt"
)
func main() {
// 要素をappendして出力するだけ。
musics := NewMusics()
musics.Append(&Music{
Id: 1,
Title: "MusicA",
Artist: "ArtistA",
})
musics.Append(&Music{
Id: 2,
Title: "MusicB",
Artist: "ArtistB",
})
for musics.HasNext() {
m := musics.Next()
fmt.Println(m)
}
}
はい、見事に集約された構造体の走査処理を隠蔽することができました。
出力はこのようになるはず。
ID: 1, Title: MusicA, Artist: ArtistA
ID: 2, Title: MusicB, Artist: ArtistB
実際に書いたコードは https://github.com/OdaDaisuke/go-algorithm/blob/master/dp/iterator/main.go