【VBA編】(準備)クラスについて(2)
クラスについて その2
前回に引き続きクラスについてもう少し見ていきましょう。
インスタンス(オブジェクト)
クラスを使うにあたっての利点の一つは1つのクラスから複数のインスタンス(実体)を作れることではないでしょうか。
特に同じようで若干異なる処理をさせたいときなどに役に立つと思います。
次のような処理をしたい場合、クラスを使わないで書くとしたらどうなるでしょうか。
[処理結果]
ひとまずこのようにしてみました。
各種類の次の書き込み行をそれぞれの変数で制御しているのが煩わしい感じがします。
'[標準モジュール] Option Explicit Public Enum CL Shokuhin = 5 Bunbougu = 7 Kaden = 9 End Enum Public Sub Main() Dim r As Long Dim eRow As Long Dim shurui As String Dim rwShokuhin As Long Dim rwBunbougu As Long Dim rwKaden As Long Cells(1, CL.Shokuhin).Value = "食品" Cells(1, CL.Bunbougu).Value = "文房具" Cells(1, CL.Kaden).Value = "家電" rwShokuhin = 2 rwBunbougu = 2 rwKaden = 2 eRow = Cells(Rows.Count, 1).End(xlUp).Row For r = 2 To eRow shurui = Cells(r, 1).Value Select Case shurui Case "食品" Cells(rwShokuhin, CL.Shokuhin).Value = Cells(r, 2).Value rwShokuhin = rwShokuhin + 1 Case "文房具" Cells(rwBunbougu, CL.Bunbougu).Value = Cells(r, 2).Value rwBunbougu = rwBunbougu + 1 Case "家電" Cells(rwKaden, CL.Kaden).Value = Cells(r, 2).Value rwKaden = rwKaden + 1 End Select Next MsgBox "終了しました。" End Sub
クラスを使って書いてみます。
'[クラスモジュール - classPlot] Option Explicit Dim mShurui As String Dim mRow As Long Dim mCol As Long '初期化処理 '自分の担当する種類を覚えて、自分の列の1行目に表示する '[引数] <- aShurui / String:自分の担当する種類, aCol / Long:表示先列 Public Sub Initialize(ByRef aShurui As String, ByRef aCol As Long) mShurui = aShurui mRow = 2 mCol = aCol Cells(1, mCol).Value = mShurui End Sub '品目追加処理 '表示先セルに品目を表示して表示先行をインクリメントする '[引数] <- aItem / String:品目 Public Sub AddItem(ByRef aItem As String) Cells(mRow, mCol).Value = aItem mRow = mRow + 1 End Sub '自分の種類を返す Public Property Get Shurui() As String Shurui = mShurui End Property
「初期化処理」では、自分の担当する「種類」と「表示先の列」を引数として取っています。
「種類」と「列」を変数に格納したら、1行目に種類を表示します。
「品目」の表示は2行目からなので、mRow に 2 をセットしています。
なお、今回は 「Class_Initialize()」での処理は不要と判断して定義していません。
「品目追加処理」では、「品目」を引数として取り、表示先のセルに表示しています。
その後、行を更新し次の表示に備えています。
あと、呼び出し元で利用するために、「種類」を返す Property Get を定義しています。
今回は、「種類」の設定は「初期化処理」のみで行うので、Property Let は定義しませんでした。
では、呼び出し元を見てみましょう。
'[標準モジュール] Option Explicit Public Sub Main_class() Dim r As Long Dim eRow As Long Dim item As String Dim cShokuhin As classPlot Dim cBunbougu As classPlot Dim cKaden As classPlot Set cShokuhin = New classPlot Set cBunbougu = New classPlot Set cKaden = New classPlot '種類と表示先列を引数として振り分け担当(オブジェクト)を作る Call cShokuhin.Initialize("食品", 5) Call cBunbougu.Initialize("文房具", 7) Call cKaden.Initialize("家電", 9) eRow = Cells(Rows.Count, 1).End(xlUp).Row For r = 2 To eRow item = Cells(r, 2).Value Select Case Cells(r, 1).Value Case cShokuhin.Shurui Call cShokuhin.AddItem(item) Case cBunbougu.Shurui Call cBunbougu.AddItem(item) Case cKaden.Shurui Call cKaden.AddItem(item) End Select Next MsgBox "終了しました。" End Sub
「食品」「文房具」「家電」を担当する「cShokuhin」「cBunbougu」「cKaden」を宣言し、
それぞれのインスタンスを生成しています(New しています)。
それぞれに該当の引数を与えて「初期化処理」をした後、A列を順番に見ていって
該当するインスタンスの「AddItem()」を呼び出して、「品目」を表示しています。
先ほどのコードと異なる点は、「品目」を表示する際に、呼び出し元では「何行目に表示するか」を一切気にしていないというところです。
また、列についても、「初期化処理」で設定してしまえば、以降は気にする必要がありません。
クラスを使うとコードが長くなる場合がありますが、必要な設定や処理はクラスに任せて、
呼び出し側では「あとはお願い」的にコードが書けるので考慮すべきことが少なくなります。
ということは、バグが混入する余地を減らすことにもつながります。
配列への格納
複数のインスタンスを作る際、上記の例のようにそれぞれで変数を宣言してもいいのですが、
宣言すべき変数が多くなる場合や、そもそもいくつ変数が必要になるかがわからない場合があるかと思います。
そういった場合はインスタンスを配列に格納すると便利です。
'[クラスモジュール - classPlayer] Option Explicit Dim mIndex As Long Dim mColor As Long Dim mBaseColor As Long Dim mRow As Long Dim mCol As Long Private Sub Class_Initialize() mRow = 1 mBaseColor = Cells(1, 1).Interior.Color End Sub Public Sub Initialize(ByRef aIndex As Long, ByRef aCol As Long) mIndex = aIndex mColor = Cells(1, aCol).Interior.Color mCol = aCol End Sub Public Property Get MyIndex() As Long MyIndex = mIndex End Property Public Property Get MyRow() As Long MyRow = mRow End Property Public Property Get MyColumn() As Long MyColumn = mCol End Property Public Function GoNext() As Boolean Dim res As Boolean Dim offsetR As Long Dim offsetC As Long res = True offsetR = 0 offsetC = 0 If Cells(mRow, mCol + 1).Interior.Color <> mBaseColor And Cells(mRow, mCol + 1).Interior.Color <> mColor Then offsetC = 1 ElseIf Cells(mRow, mCol - 1).Interior.Color <> mBaseColor And Cells(mRow, mCol - 1).Interior.Color <> mColor Then offsetC = -1 ElseIf Cells(mRow + 1, mCol).Interior.Color <> mBaseColor And Cells(mRow + 1, mCol).Interior.Color <> mColor Then offsetR = 1 Else res = False End If If res Then mRow = mRow + offsetR mCol = mCol + offsetC Application.Wait [Now()+"00:00:00.1"] Cells(mRow, mCol).Interior.Color = mColor End If GoNext = res End Function
今回はクラスモジュールのコード説明は省略します。
呼び出し側はこのようにしています。
'[標準モジュール] Option Explicit Option Base 1 Public Sub Main() Dim cPlayers() As classPlayer Dim i As Long Dim flagNext As Boolean ReDim cPlayers(4) For i = 1 To UBound(cPlayers) Set cPlayers(i) = New classPlayer Call cPlayers(i).Initialize(i, getColumn(i)) Next For i = 1 To UBound(cPlayers) Do flagNext = cPlayers(i).GoNext Loop Until flagNext = False Cells(cPlayers(i).MyRow, cPlayers(i).MyColumn).Value = cPlayers(i).MyIndex Next End Sub Private Function getColumn(ByRef aIndex As Long) As Long Dim res As Long Select Case aIndex Case 1 res = 2 Case 2 res = 6 Case 3 res = 10 Case 4 res = 14 End Select getColumn = res End Function
まずは、classPlayer 型の配列 cPlayers() を宣言しています。
要素数はここで設定してもいいのですが、今回は ReDim で設定しています。
ひとつめの For - Next ですが、
[ Set cPlayers(i) = New classPlayer ] とすることによって配列の要素ごとにインスタンスを格納しています。
そのあとで [ Call cPlayers(i).Initialize() ] を実行して、インスタンスの初期化を行っています。
次の For - Next では、配列から取り出したインスタンスを利用して処理を行っています。
このように配列に格納することで、取り回しを楽にすることができます。
(実際、この後で書いていくニューラルネットワークのコードでは、この手法を利用しています。)
ちなみに [ Option Base 1 ] で配列のインデックスを 1 始まりにしているのは、
今回のコードに都合がいいようにそうしているだけです。
上記コードはExcelシートにこのようなあみだくじを作成して実行してみてください。
色は同じでなくても構いません。
(実行前に復帰用にシートをコピーしておくことをお勧めします。)
これでひとまずクラスの説明は終わります。
クラスを使えるようにしましょう
VBAはクラスを使わなくてもコードを書くことは可能です。
しかし、クラスを使うことで、より簡単に、便利に、安全にコードを書くことができるようになります。
もちろん、適材適所がありますので、なんでもかんでもクラスにすればいいというわけではありません。
ただ、必要な時にクラスが使えるようにしておくことで、手持ちの武器が増え、表現の幅が広がります。
クラスを使うコードは使わないコードとちょっと発想の転換が必要というか頭の使い方が異なると思うので、はじめはなかなか慣れないかもしれません。
そういった場合はちょっとVBAから離れて「オブジェクト指向」の考え方のほうに目を向けてみるのもいいかと思います。