クラスモジュールを用いたStackの改良
前回
の続き。
ちょっと改良した。
クラス名の見直し
StringStackオブジェクトの各要素がStackStringというのは、余りにもわかりにくすぎるので、StringStackItemに改めた。少し長くなるけど、この方がいい。Javaとかだったらもっとえげつなく長いクラス名とか普通にあるし。わかりやすさ優先。
あと、オレオレコーディング規約で、「プロパティはパスカル、メソッド名はキャメル」という謎ルールを課しているので、Pushメソッド、PopメソッドをそれぞれpushItem、popItemに改めた。
プロパティの追加
せっかくデータの集合なのに、データの総数がわからなかったり、○○個目のデータが参照できたりしないのでは不便(Popすると、値は得られるが消えてしまう。)。
そこで、アイテム総数を返すCountプロパティ、上から○○個目のアイテムを返すItemプロパティを追加した。
改良後のコード
クラスモジュール StringStack
Option Explicit
Private topItem As StringStackItem
Private count_ As Long '……(1)'
Public Property Get Count() As Long '……(2)'
Count = count_
End Property
Public Property Get Top() As String
If ItemExists Then
Top = topItem.Value
Else
Top = ""
End If
End Property
Public Property Get Item( _
ByVal Index As Long) As String '……(3)'
Dim ret As StringStackItem
If ItemExists Then '……(4)'
Set ret = topItem '……(5)'
If Index = 1 Then GoTo Finalizer
Dim i As Long '……(6)'
For i = 2 To Index
Set ret = ret.NextItem
Next
Else
Set ret = Nothing
End If
Finalizer:
If ret Is Nothing Then Item = "": Exit Property
Item = ret.Value
End Property
Public Property Get ItemExists() As Boolean
ItemExists = Not (topItem Is Nothing)
End Property
Public Sub pushItem(ByVal argValue As String)
Dim newTopItem As New StringStackItem
newTopItem.Value = argValue
Set newTopItem.NextItem = topItem
Set topItem = newTopItem
count_ = count_ + 1 '……(7)'
End Sub
Public Function popItem() As String
Dim ret As Variant
If Me.ItemExists Then
ret = topItem.Value
Set topItem = topItem.NextItem
count_ = count_ - 1 '……(8)'
End If
popItem = ret
End Function
追加したのは、まず(1)の
Private count_ As Long
というモジュールレベル変数。こいつで、アイテム数を保持する。
(2)の
Public Property Get Count() As Long Count = count_ End Property
でCountプロパティを生やす。ReadOnly。
(3)の
Public Property Get Item( _
ByVal Index As Long) As String
Dim ret As StringStackItem
If ItemExists Then '……(4)'
Set ret = topItem '……(5)'
If Index = 1 Then GoTo Finalizer
Dim i As Long '……(6)'
For i = 2 To Index
Set ret = ret.NextItem
Next
Else
Set ret = Nothing
End If
Finalizer:
If ret Is Nothing Then Item = "": Exit Property
Item = ret.Value
End Property
でItemプロパティを生やす。
引数Indexを受け取って、上からIndex番目のアイテムの値(String)を返す。
(4)の
If ItemExists Then
で条件分岐。アイテムがなかったら、Else節へ飛んで、retにNothingをセット。別に無くても良いけど、明示する。
アイテムがある場合は、(5)の
Set ret = topItem If Index = 1 Then GoTo Finalizer
で、retに一番上に積まれているアイテムをセット。引数Indexが1だったら即Finalizerラベルへ飛び、値を返す。
Indexが2以上のときは、(6)の
Dim i As Long For i = 2 To Index Set ret = ret.NextItem Next
で、必要な回数だけNextItemを順に手繰っていって、目的のStringStackItemを取得する。
ちなみに、Indexに1未満の数やCountの値を超える数値が渡されたときの対応は未実装。
あと、(7)と(8)は、pushItem、popItemメソッド実行時にそれぞれcount_の値を増減しているだけ。
クラスモジュール StringStackItem
Option Explicit Private value_ As String Private nextItem_ As StringStackItem Public Property Let Value(ByVal argValue As String) value_ = argValue End Property Public Property Get Value() As String Value = value_ End Property Public Property Set NextItem(ByVal argItem As StringStackItem) Set nextItem_ = argItem End Property Public Property Get NextItem() As StringStackItem Set NextItem = nextItem_ End Property
こちらの方は変更なし。クラス名を変えただけ。
使ってみる
次のコードで実験。
リスト1 標準モジュール
Private Sub testStringStack()
Dim strStack As New StringStack
With strStack
Debug.Print "現在の保有アイテム数は、" & .Count & " 個です。"
Call .pushItem("1番サード岩鬼")
Debug.Print .Top & " をPushしました。"
Debug.Print "現在の保有アイテム数は、" & .Count & " 個です。"
Call .pushItem("2番セカンド殿馬")
Debug.Print .Top & " をPushしました。"
Debug.Print "現在の保有アイテム数は、" & .Count & " 個です。"
Call .pushItem("3番レフト微笑")
Debug.Print .Top & " をPushしました。"
Debug.Print "現在の保有アイテム数は、" & .Count & " 個です。"
Call .pushItem("4番キャッチャー山田")
Debug.Print .Top & " をPushしました。"
Debug.Print "現在の保有アイテム数は、" & .Count & " 個です。"
Debug.Print "===================="
Dim i As Long
For i = 1 To .Count
Debug.Print "上から" & StrConv(CStr(i), vbWide) & "番目は、" & _
.Item(i) & " です。"
Next
Debug.Print "===================="
Do While .ItemExists
Debug.Print .popItem & " をPopしました。"
Debug.Print "現在の保有アイテム数は、" & .Count & " 個です。"
Loop
End With
End Sub
計四つのアイテムをPush。そのたびにアイテム数を出力。その後、アイテムを上から順に表示し、すべてのアイテムをPop。そのたびに残りアイテム数を表示する、というもの。
実行すると、

このとおり。
おわりに
実に面白い。
2019.11.16追記
改めて見直してみたら、ItemプロパティやpopItemメソッドがString型の値を返すというのはわかりにくい。素直にStringStackItemオブジェクトを返す方が、名前に合っているような気がする。
また、Topプロパティが値を返すのも変だ。StackオブジェクトのTopにあるのはStringStackItemオブジェクトなのだから、素直にStringStackItemオブジェクトを返す方が自然だ。
よって、次のように修正することにした。
クラスモジュール StringStack
Option Explicit
Private topItem As StringStackItem
Private count_ As Long
Public Property Get Count() As Long
Count = count_
End Property
Public Property Get Top() As StringStackItem
Dim ret As StringStackItem
Set ret = Nothing
If ItemExists Then Set ret = topItem
Set Top = ret
End Property
Public Property Get Item( _
ByVal Index As Long) As StringStackItem
Dim ret As StringStackItem
Set ret = Nothing
'IndexがおかしかったらNothingを返す'
If Index < 1 Then GoTo Finalizer
If Index > Me.Count Then GoTo Finalizer
'アイテムがあったら返す。なかったらNothing'
If ItemExists Then
Set ret = topItem
If Index = 1 Then GoTo Finalizer
Dim i As Long
For i = 2 To Index
Set ret = ret.NextItem
Next
Else
Set ret = Nothing
End If
Finalizer:
Set Item = ret
End Property
Public Property Get ItemExists() As Boolean
ItemExists = Not (topItem Is Nothing)
End Property
Public Sub pushItem(ByVal argValue As String)
Dim newTopItem As New StringStackItem
newTopItem.Value = argValue
Set newTopItem.NextItem = topItem
Set topItem = newTopItem
count_ = count_ + 1
End Sub
Public Function popItem() As StringStackItem
Dim ret As Variant
If Me.ItemExists Then
Set ret = topItem
Set topItem = topItem.NextItem
count_ = count_ - 1
End If
Set popItem = ret
End Function
ついでに、Indexプロパティに不正な値が渡されたときの対応も追加した。
本来エラーを投げるべきなんだろうけど、めんどくさいからNothingを返す仕様にした。
この変更により、Topプロパティ、Itemプロパティ、popItemメソッドの全てがStringStackItemオブジェクトを返すようになった。
それに伴い、テスト用コード(リスト1)も修正が必要。
リスト2 標準モジュール
Private Sub testStringStack()
Dim strStack As New StringStack
With strStack
Debug.Print "現在の保有アイテム数は、" & .Count & " 個です。"
Call .pushItem("1番サード岩鬼")
Debug.Print .Top.Value & " をPushしました。"
Debug.Print "現在の保有アイテム数は、" & .Count & " 個です。"
Call .pushItem("2番セカンド殿馬")
Debug.Print .Top.Value & " をPushしました。"
Debug.Print "現在の保有アイテム数は、" & .Count & " 個です。"
Call .pushItem("3番レフト微笑")
Debug.Print .Top.Value & " をPushしました。"
Debug.Print "現在の保有アイテム数は、" & .Count & " 個です。"
Call .pushItem("4番キャッチャー山田")
Debug.Print .Top.Value & " をPushしました。"
Debug.Print "現在の保有アイテム数は、" & .Count & " 個です。"
Debug.Print "===================="
Dim i As Long
For i = 1 To .Count
Debug.Print "上から" & StrConv(CStr(i), vbWide) & "番目は、" & _
.Item(i).Value & " です。"
Next
Debug.Print "===================="
Do While .ItemExists
Debug.Print .popItem.Value & " をPopしました。"
Debug.Print "現在の保有アイテム数は、" & .Count & " 個です。"
Loop
End With
End Sub
ひつこいけれど、Topプロパティ、Itemプロパティ、popItemメソッドの全てがStringStackItemオブジェクトを返すので、その値が欲しいときはValueプロパティを参照する必要がある。
その分、少しコードの量は増えた。(Top.Valueなどという実に安っぽい記述が頻出することにもなったw)
もちろん、実行結果は上と同じ。