時代に翻弄されるエンジニアのブログ

ゲームプログラマをやっています。仕事やゲームや趣味に関してつらつら書きたいと思います。

SOLID原則 ゲームで使える 依存関係逆転の原則

f:id:tkymx83:20190125004247j:plain:w1000

こんにちは、今日はSOLID原則の中でも依存関係逆転の法則について解説したいと思います。

みなさん、コードが依存関係でがちがちになって修正ができない(泣)
そんな経験はありませんか?

僕はあります。ぎちぎちのせいで1つ変更するだけでもいろいろな箇所を変更しなければいけないことがありました。そのせいでエンバグが起きてしまったら目も当てられません。

そんなときには、依存関係逆転の法則が便利です。

「依存関係逆転の法則」とは

依存関係逆転の原則とは

変化が多い要素から変化の少ない要素に依存関係を逆転させる原則です。

では具体的にどのようなときに役立つのかみていきたいと思います。

変化の多い依存とは?

あるアクションゲームを想像してください。アクションゲームの中には斬撃を飛ばすことができる剣士が存在したとします。その剣士が斬撃で攻撃するときのクラス構造を図にしてみました。

f:id:tkymx83:20190125004155p:plain:w1000

コード上では、Playerクラス内でZangekiAttackクラスのApplyDamageが呼ばれるイメージです。このとき、他の攻撃を追加したい場合どうするでしょうか?メソッドを追加したとしても、Playerに変更が入ります。また、Playerが実は剣士ではなく、魔法使いだった場合はどうでしょうか?今度はPlayerが増えます。Playerが増えた場合、そのPlayerを選択するクラスも存在して。。。とどんどん修正箇所が増えていきます。

これは、ひとえにPlayerクラスが ZangekiAttackクラスに依存しているためです。

では、どのように依存性を逆転するのでしょうか?

インタフェースを使った依存性の逆転

答えはすべてインターフェースを切ることです。

以下2つの問題に対して依存性の逆転を行った例を下に示します。

①他の攻撃が増えた場合にPlayerが変更される
②他のキャラクタが増えた場合に新しいクラスが必要になる。

f:id:tkymx83:20190125005816p:plain:w1000

①の解決方法

IAttackインターフェースを継承してZangekiAttackクラスを作成しました。PlayerはIAttackインターフェースを用いて攻撃するため、具体的な攻撃の種類を知る必要はありません。つまり、新しい攻撃を生成しても、Playerのロジックを変更することなく追加ができるのです。

②の解決方法

IAttackFactoryクラスを継承してKenshiFactoryクラスを作成しました。Playerは、IAttackFactoryインターフェースを用いて、攻撃方法を取得するため、自分が誰なのかを知る必要がありません。Playerを生成するときに、剣士ならばKenshiFactoryのように、職種にあったクラスを指定するだけで新しい攻撃パターンを作ることができます。

複数の攻撃を使い分けるために

IAttackFactoryでIAttackを継承したクラスを生成できるので、今のプレイヤーの選択状況によって生成する攻撃方法を変えることができます。

変化の大きなクラスと変化の小さなクラス

ここで大切なことは、依存関係が変化の大きなクラスから変化の小さなクラスに逆転しているということです。

ZangekiAttackクラスは攻撃の演出や攻撃力などが変わることで変化します。つまり、ゲームの仕様等によって頻繁に変更が入るということです。

対して、IAttackインターフェースは動作のみしか内容が書かれていないため、基本的に変化することがありません。

そして、PlayerはIAttackインターフェースに依存しています。つまり、インターフェースを使うことで、依存性の逆転ができたということです。

まとめ

このように、インターフェースを切ることで依存性の逆転を行うことができます。また依存性の逆転の副作用として、継承したクラスをモジュールとして切り離すことができます。つまりは、完璧に依存性の分離ができたということです。

個人的な体験談ですが、

依存性の分離をすることでメリットを受けるのは、頻繁に追加が入る機能です。例えば、ゲームのキャラクタの種類とか、状態異常の種類とか、攻撃方法の種類など、複数の種類が発生する要素です。

それ以外の場合はメリットをあまり享受することはできません。ですが、いざ運用が始まったときに、急な追加に対応できる柔軟なコードを作ることができます。運用的な意味でもインターフェースで区切るのは重要なメリットがあると思います。