クラス

Public Real As Variant
Public Imag As Variant
Public Sub add(a As ComplexTest, b As ComplexTest)
Me.Real = a.Real + b.Real
Me.Imag = a.Imag + b.Imag
End Sub
Public Function add2(a As ComplexTest) As ComplexTest
Dim c As New ComplexTest
c.Real = Me.Real + a.Real
c.Imag = Me.Imag + a.Imag
Set add2 = c
End Function
Public Sub setter(r As Variant, i As Variant)
Real = r
Imag = i
End Sub
Public Function fmt() As String
Dim s
If Me.Imag >= 0 Then s = "+" Else s = "-"
fmt = Me.Real & s & Abs(Me.Imag) & "i"
End Function
Public Function inner(b As ComplexTest) As Variant
inner = Me.Real * b.Real + Me.Imag * b.Imag
End Function

クラスと言っても実はたいしたことはできない。

オブジェクトと言いながらも、実際にできるのは構造体+付属のプロシージャといった程度だ。
オブジェクト指向プログラミングなどとは程遠い。

だからオブジェクトとは言わずにクラスモジュールと称しているのだろう。

まあ、それはそれとしてプログラムのカプセリングに貢献するし、処理手続きの定型化には役立つだろう。
また、VBAではオブジェクト変数は配列にできないので、オブジェクトをクラスに取り込んで、クラスを配列にする、というテクニックが必要となってくる。

クラスは特に形式的に説明しても伝わりづらいので、実例で説明をする。

ここでは複素数を扱うクラスを想定した。

複素数と聞くだけで気分が悪くなる人もいるかも知れないが、複素数というのは一つの“数”なのに二つの“数値”(実数部と虚数部)を持っているということが奇異なだけだ。
二つの要素を持つ配列変数と言っても良いかもしれない。

まず、注意が必要なのは、通常のプログラムは「標準モジュール」に記述するのに対して、クラス(モジュール)は「クラスモジュール」に記述する。
具体的には、VBEのプロジェクトにおいて挿入→クラスモジュールで箱を作る。
「オブジェクト名」のところがクラス名になるのでここではComplexTestと設定する。
(Complexというのは英語で複素数のこと)

Public Real As Variant
Public Imag As Variant

こんな感じでこのクラスの持つプロパティを変数として定義する。
Privateで隠蔽してProperty手続きで読み書きする方法もあるが、面倒なのでこうやった。
もちろんメソッドで読み書きできるようにしても良い。

値を保存するだけならこれだけで良い。構造体のようなものだ。
これを使うプログラムはどんな感じになるのか説明をする。
まずは変数の定義。

Dim a As New ComplexTest

これは下の記述の省略形である。

Dim a As ComplexTest
Set a = New ComplexText ‘インスタンス生成

通常のオブジェクト指向言語ではコンストラクタというのがあって、引数を渡して初期値の設定等をそこでやったりするのだが、VBAではそんなことはできない。
コンストラクタというのはインスタンスの生成をしたときに呼ばれる処理のことだが、VBAではそれはあるのだが、形としてはクラスに対するイベントのようだ。

これはエディタ上でイベント選択をしてみると((General)ではなくClassにする)、Initialize(Sub Class_Initialize())とTerminate(Sub Class_Terminate)というものが見つかる。
後者はデクストラスタに相当してこれは作った変数が“消えた”時に呼ばれるもの。

オブジェクトのプロパティを操作するには次のようにする。

a.Real = 3
a.Imag = 4

こんな感じで値を設定(代入)できる。
もちろん同じ要領で読み出しもできる。
まあ、これでも良いのだがいまいちなので以下のようなプロシージャを記述する。

Public Sub setter(r As Variant, i As Variant)
Real = r
Imag = i
End Sub

こうすれば以下のように値を設定(代入)することが出来る。

Call a.setter(3,4)

Callが美しくないのだが、VBAの制限だから仕方ない。
オブジェクトを扱うプロシージャでは必ずCallをつける必要がある、と同じ論理ではと思う。

さて、クラスを作るとそれを使って演算したくなるものだ。
例えばさらに2つ変数を追加して c = a + b みたいな感じだ。
しかしそれは叶わない。

オブジェクト指向言語には用意されているべき“演算子のオーバーロード”がないからだ。
(まあ、それ以前に“継承”すらないのだが)

仕方がないのでプロシージャ(呼ぶ方からはメソッド)で作ってみる。
二つの変数(aとb)を加算して別の変数(c)に入れてみよう。
呼ぶ方の想定としては

Call c.add(a,b)

という感じだ。cへの操作(メソッド)として、引数aとbを加算してその数値を入れる。

Public Sub add(a As ComplexTest, b As ComplexTest)
Me.Real = a.Real + b.Real
Me.Imag = a.Imag + b.Imag
End Sub

ここでMeという未定義の変数が出てくる。
Meはユーザーフォームを扱ったことのある人ならお馴染みかも知れない。
上記のように呼べばcを指している。
cのメソッドとして呼んでいるので、Meとはcのことになると考えればよい。

別のやり方で同じようなことをやってみよう。
メソッドというのは値を返すこともできる。

変数aのメソッドとして変数bを加算して、その結果をメソッドの値として返す。

c = a.add2(b)

こんな感じで呼んだほうがきれいに見える。
この指針でプログラムを作ると下のようになる。

Public Function add2(a As ComplexTest) As ComplexTest
Dim c As New ComplexTest
c.Real = Me.Real + a.Real
c.Imag = Me.Imag + a.Imag
Set add2 = c
End Function

わざわざ変数cを作っていることを不思議に思ったかもしれない。
下のようにすればプログラムも短いのではと。

‘このプログラムではエラーになる!
Dim c As New ComplexTest
add2.Real = Me.Real + a.Real
add2.Imag = Me.Imag + a.Imag
End Function

しかしこのプログラムはエラーになって動かない。
私自身、これに引っ掛かってしばらくハマってしまった。

当り前だがクラスで返す手続きも書けるのだから文字列や数値を返す手続きも書ける。

変数aを複素数表記の文字列を返す(fmt)と内積を返す(inner)メソッドを参考に作成した。
両方とも値を返すので関数Functionになる。

呼ぶ方のイメージとしては

Debug.Print a.fmt
Debug.Print a.inner(b)

とこんな感じになる。
これを実現するプログラムは下のようになる。

Public Function fmt() As String
Dim s
If Me.Imag >= 0 Then s = "+" Else s = "-"
fmt = Me.Real & s & Abs(Me.Imag) & "i"
End Function
Public Function inner(b As ComplexTest) As Variant
inner = Me.Real * b.Real + Me.Imag * b.Imag
End Function
-