こんにちは、KOUKIです。
この記事では、デザインパターンの一つであるIteratorパターンについて紹介します。
<目次>
デザインパターンまとめ
シチュエーション
iterateは「繰り返す」という意味の英単語です。
Iteratorパターンは、要素の集まりを保有するオブジェクト(Go言語ではインスタンス)の各要素へ順番にアクセスする方法を提供するためのパターンです。
各要素へ順番にアクセスするとは、例えば以下のような処理です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package main import "fmt" type Person struct { Name string Age string Origin string } func (p *Person) Info() [3]string { return [3]string{p.Name, p.Age, p.Origin} } func main() { p := Person{"selfnote", "32", "Japan"} for _, i := range p.Info() { fmt.Println(i) } } |
ここでは、Person構造体のパラメータを順番に取得しています。
1 2 3 4 |
$ go run main.go selfnote 32 Japan |
Iteratorパターンを実装する方法は色々あるので、サンプルコードをみていきましょう。
チャネル版
チャネルを使ったIteratorパターンを紹介します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package main ... func (p *Person) InfoGenerator() <-chan string { out := make(chan string, 3) go func() { defer close(out) out <- p.Name out <- p.Age out <- p.Origin }() return out } func main() { p := Person{"selfnote", "32", "Japan"} // Origin // for _, i := range p.Info() { // fmt.Println(i) // } // Channel for i := range p.InfoGenerator() { fmt.Println(i) } } |
InfoGeneratorメソッドは、戻り値を送信専用(<-)のチャネルを指定しています。これで、呼び出し元でチャネルにデータが入るまで処理を待機させることができます。
InfoGeneratorメソッド内では、チャネルへのデータの書き込みをgoroutineで動かしており、処理が完了したら「defer close(out)」にてチャネルを閉じます。呼び出し元ではチャネルが閉じられたことを感知するので、自動的にループから抜けます。
以前、似た処理を実装したことがあるので、参考までにどうぞ!
Iterator構造体版
次は、Iterator構造体使うバージョンです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
package main ... type PersonInfoIterator struct { person *Person next int // 要素の位置 } func NewPersonInfoIterator(p *Person) *PersonInfoIterator { return &PersonInfoIterator{p, -1} } func (p *PersonInfoIterator) Next() bool { p.next++ return p.next < 3 } func (p *PersonInfoIterator) Value() string { switch p.next { case 0: return p.person.Name case 1: return p.person.Age case 2: return p.person.Origin default: panic("No operation") } } func main() { p := Person{"selfnote", "32", "Japan"} for it := NewPersonInfoIterator(&p); it.Next(); { fmt.Println(it.Value()) } } |
アクセスした要素をnextパラメータで管理しました。チャネル版より読みやすいコードですね。しかし、コード量は多くなります。
1 2 3 4 |
$ go run main.go selfnote 32 Japan |
next/prev処理(おまけ)
定番のnext/prev処理も実装してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
package main import "fmt" type Item struct { value interface{} } type Iterator struct { items []Item nextIndex int prevIndex int } func NewIterator(items []Item) *Iterator { return &Iterator{ items: items, nextIndex: 0, prevIndex: len(items) - 1, } } func (i *Iterator) hasNext() bool { return i.nextIndex < len(i.items) } func (i *Iterator) hasPrev() bool { return i.prevIndex >= 0 } func (i *Iterator) next() interface{} { value := i.items[i.nextIndex].value i.nextIndex++ return value } func (i *Iterator) prev() interface{} { value := i.items[i.prevIndex].value i.prevIndex-- return value } func main() { items := []Item{ {value: 1}, {value: "happy"}, {value: true}, {value: 1.32}, } iter := NewIterator(items) for iter.hasNext() { fmt.Println(iter.next()) } fmt.Println("------------------") for iter.hasPrev() { fmt.Println(iter.prev()) } } |
1 2 3 4 5 6 7 8 9 10 |
$ go run sample3/main.go 1 happy true 1.32 ------------------ 1.32 true happy 1 |
OKですね。結構面白いです^^
まとめ
要素を繰り返し処理したい場合は、Iteratorパターンの導入を検討してみるのもありですね。
このパターンは、実務でも活躍しそうです。
それでは、また!
Go言語まとめ
ソースコード
チャネル版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
package main import "fmt" type Person struct { Name string Age string Origin string } func (p *Person) Info() [3]string { return [3]string{p.Name, p.Age, p.Origin} } func (p *Person) InfoGenerator() <-chan string { out := make(chan string, 3) go func() { defer close(out) out <- p.Name out <- p.Age out <- p.Origin }() return out } func main() { p := Person{"selfnote", "32", "Japan"} // Channel for i := range p.InfoGenerator() { fmt.Println(i) } } |
Iterator構造体版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
package main import "fmt" type Person struct { Name string Age string Origin string } func (p *Person) Info() [3]string { return [3]string{p.Name, p.Age, p.Origin} } type PersonInfoIterator struct { person *Person next int // 要素の位置 } func NewPersonInfoIterator(p *Person) *PersonInfoIterator { return &PersonInfoIterator{p, -1} } func (p *PersonInfoIterator) Next() bool { p.next++ return p.next < 3 } func (p *PersonInfoIterator) Value() string { switch p.next { case 0: return p.person.Name case 1: return p.person.Age case 2: return p.person.Origin default: panic("No operation") } } func main() { p := Person{"selfnote", "32", "Japan"} for it := NewPersonInfoIterator(&p); it.Next(); { fmt.Println(it.Value()) } } |
next/prev処理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
package main import "fmt" type Item struct { value interface{} } type Iterator struct { items []Item nextIndex int prevIndex int } func NewIterator(items []Item) *Iterator { return &Iterator{ items: items, nextIndex: 0, prevIndex: len(items) - 1, } } func (i *Iterator) hasNext() bool { return i.nextIndex < len(i.items) } func (i *Iterator) hasPrev() bool { return i.prevIndex >= 0 } func (i *Iterator) next() interface{} { value := i.items[i.nextIndex].value i.nextIndex++ return value } func (i *Iterator) prev() interface{} { value := i.items[i.prevIndex].value i.prevIndex-- return value } func main() { items := []Item{ {value: 1}, {value: "happy"}, {value: true}, {value: 1.32}, } iter := NewIterator(items) for iter.hasNext() { fmt.Println(iter.next()) } fmt.Println("------------------") for iter.hasPrev() { fmt.Println(iter.prev()) } } |
コメントを残す
コメントを投稿するにはログインしてください。