はじめに
ZOOM勉強会後、覚え書きとして、内容をまとめてみました。 初心者二人での勉強会のため、間違っている点などあるかもしれません。勉強会について
目的
- 学習を習慣化する
- オブジェクト指向・デザインパターンへの理解を深める
- 業務に活かす
内容
結城 浩さん著の「増補改訂版Java言語で学ぶデザインパターン入門」を読み、プログラムを作成して、デザインパターンを理解します。初回はIteratorパターンについて学びました。
環境
VB.NET Visual Studio 2017 & 2019 .NET Framework 4.7.2Iteratorパターン
Iteratorパターンとは数え上げを行うパターンです。登場する役割は以下のとおりです。
- Iterator 要素を順番にスキャンしていく反復子のインタフェース
- ConcreteIterator Iteratorを具体的に実装
- Aggregate Iterator役を作り出すインタフェース
- ConcreteAggregate Aggregateが定めたインタフェースを実装
Iterator
.NetFrameworkではIterator役として`IEnumerator`が用意されています。 各メソッドの役割は、コメントに記述しているとおりです。Public Interface IEnumerator
'列挙子の現在位置にあるコレクション内の要素を取得します。
ReadOnly Property Current As Object
'列挙子を初期位置、つまりコレクションの最初の要素の前に設定します。
Sub Reset()
'列挙子をコレクションの次の要素に進めます。
'次の要素に正常に進んだ場合Trueを返します。
Function MoveNext() As Boolean
End Interface
Aggregate
一方、Aggregate役として`IEnumerable`が用意されています。 Aggregate役は、Iterator役を返すメソッドだけを定義しています。Public Interface IEnumerable
'コレクションを反復処理する列挙子を返します。
Function GetEnumerator() As IEnumerator
End Interface
今回はこれらのIEnumeratorとIEnumerableを使用してIteratorを実装してみました。
ConcreteAggregate
以下のMyListクラスが今回のConcreteAggregate役です。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役です。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()で使い回すのは良くないのでは?という意見もありました。
使い方
以上で、すべての役が出揃いました。 実際に数え上げを行うコードは以下です。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のサンプルコードです。
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を順番に返しています。
呼び出し側は、以下の様になっています。
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が何をしているか理解でき良い勉強になりました。