LoginSignup
0
1

More than 1 year has passed since last update.

デザインパターン勉強会①Iterator

Posted at

はじめに

ZOOM勉強会後、覚え書きとして、内容をまとめてみました。
初心者二人での勉強会のため、間違っている点などあるかもしれません。

勉強会について

目的

  • 学習を習慣化する
  • オブジェクト指向・デザインパターンへの理解を深める
  • 業務に活かす

内容

結城 浩さん著の「増補改訂版Java言語で学ぶデザインパターン入門」を読み、プログラムを作成して、デザインパターンを理解します。
初回はIteratorパターンについて学びました。

環境

VB.NET
Visual Studio 2017 & 2019
.NET Framework 4.7.2

Iteratorパターン

Iteratorパターンとは数え上げを行うパターンです。

登場する役割は以下のとおりです。

  • Iterator
  • 要素を順番にスキャンしていく反復子のインタフェース
  • ConcreteIterator
  • Iteratorを具体的に実装
  • Aggregate
  • Iterator役を作り出すインタフェース
  • ConcreteAggregate
  • Aggregateが定めたインタフェースを実装

Iterator

.NetFrameworkではIterator役としてIEnumeratorが用意されています。
各メソッドの役割は、コメントに記述しているとおりです。

IEnumerator
Public Interface IEnumerator
    '列挙子の現在位置にあるコレクション内の要素を取得します。
    ReadOnly Property Current As Object

    '列挙子を初期位置、つまりコレクションの最初の要素の前に設定します。
    Sub Reset()

    '列挙子をコレクションの次の要素に進めます。
    '次の要素に正常に進んだ場合Trueを返します。
    Function MoveNext() As Boolean
End Interface

Aggregate

一方、Aggregate役としてIEnumerableが用意されています。
Aggregate役は、Iterator役を返すメソッドだけを定義しています。

IEnumerable
Public Interface IEnumerable
    'コレクションを反復処理する列挙子を返します。
    Function GetEnumerator() As IEnumerator
End Interface

今回はこれらのIEnumeratorとIEnumerableを使用してIteratorを実装してみました。

ConcreteAggregate

以下のMyListクラスが今回のConcreteAggregate役です。

MyList
Public Class MyList : Implements IEnumerable

    ' 要素の集合
    Private _elements As Object()

    ' 要素数
    Private _last As Integer = 0

    Public Sub New(ByVal maxSize As Integer)
        ReDim _elements(maxSize)
    End Sub

    ' indexの要素を取得する
    Public Function GetElementAt(ByVal index As Integer) As Object
        Return _elements(index)
    End Function

    ' 要素を追加する
    Public Sub AppendElement(ByVal obj As Object)
        _elements(_last) = obj
        _last += 1
    End Sub

    ' 要素の数を取得する
    Public Function GetLength() As Integer
        Return _last
    End Function

    ' 反復処理する列挙子を返す
    Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
        Return New MyListIterator(Me)
    End Function


End Class

MyListは、Aggregate役のIEnumerableを実装しており、GetEnumerator()でIterator役のIEnumeratorを返します。
このメソッドは、Iteratorパターンを使用する側のコードから呼び出します(Mainなど)。

また、このクラスは各要素と要素数の取得ができるようになっています。
次に説明するConcreteIteratorでは、これらのメソッドを使用して集合体の情報にアクセスします。

ConcreteIterator

以下のMyListIteratorが今回のConcreteIterator役です。

MyListIterator
Public Class MyListIterator : Implements IEnumerator

    '集合体
    Private _myList As MyList

    '今返しているインデックス
    Private _index As Integer

    Public Sub New(ByVal myList As MyList)
        Me._myList = myList
        _index = -1
    End Sub

    '現在の要素を返す
    Public ReadOnly Property Current As Object Implements IEnumerator.Current
        Get
            Dim obj As Object = _myList.GetElementAt(_index)
            Return obj
        End Get
    End Property

    'インデックスをリセット
    Public Sub Reset() Implements IEnumerator.Reset
        _index = -1
    End Sub

    '次のインデックスにデータが存在するかを返す
    Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
        _index +=1
        If _index < _myList.GetLength Then
            Return True
        Else
            Return False
        End If
    End Function
End Class

MyListIteratorは、Iterator役のIEnumeratorを実装しています。
このクラスは、コンストラクタでConcreteAggregate役のMyListを渡されています。
ここで渡されている集合体に対して、数え上げを行うことになります。

各メソッドについて説明します。

Currentプロパティでは、Getで現在のインデックスが指し示す要素を返しています。

MoveNext()では、次の要素に進めることができるかを返しています。
以下のコードでは、単純にインデックスを一つだけ進めていますが、ここの実装を変えることで、様々な数え上げを表現することができます。

Reset()では、インデックスを初期値に戻します。
Java言語で学ぶデザインパターン入門のIteratorには、このメソッドはありませんでした。
勉強会では、Iteratorは使い捨てでよく、Reset()で使い回すのは良くないのでは?という意見もありました。

使い方

以上で、すべての役が出揃いました。
実際に数え上げを行うコードは以下です。

Main
Sub Main()
    '準備
    Dim mylist As New MyList(10)
    mylist.AppendElement("a")
    mylist.AppendElement("b")
    mylist.AppendElement("c")
    mylist.AppendElement(1)

    'ここからがIteratorパターン活用部分    
    Dim it As IEnumerator = mylist.GetEnumerator()

    While it.MoveNext()
        Debug.Write(it.Current)
    End While
    '出力結果↓
    'abc1

    'これが上と同じことをしている
    For Each element In mylist
        Debug.Write(element)
    Next
    '出力結果↓
    'abc1
End Sub

GetEnumerator()でIteratorを取得し、そのIteratorのMoveNext()で要素を進め、Currentで要素を取得します。
For Each文は、このIteratorパターンの糖衣構文だそうです。

Iteratorパターンで数え上げを行っている部分を見ると、MyListに実装されているメソッドは出てきていません。
つまりWhileループは、MyListには依存しないことになり、MyListの中身が配列であろうとListであろうと数え上げを行うことができます。
MyListがIteratorを返すことができれば、MyListを修正しても、数え上げの部分を修正しなくて良くなります。
以上がIteratorパターンの説明になります。

ついでに勉強したこと

Iterator Function

VB.NETでは、IEnumerable型を返すIterator Functionというものがあるようです。
このIterator Functionを定義することで、簡易的な数え上げの機構をつくることができます。
以下がIterator Functionのサンプルコードです。

MyList2
Public Class MyList2

    Private _elements As Object()
    Private _last As Integer = 0 '要素数

    '
    '  addなどは省略
    '

    '例1
    Iterator Function GetEnumerable() As IEnumerable(Of Object)
        For i = 0 To _last - 1
            Yield _elements(i)
        Next
    End Function

    '例2 こんなふうにも書けます
    Iterator Function GetEnumerable2() As IEnumerable(Of Object)
        Yield 1
        Yield 2
        Yield 3
    End Function

End Class

Iterator Functionの中では、Yieldが来るとその値を返し、一旦この関数を抜けます。
呼び出し側の処理が終わり次の要素を要求されたとき、先程の続きから処理を開始し、次のYieldでまた関数を抜け値を返します。
これを繰り返して数え上げを行います。
1つ目の例だと、単純に配列の要素を順番に返しています。
2つ目の例だと、1,2,3を順番に返しています。

呼び出し側は、以下の様になっています。

IteratorFunctionMain
    For Each element In mylist2.GetEnumerable
        Debug.WriteLine(element)
    Next

For Eachの部分でIterator Functionが呼ばれます。
Iterator Functionでは、Yieldが来るたびに一旦処理を抜け、返された値がelementに入ります。
Nextまで行くと、処理を抜けたIterator Functionの途中から処理が始まり、次のYieldで、次のelementが決まります。
先程の1つ目の例だと、配列の要素が順番に入ります。
2つ目の例だと、1,2,3が順番に入ります。
これを繰り返し、数え上げが行われています。

まとめ

今回はIteratorパターンについて学びました。
VB.NETでは、IEnumerableとIEnumeratorがあり、普段Listなどを使うときにはIteratorの恩恵を受けていたようです。

勉強会では開発に時間をかけるほどお金がかかるという意見もあり、配列が出てくるたびにIteratorパターンを実装するのがよいわけではないと思います。
今後自分たちが実装する機会は少ないかもしれませんが、IEnumerableとIEnumeratorが何をしているか理解でき良い勉強になりました。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1