VBAのクラス

ExcelVBAの基礎

クラスとは

 ExcelVBAでのクラスの使用率は低いように思います。ただ、他の言語ではオブジェクト指向プログラミングが当たり前で、クラスをよく利用しています。

 クラスはオブジェクト指向プログラミングでは、オブジェクトの設計図の役割を果たしています。
 オブジェクトという言葉が出てきましたが、これまで「オブジェクトとはExcelVBAの部品だ」と話してきました。
 実は、これまで使ってきたExcelVBAの部品もオブジェクト指向で書かれているのです。
 その設計図がクラスというわけです。

 ですから、ある程度VBAでプログラミングをした人なら、すでにオブジェクト指向の恩恵に預かって使っていたという事になります。
 オブジェクト指向の話を初心者の方にイメージしてもらうのは難しいのですが、これまでの変数、関数(プロシージャ)という世界から一歩進化して、変数や関数を組み合わせた仕組みを一つの塊りとして扱う事ができるようになるのが、オブジェクト指向の考え方です。
 プログラムが大きくなったり、複雑な仕組みを作るときに、開発性やメンテナンス性に大きく影響してきます。

 だた、逆を言いますと簡単なプログラムでは無用の長物でもあります。
 ここでは中級者への予習というスタンスで紹介していきます。

クラスを作るときは、メニューの「挿入」「クラスモジュール」を選択します。

メンバ

 クラスの構成要素には、プロパティ(変数)メソッド(関数)があります。これらを総称してメンバと呼びます。
メンバにはそれぞれ、Public, Private, Protect というアクセス指定子があります。
 このアクセス指定子でメンバの使用できる範囲を制限することができます。

メンバ

  • プロパティ(変数の役割)
  • メソッド(関数の役割)

アクセス指定子

  • Public  クラス外側からでもアクセスできます。(外部アクセス用)
  • Private  クラス内部で使用します。(クラス内のみで使用)
  • Protected  クラス内部と継承先のクラス内で使用できます。

プロパティ

 プロパティは変数のようなものですが、もう少し高機能な使い方ができます。
 プロパティは、値を取得または代入するときに専用の関数を経由しますので、そこで値が不正でないかチェックしたり、イベントを発生させるような事ができます。

 まずは実例をみてください。

'// クラス内部 ///

Private mValue as Long

Public Property Let Value(n As Long)
	mValue =n
End Property

Public Property Get Value() As long
	Value = mValue
End Property


'// クラスの外 (標準モジュールなど) からのアクセス //

myClass.Value = 5   'ここでは、Let が実行されています。
msgbox myClass.Value 'ここでは、Get が実行されています。

 プロパティはクラスの外部から変数のようにやり取りする名前で、それ自体が値を保持するのではありません。
 値は、それに関連するメンバ変数に保存します。メンバ変数の宣言は以下の通りです。
 変数名は何でもいいですが、プロパティの変数だとわかるようにプロパティ名を付けます。

 Private mプロパティ名 As 型名
(メンバとわかりやすいように小文字の m を付けています)

Let と Get はそれどれ、代入と参照の役割があります。

Let

Property Let プロパティ名(value As 型名)
	メンバ変数 = value
End Property

Let はプロパティに代入するときに実行される関数です。
引数 (value) に設定された値が渡されます。引数名は何でもOKです。
プロパティ名とメンバ変数は関連があるので、上記の例では、Value , mValue のようにしてわかりやすい名前にしています。

Get

Property Get プロパティ名() As 型名
	プロパティ名 = メンバ変数
End Property

Get はプロパティを取得するときに実行される関数です。
Function 関数と同じように戻り値が取得される値になります。
プロパティ名とメンバ変数のなめの付け方は Let と同じです。

プロパティは外部とのアクセスで使いますので、アクセス指定子は Public を使います。
(省略した場合は Public となります。)

ちなみに、Letを宣言せず、Getだけ宣言するとプロパティは読込専用となります。
この場合は、値をセットするのはクラス内部のみで行うことになります。

Set

プロパティの型がシートやクラスなどの オブジェクト のときは、Let の代わりに Set を入力します。

Property Set プロパティ名(引数名 As オブジェクト型)

宣言とインスタンス化

クラスは宣言した後に、インスタンス化を行います。
インスタンスとは「実体」という意味になります。クラスは「設計図」ですから、それを使える形にするのがインスタンス化です。具体的にはクラスオブジェクトに必要な初期化を行って使えるようにする作業です。

Dim myClass As Class1		'宣言
Set myClass = New Class1	'インスタンス化

宣言はクラス名に続いて使いたいクラスを指定します。
インスタンス化は、New クラス としてインスタンス化し、クラス名に Set を付けて代入します。

インスタンス化しないで、クラスを使用しようとするとエラーが発生しますので注意してください。
( Set の付け忘れにも注意しましょう。)

コンストラクタ と デストラクタ

クラスが生成される時に暗黙的に呼ばれる関数をコンストラクタといいます。
通常は、ここに初期化したい内容を書きます。

また、クラスが破棄される時に暗黙的に呼ばれる関数をデストラクタといいます。
デストラクタにはクラスが破棄される時に処理したい内容を書きます。

これら2つはVBAのイベントとして設けられているので、VBEで選択できるようになっています。

コンストラクタは Initialize で、クラスを New した時に呼ばれます。
デストラクタは Terminate でクラスを Nothing とした時に呼ばれます。

Initialize はイベントなので引数を渡すことはできません。
メンバ変数を外からの値で初期化したい場合は、Newの後に代入する事になります。

Terminate はクラスが破棄され時に呼ばれます。ここに破棄される時行いたい処理を書きますが、メモリーの開放などは自動で行われるので、それほど意識する必要はありません。

クラスの実例

 クラスは実際に書いて動かしてみないと、なかなか実感がわかないと思います。
 以下で簡単なRPGもどきのプログラムを書きながら説明します。

まずはクラスを書いていきます。
VBEのメニューから、「挿入」「クラスモジュール」として新しいクラスモジュールを開き、名前をCCharaとします。

クラス名を変更したいときは、プロパティウインドウの(オブジェクト名)の項目を変更します。

'RPGのキャラクタークラス

'メンバ変数
Private m名前 As String
Private m経験値 As Long
Private m体力 As Long

最初は、メンバ変数を宣言します。今回は3つとします。

'コンストラクタ
Private Sub Class_Initialize()
    m名前 = "foo"
    m経験値 = 1
    m体力 = 20
End Sub

次にコンストラクタを書きます。コンストラクタは、イベント関数なので、プログラムを書くウインドウの上部から、Classを選択して、Initialize を選択すると自動で記述されます。

中身は、各メンバ変数を初期値を設定します。
この値は、クラスを生成するとすぐに実行されますが、その後で好きな値に変更することもできます。

次はプロパティを書いていきます。

'プロパティ
Public Property Let 名前(s As String)
    m名前 = s
End Property

Public Property Get 名前() As String
    名前 = m名前
End Property

Public Property Let 経験値(n As Long)
    m経験値 = n
End Property

Public Property Get 経験値() As Long
    経験値 = m経験値
End Property

Public Property Let 体力(n As Long)
    m体力 = n
End Property

Public Property Get 体力() As Long
    体力 = m体力
    If m体力 < 1 Then
        MsgBox "体力がもたない..."
    End If
End Property

最後の  Get 体力() では、If文を用いてメッセージを表示させています。
ここでは体力が 0以下になったら弱音を吐くようにしています。
このようにクラスのプロパティでは、参照の時に何かさせたりという事が可能になります。

次は、このクラスをインスタンス化してプログラムを書いてみましょう。
クラスは設計図なので、実体は標準モジュールに書きます。

Function Shaffle() As Long
    'ランダム値を得る
    Randomize
    Shaffle = Int(60 * Rnd) + 1
End Function

Sub RPG()
    Dim r As Long, i As Long
    Dim str As String
    Dim 相手体力 As Long
    Dim ダメージ As Long
    
    r = 3
    相手体力 = 300

    'クラスをインスタンス化
    Dim c剣士 As CChara
    
    Set c剣士 = New CChara
    'コンストラクタが実行される
    
    c剣士.名前 = "剣士"
    Range("B3") = c剣士.名前
    
    'バトル開始
    
    Range("C3:D103").ClearContents
    
     For i = 0 To 10
        'こちらの攻撃
        ダメージ = Shaffle()
        相手体力 = 相手体力 - ダメージ
        Cells(r, "D") = c剣士.体力
        
        If 相手体力 < 1 Then
            c剣士.経験値 = c剣士.経験値 + 100
            Cells(r, "C") = c剣士.名前 & "は、相手を倒した! 経験値が" & c剣士.経験値 & "になった!"
            MsgBox "勝利!"
            Exit Sub
        Else
            Cells(r, "C") = c剣士.名前 & "は、相手に" & ダメージ & "のダメージを与えた!"
            r = r + 1
        End If
        
        '相手の攻撃
        ダメージ = Shaffle()
        Dim val As Long
        val = c剣士.体力 - ダメージ     'get(ここは0ではないのでメッセージは表示されない)
        c剣士.体力 = val                'let
        
        'c剣士.体力 = c剣士.体力 - ダメージ
        
        Cells(r, "D") = c剣士.体力      'get
        If c剣士.体力 < 1 Then          'get
            Cells(r, "C") = c剣士.名前 & "は、負けてしまった..."
            MsgBox "敗北..."
            Exit Sub
        Else
            Cells(r, "C") = c剣士.名前 & "は、" & ダメージ & "のダメージを受けた..."
            r = r + 1
        End If
    
    Next i
    
    MsgBox "決着がつかず、お互いに立ち去った。。。"

End Sub

 最初の Shaffle は、ランダムで1から60の値を得ています。
 その後の RPG関数がメインの処理となります。
 まず、相手の退職が 300 と多めに設定されています.。
 次に主人公である剣士のクラスを定義して Set でインスタンス化.

 インスタンス化と同時に初期化が行われます。
 剣士の体力が 20 に対して、相手は 300 ですから十中八九負けることになると思います。

 名前を「剣士」にセットしたら、バトル開始です。

 まずは剣士の攻撃。ランダムで相手にダメージを与えます。
 続いて、相手の攻撃。剣士の体力は 20 ですので、下手をすると一発でKOです。

 運が良ければ、10回戦行い、それ以上はバトルせずに、「決着がつかず、お互いに立ち去った。。。」となり終了です。

 それまでに勝負がつけば、メッセージを出して終了します。

 このプログラムの面白いところは、剣士の体力を参照した時、0以下なら弱音を吐く点です。
 剣士のダメージを削る処理は、

    c剣士.体力 = c剣士.体力 – ダメージ

 と一文で書いても良かったのですが、内部の動きがわかるように、変数val を使って処理を分けてみました。
 Getで剣士の体力を参照するタイミングで、0以下ならメッセージを出します。
 今回、参照は3回あります。ただ、一回目はダメージを受ける前なので 0以下という事はありません。
 というわけで、体力が無くなった場合は、2回弱音を吐いて終了となります。

応用1

 弱音を吐くの1回にしたい場合はどうしたらよいでしょうか?
 プロパティの参照で弱音を吐くのですから、それを1回にすれば解決ですね。
 2回目のGetを、
    Cells(r, “D”) = val    ‘get
 とすればOKです。

応用2

 今回は、バトルの処理を標準モジュールに書きましたが、クラスのメソッドとして書く方法もあります。
 この場合は、プロパティを用いずに、直接メンバ変数を使えますので、書き換える必要があります。
 プロパティを使わないので弱音を吐くメッセージも無くなってしまします。

 出来たら、標準モジュールでクラスメソッドを呼び出すようにします。

 このように、関数をクラス内に持つのか、それとも外に出して書いたらいいのかというのは迷う所です。
 これといった正解はないのかもしれませんが、今回はセルに書き出す部分が外にあった方が良いという判断で外に出しています。

まとめ

 クラスは、宣言の仕方やプロパティの使い方に注意すれば、書き方自体はそれほど難しくありません。
 ただ、クラスは、後の拡張や更新を踏まえて、そのようなメンバ変数やメンバ関数を持つかという点で本領を発揮します。
 使いこなすためには、とにかく書いてみて慣れるしかありません。

タイトルとURLをコピーしました