#Iteratorパターンとは
Iteratorパターンとは、デザインパターンの一種で「集合体に対する順次アクセスを効率化する」ためのものです。集合体とは、要素(データ)の集まりのことをいいます。わかりやすくいうと配列やCollectionなんかが集合体に相当します。
#Iteratorパターンのメリット
集合体にアクセスする際は添字を使ったり、for nextやfor eachで順次アクセスする場合が多いですが、Iteratorパターンを利用するとこのような順次アクセス操作を基本的に2つのメソッドhasNext(), next()で実現できます。
具体的なメリットについては以下が詳しいのでここでは詳細説明しませんが、一番のメリットとしては**「ある集合体の要素について決まった順序で順次アクセスしてもらいたい場合などに、そのルールを集合体の側で決定することができる」**だと思っています。
【プログラム設計】デザインパターン学習 "Iteratorパターン" とは【オブジェクト指向】
#Iteratorパターンの実装
##Iteratorインタフェースを作成する
VBAではnextは予約語のためNextItemとします
Option Explicit
' 次要素があるかどうか
Public Function hasNext() As Boolean
End Function
' 次要素を取得する
Public Function NextItem() As Object
End Function
##集約体(Aggregate)インタフェースを作成する
Option Explicit
' Iteratorオブジェクトを返す
Public Function iterator() As IIterator
End Function
##集約体に格納する要素オブジェクトを作成する
別になんでもいいのですが、今回は株価データにしてみます。Excel VBAではコンストラクタに引数を渡せないので、インスタンス生成後に値をセットするためにinitメソッドを用意します。ダサい!
Option Explicit
Private mPriceDate As Date
Private mPriceOpen As Double
Private mPriceHigh As Double
Private mPriceLow As Double
Private mPriceClose As Double
Public Property Get pricedate() As Date
pricedate = mPriceDate
End Property
Public Property Get priceopen() As Double
priceopen = mPriceOpen
End Property
Public Property Get pricehigh() As Double
pricehigh = mPriceHigh
End Property
Public Property Get pricelow() As Double
pricelow = mPriceLow
End Property
Public Property Get priceclose() As Double
priceclose = mPriceClose
End Property
Public Sub init(pricedate As Date, priceopen As Double, pricehigh As Double, pricelow As Double, priceclose As Double)
mPriceDate = pricedate
mPriceOpen = priceopen
mPriceHigh = pricehigh
mPriceLow = pricelow
mPriceClose = priceclose
End Sub
##Aggregateインタフェースを実装した具体クラスを作成する
BarAggregateとしておきます。具体クラスにはインタフェースを実装するiterator()のほか、実データを格納する配列やcollectionオブジェクト、要素を追加したり要素の最大数を数えるメソッドも用意しておきます。iterator()はIteratorオブジェクトを生成する際に自身を引数として渡すのですが、ここでもコンストラクタが無いためinitメソッドを用意します。ダサい!
Option Explicit
Implements IAggregate
' データ格納用のcollectionオブジェクト
Private BarItems As New collection
' インタフェースの実装
Public Function IAggregate_iterator() As IIterator
Dim res As New BarIterator
' 集約体を渡す
Call res.init(Me)
Set IAggregate_iterator = res
End Function
' 要素を追加する
Public Sub addItem(pricedate As Date, priceopen As Double, pricehigh As Double, pricelow As Double, priceclose As Double)
Dim Bar As New BarItem
Call Bar.init(pricedate, priceopen, pricehigh, pricelow, priceclose)
BarItems.Add Item:=Bar
End Sub
' 要素を数える
Public Function getCount() As Long
Dim res As Long
res = BarItems.Count
getCount= res
End Function
' 要素を取得する
Public Function getBarItem(index As Long) As BarItem
Dim res As New BarItem
Set res = BarItems.Item(index)
Set getBarItem = res
End Function
##Iteratorインタフェースを実装した具体クラスを作成する
BarIteratorとしておきます。これは、生成される際に(厳密にいうとコンストラクタが使えないので生成後にinitで)集約体を引数として渡されることで、メンバ変数に集約体を持ちます。
Option Explicit
Implements IIterator
' データ格納用の集約体
Private Bars As New BarAggregate
' 要素位置
Private index As Long
Public Function IIterator_hasNext() As Boolean
Dim res As Boolean
If Bars.getCount > index - 1 Then
res = True
Else
res = False
End If
IIterator_hasNext = res
End Function
Public Function IIterator_NextItem() As Object
Dim res As New BarItem
Set res = Bars.getBarItem(index)
Set IIterator_NextItem = res
index = index + 1
End Function
' メンバ変数に集約体をセット、カウンタをリセット
Public Sub init(BarAggregate As IAggregate)
Set Bars = BarAggregate
index = 1
End Sub
##動作イメージ
標準モジュールから使用する際は、以下のように集約体を生成->要素を追加->イテレータを生成と利用します。
Option Explicit
Sub test_iterator()
Dim Bars As New BarAggregate
Call Bars.addItem(#1/4/2005#, 512, 516, 511, 515)
Call Bars.addItem(#1/5/2005#, 511, 514, 507, 508)
Call Bars.addItem(#1/6/2005#, 502, 513, 502, 511)
Call Bars.addItem(#1/7/2005#, 512, 513, 497, 502)
Call Bars.addItem(#1/11/2005#, 503, 511, 503, 510)
' イテレータを作成
Dim itr As New IIterator
Set itr = Bars.IAggregate_iterator()
Dim BarItem As New BarItem
Do While (itr.hasNext)
Set BarItem = itr.NextItem
With BarItem
Debug.Print .pricedate & " " & .priceopen & " " & .pricehigh & " " & .pricelow & " " & .priceclose
End With
Loop
End Sub
###output
2005/01/04 512 516 511 515
2005/01/05 511 514 507 508
2005/01/06 502 513 502 511
2005/01/07 512 513 497 502
2005/01/11 503 511 503 510
##応用
以上のイテレータは追加された要素順に出力するものでしたが、これを逆順に出力する場合は、BarIteratorのIIterator_NextItem()を次のように書き換えことで実現できます。
Public Function IIterator_NextItem() As Object
Dim res As New BarItem
' 走査を逆順にする
Set res = Bars.getBarItem(Bars.getCount - index + 1)
Set IIterator_NextItem = res
index = index + 1
End Function
###output
2005/01/11 503 511 503 510
2005/01/07 512 513 497 502
2005/01/06 502 513 502 511
2005/01/05 511 514 507 508
2005/01/04 512 516 511 515
##まとめ
Iteratorの利点は、集約体の生成と繰り返し処理を別クラスに分離することで、集約体の実装と繰り返し処理の間の依存関係が絶たれることです。Aggregate側での内部データ保持方法が変わっても影響は受けませんし、Iterator側がどのような順序で走査しようともAggregateには関係ありません。ループを数え上げる部分もhasNext()とNextItem()しか使っていないので、走査の方法が変わっても、変更する必要はありません。