Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

概要

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

dmmcom
総合エンタテイメントサイト「DMM.com」を運営。会員数は2,900万人を突破。動画配信、FX、英会話、ゲーム、太陽光発電、3Dプリンタなど40以上のサービスを展開。沖縄での水族館事業参入、ベルギーでのサッカークラブ経営など、様々な事業を手掛ける。また2018年より若手起業家の支援を強化、「DMM VENTURES」による出資や、M&Aなどを積極的に展開している。
https://dmm-corp.com
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