うどんゲームメモ

ゲームの技術メモをまったりと

Unity開発における継承とコンポジション

開発を進めていくうえでクラスの関係性をどのように設計するかを画策することは多々あると思います。
最近ではUnity2018でECSといったUnityでの開発アーキテクチャも話題になり、Unityでそれらをどう管理するのが正解か?という議論もちょこちょこ見られるようになりました。(自分だけかも)
そんな今回はUnityの開発思想に則して、少し基礎的な部分になりますがクラス設計の小話をしたいと思います。ECSの理解に対して必須の見識だと感じているので参考になれば幸いです。(変な解釈の部分があればご指摘ください)

継承とコンポジション

まず、プログラミングにおいて基礎的な要項ですが、継承とコンポジションの相違性について洗い出します。
継承もコンポジションも複数クラスの共有化という点では同じです、簡単に言ってしまうと違いはその共有する際のクラス間の関係性です。
継承の関係をざっくり言うと「is-a」です。継承ではスーパークラスの性質をサブクラスが全て引き継ぎ、全て我が物として使用します。
コンポジションの関係をざっくり言うと「has-a」です。共有するクラスをメンバとして管理し、依存したい部分にアクションを起こします。
継承はスーパークラスそのものでありそのサブタイプとなりますが、コンポジションではあくまでも共有する機能を持った拡張クラスでしかありません。

Unityとコンポーネント指向

それでは継承とコンポジションの相違点を踏まえ、Unityで開発する際のクラス関係を考えましょう。

Unityはコンポーネント指向をベースとしており、ある実装の枠組みに対してコンポーネントという機能単位で実装を図っていくことがベターです。
例えばある3Dキャラクターの敵を実装するとして、描画を行うコンポーネント、座標を管理するコンポーネント、敵キャラとしてアクションを起こすコンポーネント...等と作用ごとに分割し実装していきます。
それに対してクラスを共有するような物が出てきたらどうでしょう。

上の例から、様々な敵クラスに派生していくことを考えます。当然、敵毎のユニークなアクションを行うものは別途で実装を行いますが、3Dキャラクターとしての共通処理、敵としての共通処理は共有化を図ることになります。
その時、正解なのは「is-a」ベースの継承クラスを用意することでしょうか、それとも「has-a」ベースの共有コンポーネントを用意することでしょうか。
上記でも述べた継承の特性をもう一度考えます、継承という関係性はサブクラスがスーパークラスに対して大きく依存をすることになります。スーパークラス内で敵クラスについて共通処理の改修が行われた際にその派生した敵キャラクターについては保証されません。設計者がそれについて担保した設計にしているのならば別ですが、リリース毎に改修作業が増える可能性は十分にあります。

コンポーネント指向において、完全な「is-a」関係を要するものはそう多くはないと考えています。共有化すべき処理をCommonなりなんなり共有コンポーネントとして与え、それについてアクセス側がアクションを起こすという形が無難であり、そういった「has-a」の関係性で構成されるべきです。
継承が必要な関係性の場合は良いのですが、コンポーネント指向である以上、実装において共有化を要する際には機能単位でのコンポジションベースで考えることを徹底したほうがいいですね。


Unity2018のECSはコンポーネント指向を理解し、コンポーネント単位での振る舞いを考えることで理解が深まるものだと感じています。
コンポジション等の設計指向の基礎を再確認し、ECSを学ぶことで最善な設計を追求していきたいです。
自分もまだこの部分の解釈については検討途中であるため、感想や意見、指摘などがあればお願いします。