Iteratorクラスを作ってみた
デザインパターンをまじめに勉強したくなったので、遊び半分でコーディング。
Iteratorクラス
オブジェクト名は「Iterator」です。
リスト1ー1 クラスモジュール 宣言セクション
Option Explicit
Private Const ERROR_NOT_INITIALIZED As String = _
"Iteratorクラスのinitメソッド未実行"
Private cnt As Long
Private isInitialized As Boolean
Private vault_ As Variant
Private countOf_ As Long
Private hasNext_ As Boolean
Private hasPrevious_ As Boolean
リスト1ー2 クラスモジュール Property部
Public Property Get hasNext() As Boolean
If Not isInitialized Then Call raiseErrorNotInitialized
hasNext = hasNext_
End Property
Public Property Get getNext() As Variant
If Not isInitialized Then Call raiseErrorNotInitialized
If Not hasNext_ Then Exit Property
If VarType(vault_.Item(cnt)) = vbObject Then
Set getNext = vault_.Item(cnt)
Else
getNext = vault_.Item(cnt)
End If
cnt = cnt + 1
If cnt > countOf_ Then cnt = countOf_: hasNext_ = False
End Property
Public Property Get hasPrevious() As Boolean
If Not isInitialized Then Call raiseErrorNotInitialized
If cnt > 1 Then hasPrevious_ = True
hasPrevious = hasPrevious_
End Property
Public Property Get getPrevious() As Variant
If Not isInitialized Then Call raiseErrorNotInitialized
If Not hasPrevious_ Then Exit Property
If VarType(vault_.Item(cnt)) = vbObject Then
Set getPrevious = vault_.Item(cnt)
Else
getPrevious = vault_.Item(cnt)
End If
cnt = cnt - 1
If cnt < 1 Then cnt = 1: hasPrevious_ = False
End Property
メソッドにすべきか迷ったが、せっかくPropertyという便利な仕組みがあるのだから、本家Iteratorのhasnext、nextいづれもPropertyにした。
ただし、「next」は予約語で使えないので、「getNext」にしてある。
リスト1ー3 クラスモジュール Constructor部
Private Sub Class_Initialize()
cnt = 1
hasNext_ = True
hasPrevious_ = False
End Sub
Public Function init(ByRef object_ As Variant) As Boolean
On Error GoTo errorHandler
If IsArray(object_) Then
vault_ = object_
Set vault_ = convertArrayToCollection(object_)
countOf_ = vault_.Count
init = True
isInitialized = True
Exit Function
End If
If TypeName(object_) = "Collection" Then
Set vault_ = object_
countOf_ = vault_.Count
init = True
isInitialized = True
Exit Function
End If
If TypeName(object_) = "Dictionary" Then
Set vault_ = convertArrayToCollection(object_.Items)
countOf_ = vault_.Count
init = True
isInitialized = True
Exit Function
End If
errorHandler:
init = False
isInitialized = False
End Function
例によって、VBAはコンストラクタに引数を渡せないクソ仕様なので、コンストラクタが2段階になる。
2段階目のinitメソッドでは、受け取ったものが配列だろうがCollectionだろうがDictionaryだろうが、全てCollectionに突っ込むという処理を行っている。
ちなみに、convertArrayToCollectionというのは、
のリスト1です。
リスト1ー4 クラスモジュール その他
Private Sub raiseErrorNotInitialized()
Err.Raise Number:=10001, _
description:=ERROR_NOT_INITIALIZED
End Sub
2段階目のコンストラクタ未実行のままPropertyを参照したらエラーを吐くようにするためのメソッド。インスタンスから呼ばれたら困るからPrivate指定。
使ってみる
下記のコードで実行。
リスト2 標準モジュール
Public Sub hoge() Dim ar(2) As String ar(0) = "アホ" ar(1) = "ボケ" ar(2) = "カス" Dim dic As New Scripting.Dictionary With dic .Add Key:="a1", _ Item:="ち~んw" .Add Key:="a2", _ Item:="プヒー!" .Add Key:="a3", _ Item:="( ´,_ゝ`)プッ" .Add Key:="a4", _ Item:="( ´_ゝ`)フーン" End With Dim cl As New Collection With cl .Add "バカ" .Add "クズ" .Add "デコスケ" End With Dim it1 As New Iterator Call it1.init(ar) Do While it1.hasNext Debug.Print it1.getNext Loop Dim it2 As New Iterator Call it2.init(dic) Do While it2.hasNext Debug.Print it2.getNext Loop Dim it3 As New Iterator Call it3.init(cl) Do While it3.hasNext Debug.Print it3.getNext Loop Debug.Print it1.hasNext Debug.Print it2.hasNext Debug.Print it3.hasNext Debug.Print it1.hasPrevious Debug.Print it2.hasPrevious Debug.Print it3.hasPrevious Do While it1.hasPrevious Debug.Print it1.getPrevious Loop End Sub
無駄にタテに長いコードだが、勘弁してほしい。
参照設定で「Microsoft Scripting Runtime」にチェックを入れるのを忘れないようにしてください。
配列、Scripting.Dictionary、Collectionを渡して、要素を順に取り出しているだけ。
実行結果

いちおう、意図どおりの出力が得られている。
おわりに
「だから何?」とか言わないでください。
自分でも何の役に立つのか分からないし、これをどうしたら良いのかも分かりません。
これでIteratorになっているのかどうかも不明。
達人のアドヴァイス、お待ちしております。
「デザインパターン」シリーズ、この後も続くのだろうか……???