クラスモジュールを用いたStackの改良
クラスモジュールを用いた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)
もちろん、実行結果は上と同じ。