こんにちは、たくという名前でブログをやっています
突然ですが、クラスには継承という仕組みがあります。クラスの継承元はスーパークラスと呼ばれ、記載したメソッドやメンバ変数が継承先でも使用できるようになります。継承を利用して「ポリモーフィズム」などの概念を実装するのが一般的です。
この継承についてなのですが、スーパークラスのメソッドが具象クラスでも使用できるという点を利用して、敵の挙動などの共通処理を書いてしまうことがあるのですが、最近あまり良くないなと思い共通処理を書くのを控えています。
今日はその経緯などを知見として残したいと思います。
スーパクラスに共通処理を記載するデメリット
マリオなどのアクションゲームをやったことはありますか?
主人公が敵を倒しながらゴールを目指すゲームです。
このゲームにおける敵の挙動を実装することを想定してください。敵という概念を持った複数種類の敵キャラクタが存在するので、継承を使いたくなります。そして、継承を使用して敵の共通処理をスーパークラスに書いてしまいたくなります。
歩いたり、攻撃したり、攻撃を受けたり、共通の処理はスーパークラスに記載して、トブだったり、棘を持っていたり、共通ではない処理は具象クラスに記載する形を想定してください。一見実装としてはスッキリしたコードになるような気がします。しかし、僕はこの継承の使い方を推奨しません。
理由は二点あります
- スーパークラスが肥大化していくから
- 固有の処理を記載したい場合の柔軟性に乏しいから
スーパクラスの肥大化とは?
肥大化とは、コード量が多くなり、コードの可読性が悪くなったり、依存部分が多くなることで修正が困難になる現象です。
はじめは、敵の行動もシンプルで、左右に動いたり、HPを持っていたり、あたったらダメージを受けるぐらいかもしれません。しかし、敵のバリエーションを増やすにあたって、飛ぶ機能をつけるために行動を汎用化したり、ハンマーを投げるために攻撃を汎用化したりと機能がどんどん増えていくと共通部分もどんどん増えていきます。
ただ増えていくだけなら良いのですが、機能が付け足されるということは当初想定していない状態になるということです。設計時点で想定していない物が増えていくとどうしてもコードが複雑になってしまい可読性が悪くなります。最終的には誰も読めないようなコードになります。
どうすればいいのか?
敵キャラごとに固有のクラスを作成する。共通機能も機能ごとに別クラスを作成する。敵キャラごとの固有クラスから機能クラスを呼び出すようにするのが良いと思っています。
つまりは、継承をしてスーパークラスに共通部分を書くのではなく、クラスに部品クラスを集約するイメージです。
そうすることで、機能ごとに責務が明確な部品クラスができるため、可読性が上がり、さらに再利用性も上がります。また、敵クラスに関しても部品を選択して使用するので、どのような機能があるのか一目瞭然です。そういう意味でも可読性が上がります。
つまりは、SOLID原則の 責務分離の法則
上記で紹介したように、肥大化したクラスは、クラスとしての責務が多すぎる状態です。この状態はSOLID原則的にも、良い状態ではあります。
今回機能を部品クラスごとにわけ、敵クラスに機能を集約したのは、責務分離の法則に則っています。
責務を分離することで、そのクラスで何をしたいかが明確になり、可読性や再利用性が向上します。
おわりに
継承はオブジェクト指向のなかでも強力な力を持っている機能ですが、使い方によっては苦労することが多いです
次は、もっと汎用的に継承のデメリットとメリットについてゲーム開発の支店も含めて解説したいと思います