作成日 2008/1/5
最終更新日 2016/5/1

オブジェクト指向プログラミングに関して、VBAで出来ないこと

 ここでは、オブジェクト指向プログラミングに関して、VBAで出来ないことについて説明します(※1)。
 VBAでオブジェクト指向プログラミングをするときは、設計段階でVBAで何が出来ないのかを把握しておく必要があります。
 出来ないことを他の方法で回避しようとすると、ソース行数が増えたり、デバッグが面倒なことになるということを覚えて欲しいです。
(実装や単体テストに工数がかかるようになるということを覚えて欲しいです。)

-----
※1:出来ることを説明するのではなく、出来ないことを説明する理由は、GoogleやVBAのヘルプにて、出来ることを調べるのは意外と簡単なのですが、出来ないことを調べるのはとても難しいからです。
(出来ないことはヘルプに載っていないことが多く、検索条件が悪かったのかの判断が難しいためです。)

1.継承がサポートされていない
2.インタフェースのみの継承に問題がある
3.エラーオブジェクトに問題がある
4.パッケージという概念が無い
5.1つのモジュールにクラススコープのメソッド・属性とインスタンススコープのメソッド・属性を同時に定義できない
6.コンストラクタに引数を持たせられない
7.オーバーロードができない
8.リフレクション機能がいまいち


1.継承がサポートされていない

 VBAでは継承がサポートされていません。

※1つだけ補足しますが、ここで述べているのは、新しく作成するクラスは別のクラスを継承できないという意味です。
すでにあるクラス(特にExcelライブラリやOfficeライブラリに含まれるクラス)は別のクラスを継承していたりします。

■問題点1:
 多態性 をどうするか…。
(回避策1or2を使えば、設計に関しては特に意識する必要はなくなりますが、実装にかかる工数が増えます。)

■問題点2:
 デザインパターン(例えばGofのデザインパターン)の適用が難しくなります。
(デザインパターンの中には継承を使用したものがあるためです。)

■回避策1:
 継承の代わり(?)に  インタフェースのみの継承 + コンポジション  ではどうか?
これも問題があったりする。(インタフェースののみの継承に問題があるため

■回避策2:
 ちなみに、だるまが良く行う方法は、  実行時バインディング + コンポジション  です。
ただし、これだと、コンパイル時に行ってくれるチェックが緩くなってしまうため、デバッグに工数がかかるようになります。

このページのトップへ

2.インタフェースのみの継承に問題がある

 VBAではインタフェースのみの継承について2点問題があります。
 VBAでは「Implements」ステートメントを使用することによりインタフェースのみの継承を行うことが出来ます。
「Implements」ステートメントの詳細についてはVBAのヘルプを参照してください。

■問題点1:
 「Implements」ステートメントはVBAのバージョンが6以上でないとサポートされてないです。
つまり、VBAのバージョンが5であるExcel97やExcel X for macではインタフェースのみの継承は行うことが出来ません。

■問題点2:
 実装したメソッドの呼び出し方法についてです。
実装したメソッドを呼ぶ出すにはオブジェクトを一旦、インタフェースクラスにキャストする必要があります。
クラス1がクラス2のインタフェースを実装したとします(図1)。

インタフェースのみの実装クラス図

図1 クラス図

 この場合、クラス1はクラス2のaaaメソッドを実装することになります。
じゃあ、
  Dim Obj1 As new Class1
  Call Obj1.aaa
が実行可能かというと、出来ない(コンパイルエラーになります)です。
以下のようにする必要があります。
  Dim Obj1 As new Class1
  Dim Obj2 As Class2
  Set Obj2 = Obj1
  Call Obj2.aaa

■感想:
面倒くさい・・・。
(行数が長くなりソースが読みにくくなります。)

■回避策(問題点2の回避策):
そもそも、何でこういうことになるかというと、サブクラス側でのメソッドが、
 Private Sub Class2_aaa()
 End Sub
であって、
 Public Sub aaa()
 End Sub
ではないからです。
なので、
 Public Sub aaa()
 End Sub
を作ってしまい、
 Private Sub Class2_aaa()
  call aaa
 End Sub
とすれば良いです。行数は少し増えますが、クラスを使う側のソースは増えないので良いと思います。

 hogeさんへ:掲示板への記載(データNo:201)、ありがとうございました。

このページのトップへ

3.エラーオブジェクトに問題がある

 エラーオブジェクト(VBAライブラリのErrObjectクラス)に問題があります。

■問題点1:
 エラーに含むことの出来る情報が少なすぎです。
 エラーオブジェクトをネストすることも出来ません。
(javaでJ2SE1.4以上ではjava.lang.Throwableクラス にcause属性が定義されています。これに相当するものがVBAのエラーオブジェクトにはありません。)
 また、javaにはあるのですが、VBAのエラーオブジェクトには関数の呼び出し履歴と行数の一覧がない(スタックトレースがない)のも問題があります(どこでエラーが発生したのかがわかりにくい)。

■問題点2:
 VBAではエラーの種類を番号(Err.Number)で識別します。
 しかし、エラーを番号で識別するのは問題があります(番号が重複してしまう恐れがある)。

■回避方法1(問題点1に対する回避方法):
 この問題を回避するために、独自のエラー用のクラスを作成することも考えられます。
ただし、スタックトレースは関数名までしか出来ないです(※1)。
 
 メソッドの引数にエラーを渡す引数を追加するか、戻り値でエラーを返すようにする(※2)必要が出てきますが、それをやってしまうとOn Error goto 文が使用しにくくなります。
 
■回避方法2(問題点2に対する回避方法):
 エラーを、番号でJavaではjava.lang.Exceptionクラスを継承した例外クラスを自作で出来ますが、VBAではそのようなことは出来ません。
 回避方法1とあわせ(エラー用のオブジェクトを自作し)、文字列、もしくはバイナリデータ(バイト配列)から、エラーの種類がわかるようにするか・・・。面倒だけど(工数がかかるけど)。
 
■回避方法3(?):
 それでも、VBA標準のエラーオブジェクトを使用する。
 プログラムが小さい場合はこれでも問題は無いです。

-----
※1:行番号を記述しておいて、Erl関数を使用するということをすれば、行数も取得できますが、お勧めしないです。だって大変だから…。
関数名についても、もちろん、自分で手で実装しないと駄目です。疲れるよ…。
※2:メソッドの戻り値でエラーを返すようにした場合、Subステートメントと、Property Let、Property Setステートメントは使用できなくなります。
このページのトップへ

4.パッケージという概念が無い

 VBAではパッケージという概念がないです。
 それに変わるようなものは、無いと思います。
(「ライブラリ」や「プロジェクト」はパッケージに相当するかもしれませんが、しかし、1つのエクセルブック内には1つのプロジェクトしか含められませんので、代わりにはなりません(※1)。)

 これは、大きいプログラムを組んだときに問題になります。

■問題点1:
 クラスをカテゴリ化することが出来ない。そのため、クラス数が多くなってくると、混乱してくる。

■問題点2:
 自作の共通クラス(例えば、文字列操作用のユーティリティクラス、数学計算ルーチンがあるユーティリティクラス、ファイル関連のクラスなど。他のエクセルのマクロやアドインでも使用できそうなクラス)とそうでないクラスの区別がわかりにくくなる。

■問題点3:
 当然ですが、UML2.0のpackageに相当するような可視性(VBAではprivate,publicしかない(※2)。)はないです。
本来だったら、可視性がpackageで十分なメソッド、属性の可視性をpublicにしなければならなくなることがあります。その結果、デバッグが面倒になります。

■回避策(問題点1,2の回避策):
(1)クラスの名前を工夫する。
(2)用途が限定はされますが、グラフシート(Microsoft Excel Objects)を使用する。
 5.1つのモジュールにクラススコープのメソッド・属性とインスタンススコープのメソッド・属性を同時に定義できないにて、 シングルトンクラスを作成したい場合には、Sheet1などを使用すれば良いと説明しています。同じようにグラフ(Graph1など)も同様に使用可能です。
シートとグラフではアイコンが異なるので、カテゴリ分けに使うことが出来ると思います。

 hogeさんへ:掲示板への記載(データNo:202)、ありがとうございました。


-----
※1:
 VBE(Visual Basic Editor)の[ツール]-[参照設定]にて他のプロジェクト(他のエクセルブックやアドインにあるプロジェクト)やVB6.0で作成したDLLを参照するのであれば「ライブラリ」や「プロジェクト」はパッケージの代わりになるのかもしれません。
 しかし、他のプロジェクトを参照するエクセルブックやアドインが開いている必要がありますし、あまり一般的ではないと思います。また、クラスモジュールについては属性の「Instancing」の値を2(PublicNotCreatable)にする必要がありますが(こうしないと参照先のプロジェクトでクラス解決できません)、この属性はVBAのバージョンが6以上でないとサポートされていません。(※Excel 97やExcel X for macのVBAのバージョンは5です。)
 VB6.0で作成したDLLを参照するのは結構やる手ですが、Macでは出来ません(作成したマクロ、アドインがMacで動作しなくなります)。
※2:
 正確には、Friendという可視性もあります。
 ただし、FriendはVBAのバージョンが6以上でないとサポートされていません。(※Excel 97やExcel X for macのVBAのバージョンは5です。)
このページのトップへ

5.1つのモジュールにクラススコープのメソッド・属性とインスタンススコープのメソッド・属性を同時に定義できない

 VBAでは1つのモジュールにクラススコープのメソッド・属性とインスタンススコープのメソッド・属性を同時に定義できません。

 VBAではクラススコープのメソッド・属性は標準モジュールに定義する必要があり、インスタンススコープのメソッド・属性はクラスモジュールorユーザーフォーム内に定義しなければならないためです。

■問題点1:
 例えば、本来であれば可視性がprivateで良いはずのクラススコープの属性をpublicにしなければならなくなります。
つまり、これにより情報隠蔽・カプセル化がうまく出来なくなります。そのため、デバッグに工数がかかるようになります。

■問題点2:
 問題点1により、シングルトンパターンの実装が行いにくいです。


■回避策(問題点2の回避策):
 シートクラス(Sheet1など)を使えばシングルトンパターンを簡単に作成できます。というか、初めから、シングルトンになっています。そして、シングルトンではないようにすることが出来ません
 シートクラスは、新しくオブジェクトを生成できない(new できない)様になっています。
 言っていることが難しいのですが、シートを後から挿入は出来ますが、それは別のクラスになります(分かりにくいのですが、「Sheet」は何個でも作れるが、「Sheet1」は1つしかない)。

 hogeさんへ:掲示板への記載(データNo:202)、ありがとうございました。

このページのトップへ

6.コンストラクタに引数を持たせられない

 VBAではコンストラクタに引数を持たせられないです。

■回避方法:
 initメソッドを作成し、newした後に呼んでもらうしかないと思います。
 ただし、この場合、本当にnewの後にinitが呼ばれているかどうかのチェックはソースを目視で確認するか、単体テストにて確認するしかないです。
(要するにデバッグに工数がかかります。)
このページのトップへ

7.オーバーロードが出来ない

 VBAではオーバーロードが出来ないです。
 
■回避方法:
 1番目の方法としてOptionalキーワード(引数を省略可能にする)やParamArrayキーワード(任意の個数の引数を渡せる)を使用してオーバーロードのように見せかけることが挙げられます。
 ただ、やりすぎるとソースが読みにくくなるので注意してください。(ま、それはオーバロードもそうですが。)
 2番目の方法としては引数をVariant型やObject型にすることでしょうか。しかし、コンパイルチェックが甘くなるのであまりお勧めできません。
 3番目の方法としてはオーバーロードを使用しない設計にすることです。だるま個人の考えではこれが一番良いです。


このページのトップへ

8.リフレクション機能がいまいち

 VBAはリフレクション機能がいまいちです。
 一応、VBIDEを使えばVBAソースを取得できるので、がんばれば多少なんとかなりますが(※1)、自作のクラスに限られます。組み込みのクラス(※2)のメソッド一覧を実行時に取得するのは出来ないです。

 クラスの属性情報(属性名、型、可視性)の一覧、メソッド情報(メソッド名、返り値の型、引数情報、可視性など)一覧を実行時に取得するのは非常に厳しいと思ってください。
 それと、クラス名からクラスのオブジェクトを生成することは無理だと思います。
(Javaなら obj = Class.forName("java.util.HashMap").newInstance(); のような感じで出来ると思うのだが(※3)。)

 VBAで出来るのは、以下の2点くらいしか思いつかなかったです。
 ・TypeName関数を使用してオブジェクトのクラス名を取得する。
 ・CallByName関数(※4)を使用してオブジェクトのメソッド名からメソッドを呼び出す。

-----
※1:
 ここで言っている、「がんばれば」というのは「VBAソースコードを構文解析すれば」という意味です。メソッド情報一覧の取得だけでよいのであれば、それほど大変ではないのですが(※Declare宣言されたメソッドは除く)、属性情報の一覧の取得は面倒です。
※2:
 初めからVBAに搭載されているクラス。例えばVBA.CollectionクラスやExcel.Workbookクラス。
※3:
 このJavaの例はコンストラクタに引数がなかった場合の例です。
コンストラクタに引数がある場合は、もう少し長いコードになります。
※4:
 CallByName関数を使用するには、VBAのバージョンが6以上であることが必要となります。ExcelがWindows版でかつ2000以上であればVBAのバージョンは6以上です。
 

Prev Up Next  Top
このページのトップへ

このページの利用によって発生した、いかなる損害について、このホームページの作成者は責任を負いません。
このページの間違いや嘘を見つけた方、このページに書いて欲しい情報がある方はメールを出すか、掲示板への書き込みをお願いします。

Microsoft 、Windows 、Visual Basic および Excel は米国Microsoft Corporationの米国およびその他の国における登録商標または商標です。
ここではExcel® をエクセル、Visual Basic® for Applications をVBAと表記する場合があります。
Mac 、Mac OS 、Mac OS X は米国Apple Computer,Inc.の登録商標または商標です。
OMG、UML、Unified Modeling Languageは、Object Management Groupの商標または登録商標です。
Sun、Sun Microsystems、サンのロゴマーク、Java、及び、Sun/Solaris/Java に関連するすべての商標およびロゴマークは米国 Sun Microsystems, Inc. の米国およびその他の国における商標または登録商標です。
その他、社名および商品名、システム名称などは、一般に各社の商標または登録商標です。

このホームページの作成者はこれらの会社とはいっさい関係がありません。