Excel VBAでIteratorパターンを実装する

#Iteratorパターンとは

Iteratorパターンとは、デザインパターンの一種で「集合体に対する順次アクセスを効率化する」ためのものです。集合体とは、要素（データ）の集まりのことをいいます。わかりやすくいうと配列やCollectionなんかが集合体に相当します。

#Iteratorパターンのメリット

集合体にアクセスする際は添字を使ったり、for nextやfor eachで順次アクセスする場合が多いですが、Iteratorパターンを利用するとこのような順次アクセス操作を基本的に2つのメソッドhasNext(), next()で実現できます。

具体的なメリットについては以下が詳しいのでここでは詳細説明しませんが、一番のメリットとしては**「ある集合体の要素について決まった順序で順次アクセスしてもらいたい場合などに、そのルールを集合体の側で決定することができる」**だと思っています。

[【プログラム設計】デザインパターン学習 "Iteratorパターン" とは【オブジェクト指向】](http://matome.naver.jp/odai/2135164466638306201)

#Iteratorパターンの実装

##Iteratorインタフェースを作成する
VBAではnextは予約語のためNextItemとします

```vbnet:IIterator.cls
Option Explicit

' 次要素があるかどうか
Public Function hasNext() As Boolean

End Function

' 次要素を取得する
Public Function NextItem() As Object

End Function
```

##集約体(Aggregate)インタフェースを作成する

```vbnet:IAggregate.cls
Option Explicit

' Iteratorオブジェクトを返す
Public Function iterator() As IIterator

End Function
```

##集約体に格納する要素オブジェクトを作成する
別になんでもいいのですが、今回は株価データにしてみます。Excel VBAではコンストラクタに引数を渡せないので、インスタンス生成後に値をセットするためにinitメソッドを用意します。ダサい！

```vbnet:BarItem.cls
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メソッドを用意します。ダサい！

``` vbnet:BarAggregate.cls
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で）集約体を引数として渡されることで、メンバ変数に集約体を持ちます。

```vbnet:BarIterator.cls
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
```

##動作イメージ
標準モジュールから使用する際は、以下のように集約体を生成->要素を追加->イテレータを生成と利用します。

```vbnet:test.bas
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()を次のように書き換えことで実現できます。

```vbnet:BarIterator.cls
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()しか使っていないので、走査の方法が変わっても、変更する必要はありません。