はじめに

VBAのクラスは、他のプログラミング言語(C++、Java、C#など)のような本格的なオブジェクト指向の機能をすべて備えているわけではありません

そのため、他の言語では当たり前にできることが、VBAではできなかったり、工夫が必要だったりします。

この記事では、VBAのクラスで「できないこと」と「その理由」について、一覧で分かりやすく解説します。

VBAと他言語のクラス機能の比較一覧

VBAのクラスは、C++、Java、C# などの主要なオブジェクト指向言語に比べて機能が限定されています。

以下の表では、そうしたVBAに存在しない機能と、それに対する回避策や代替手段があるかどうかをまとめました。

機能 VBAでの対応方法 機能説明
継承 不可。代替案として親クラスのインスタンスを子クラスの内部に持たせる 基底クラスの機能を子クラスに継承して再利用する仕組み
多重継承 不可 複数のクラスを同時に継承する仕組み
抽象クラス 不可 インスタンス化できず、子クラスにメソッドの実装を強制するクラス
インターフェース 不可 メソッド名と型定義だけを行い、実装コードはクラスに書く仕組み
ポリモーフィズム 不可 同じインターフェースで異なるクラスを同様に扱える仕組み
コンストラクタのオーバーロード 不可。Class_Initialize の1種類しかない 引数の異なる複数のコンストラクタを定義可能
メソッドのオーバーロード 不可。別名のメソッドを用意して対応 同名のメソッドを引数違いで複数定義可能な仕組み
演算子のオーバーロード 不可 演算子(+ や =)の意味を独自に定義できる仕組み
アクセス修飾子 Public と Private のみ利用可能 アクセスレベルの制御(public, private, protected など)
デストラクタ Class_Terminate あり。ただし呼ばれるタイミングは保証されない オブジェクト破棄時に明示的な処理を実行する仕組み
名前空間 不可。命名規則で回避 クラスや関数をグループ化し、名前の衝突を防ぐ仕組み
静的クラス / 静的メンバ 不可。標準モジュールを使うことで一部代用可能 インスタンスを生成せずに使用できるクラスやメンバ
ジェネリクス 不可。Variant 型や Collection で簡易代用可能 型を抽象化し、再利用性の高いクラスや関数を作る仕組み
例外処理 On Error Goto 等で対応 Try-Catchブロックによる明確な例外処理
属性(アノテーション) 不可 メタ情報をコードに付加する仕組み

なぜVBAにはこれらの機能が無いのか

VBAのクラスには、他の主要なオブジェクト指向言語(C++、Java、C#など)に備わっている多くの機能が存在しません。その理由として、以下のような点が考えられます。

  1. まず、VBAは COM(Component Object Model)をベースとした言語であり、VBAで作成したクラスやオブジェクトはCOMオブジェクトとして扱われます。COMにはクラス継承の仕組みが存在しないため、親クラスを変更した際に子クラスへ影響を与えないよう制御することが極めて困難です。
  2. また、VBAにはC#やJavaのようなガーベジコレクション(メモリ自動解放機能)がありません。そのため、リソース管理をプログラマが手動で行う必要があります。VBAにも Class_Terminate によるデストラクタ相当の仕組みはありますが、実行タイミングが保証されないため、親クラスと子クラスの間でリソース解放の順序がずれてしまう恐れがあります。このことからも、継承機能の導入はリスクが高いと考えられます。
  3. さらに、VBAは主にExcelなどOffice製品上での自動化やツール作成を目的としており、複雑な継承やインターフェースを必要とする場面が少ないです。仮にそうした機能を導入したとしても、活用される機会が限られ、導入コストに見合わないと判断された可能性があります。

以上のように、

  • COMの仕様上の制約(継承の仕組みがない)
  • メモリ管理が困難(親子で同期がとれない)
  • VBAの利用シーンにおける必要性の低さ(ほとんど使われそうにない)

といった複数の理由から、VBAには高度なオブジェクト指向機能があえて実装されていないと考えられます。

継承の代替案コード例

とはいえ、継承の知識を持っている人からすると、「やっぱり継承ぐらいはしたいなあ」、というのは当然出てきます。

完全な方法ではありませんが、以下のように親クラスに該当するクラスを子クラスの内部で使うようにすると、継承に似た構造にすることは可能です。

なお、この方法でも上に挙げた一覧の通り、オーバーライドは不可、などの制限があります。

具体的にどのように実装するのかが以下のコードになります。

親クラス例:ログ出力クラス

子クラス例:業務処理クラス

Loggerは「継承された親クラス」という書き方ではなく、子クラスの中で領域確保しており、通常の継承の考え方とは異なりますが、目的は果たせています。

また、クラスそれぞれが独立しており、互いに干渉していないため、VBAでは継承の代替手段として「委譲(別のクラスに処理を任せる方法)」を使うことで、十分に実用的なクラス設計が可能です。

おまけ:私の失敗談「FileSystemObjectは継承できません」

VBAのクラスについて私の失敗談というか、反省した経験があります。

それは私が初めてVBAのクラスモジュールを利用したときのことです。

当時作っていたマクロでVBAのFileSystemObjectクラスに不便を感じたので、FileSystemObjectクラスを継承したサブクラスを作ろうと思ったことがありました。そこで使ったことが無かったクラスモジュールを使うことにしました。ところが実際にクラスモジュールを触ってみて気づきました。

「あれ? もしかしてVBAの自作クラスって継承できないの?」

ヘルプを見たりして、継承が出来ないことに気が付きました。

「あ、FileSystemObjectはCOM(COMには継承の仕組みはそもそも無い)だから継承出来ないじゃん、どうしよう・・・」

このときの私の頭には「クラスは継承が出来て当たり前」という認識だったため、いきなりつまづくことになりました。さらに、継承できない場合の回避策としてインターフェースを思いついたのですが、インターフェースも使えない(そもそも無い)ということも分かり、やろうと思っていたことが単純にはいかないことに気が付き、

「あー、どうしようかなー・・・」

と、しばらく固まったのをよく覚えています。

これは、オブジェクト指向プログラミングの機能はどの言語も大体同じだ、という思い込みによる認識誤りによるものです。こういうのは技術者ではあるあるですが、あまりにもVBAのクラス構成が当時の自分の常識だと思っていたクラス構成と掛け離れていたため、結構な回り道をすることになりました。

今思うと、VBAのクラスは何ができないのか、という整理された情報が無かった・見つけられなかった、というのもあります。

この記事はその時の自分向けへの記事でもあります。