PythonやJavaやC++など、オブジェクト指向プログラミング言語ではクラスの継承がサポートされており、保守性や再利用性の高いコードを書くことができます。
そうなると、VBAではクラスの継承(のようなこと)はできるの?と思う方は少なからずいると思います。結論としては、VBAにはクラスの継承に相当するものはありません。
しかし、それっぽいものを擬似的に作ったり、JavaのInterface構文に似たImplementsという機能があります。今回は、クラスの継承を擬似的に作る方法を紹介します。
(Implementsは次の記事で紹介させていただきます。)
①やりたいことのイメージ
例えば、PythonではABC(Abstract Base Class)で抽象基底クラスを定義し、クラスの継承を組み合わせることで下記のようなことが可能です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
from abc import ABC, abstractmethod # 抽象基底クラス class Job(ABC): def __init__(self, name): self.name = name @abstractmethod def attack(self): pass def defense(self): return f"{self.name}は身を守っている" # Jobを継承したナイトクラス class Knight(Job): def attack(self): return f"{self.name}は剣で攻撃した" # Jobを継承した魔法使いクラス class Mage(Job): def attack(self): return f"{self.name}は杖で攻撃した" def magic(self, spell): return f"{self.name}は{spell}の魔法を使った" # Jobを継承したヒーラークラス class Healer(Job): def attack(self): return f"{self.name}は杖で攻撃した" def heal(self, target): return f"{self.name}は{target}を回復した" # メイン処理実行 if __name__ == "__main__": # 各インスタンスを生成 knight = Knight("nao") mage = Mage("Uta") healer = Healer("re") # 行動の出力 print(knight.attack()) # ナイトの攻撃 print(knight.defense()) # ナイトの防御 print(mage.magic("ファイア")) # 魔法使いの魔法攻撃 print(healer.heal(knight.name)) # ヒーラーの回復 |
このように、基底クラスに各職種で共通の行動や全職で使う内容が異なるものを定義し、継承先クラスには固有の行動を定義するということが簡単に出来てしまいます。
これにより、nameやhpなどのプロパティやdefenseやuse_itemなどの完全に同じメソッドを何度も書く必要がないため、保守性の高いコードが書けることがわかると思います。
本記事では、これに近いことが出来るかをちょっとだけ検証してみた結果を共有します。(もっとうまくやる方法はありそう)
②共通の実装
まず、キャラクターステータス用のCharacterStatusクラスを作ります。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Option Explicit Private name_ As String Private hp_ As Long Public Property Get name() As String name = name_ End Property Public Property Let name(ByVal val As String) name_ = val End Property Public Property Get hp() As Long hp = hp_ End Property Public Property Let hp(ByVal val As Long) hp_ = val End Property |
正直、このアクセサを大量に書くのは面倒ですが、ここは我慢しましょう。
次に親クラスとなるParentClassクラスを作成します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Option Explicit Private status_ As New CharaterStatus Public Property Get status() As CharaterStatus Set status = status_ End Property Public Sub Attack(ByVal weapon As String) Debug.Print status_.name & "は" & weapon & "で攻撃した" End Sub Public Sub Defense() Debug.Print status_.name & "は身を守っている" End Sub |
ポイントは、各職クラスで親クラスのインスタンスを持たせる点です。そのために、このように親クラスにステータスを持たせるときれいなりました。(他に最適な方法はあるかもしれません)
③擬似的なクラスの継承
各職クラス(子クラス)を書いていきます。(KnightClass・MageClass・HealerClass)
まず、KnightClassからです。このクラスは独自メソッドを持たず親と同じ動きをしています。
Attackでポリモーフィズムのようなものを実現しています。
・KnightClass
|
1 2 3 4 5 6 7 8 9 10 11 |
Option Explicit Private parent_ As New ParentClass Public Property Get parent() As ParentClass Set parent = parent_ End Property Public Sub Attack() parent_.Attack "剣" End Sub |
・MageClass 独自メソッドMagicを実装します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Option Explicit Private parent_ As New ParentClass Public Property Get parent() As ParentClass Set parent = parent_ End Property Public Sub Attack() parent_.Attack "杖" End Sub Public Sub Magic(ByVal spell As String) Debug.Print parent_.status.name & "は" & spell & "の魔法を使った" End Sub |
・HealerClass 独自メソッドHealを実装します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Option Explicit Private parent_ As New ParentClass Public Property Get parent() As ParentClass Set parent = parent_ End Property Public Sub Attack() parent_.Attack "杖" End Sub Public Sub Heal(ByVal target As String) Debug.Print parent_.status.name & "は" & target & "を回復した" End Sub |
最後に呼び出し処理を標準モジュールに作成します。
それぞれステータスを設定した後に行動させます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
Option Explicit Sub test() Dim player1 As New KnightClass Dim player2 As New MageClass Dim player3 As New HealerClass ' ナイト With player1 Call setCharacterStatus(.parent.status, "nao", 100) .Attack .parent.Defense End With ' 魔法使い With player2 Call setCharacterStatus(.parent.status, "Uta", 50) .Attack .Magic "ファイア" End With ' ヒーラー With player3 Call setCharacterStatus(.parent.status, "re", 60) .Attack .Heal player1.parent.status.name End With End Sub Private Sub setCharacterStatus(status As CharaterStatus, _ ByVal name As String, ByVal hp As Long) With status .name = name .hp = hp Debug.Print "name:" & .name & ", hp:" & .hp End With End Sub |
実行結果は次のようになります。

Pythonの例と比べるとかなり規模が大きくなりますが、概ね似たようなことが出来たかと思います。
全職で共通の要素をParentClassに作り、各子クラスでは、親インスタンスを介してそれらを使用することができます。(擬似継承)
また、今回のAttackのように、子クラス毎に動きを変えたい場合にも対応可能です。(ポリモーフィズム)
一見、この方法で何も問題ないかのようにも見えますが、実はいくつか欠点があります。
1点目は、上記の子クラスであるKnightClassなどを別のプロシージャに渡したい場合など、やはりKnightでもMageでも関係なく渡せるようにしたいですね?
その場合は引数としてObject型を使って渡すことになりますが、これをすると受け取った側で型が不明な状態になるので、まず自動メンバー表示が使えません。オブジェクトであれば何でも渡せてしまうので、型チェックが効かず、何のエラーが出ているのかわかなくなります。
2点目は、実装を見ていただくとわかるようにPublicなので隠蔽が出来ておらず、呼び出し側から全てアクセス出来てしまうのは、あまりよろしくありません。
このような実装は規模が大きくなるとバグの温床となる可能性があるので注意が必要です。
とはいえ、実際のところ自分しか触らないから問題なかったり、柔軟に書けるから扱いやすかったりと、メリットもあるので一概にダメとは言えません。実際に使うかはケースバイケースということになります。
ただ、チームでの開発や顧客へ納品する場合などは、このような実装方法は極力避けた方が無難ですね。
④おわりに
今回の内容はここまでとなります。
書き方を工夫することで、クラスの継承のようなものを実現できることがわかりました。しかし、いくつか課題は残るため、解決策が求められるという内容でした。
次回はImplementsを使って、インターフェースクラスを作ることで、今回の課題を解決できるか見ていきます。
以上、最後まで読んでいただき、ありがとうございました。
