9
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

Organization

GoでIteratorパターンを実装する

概要

デザインパターンの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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
9
Help us understand the problem. What are the problem?