はじめに
様々な言語で「デザインパターン」の本が世の中にありますが、筆者個人の経験では
いまいちピンとこない例
いまいちピンとこないコード
で説明されてることが多く、
結局これっていつ使うの?
という疑問に答えるには仕事仲間等との議論をしないと
辿り着けないことが多々ありました。
そこで特に「ゲーム開発ではどう使うか?」にフォーカスを当てて、実践的な例を交えて
デザインパターンの説明の需要があると思い記事を作りました。
デザインパターンを学ぶ理由
デザインパターンを学ぶ理由としては
- 車輪の再発明の防止
- 長文で読みにくいコード(可読性の低いコード)を減らす
- コードを疎結合にして変更に強くなる(変更時のコスト・変更箇所を減らす)
- モジュールとして使いまわせるように、コードの再利用性を高める
といった効果を期待できます。
対象読者
Unity 全くの初心者(インストールしただけで触ったことがないような方)はお断りです。
最低限以下のことは理解・経験を積んでおくことが必須になります。
- MonoBehaviour 継承クラスでコードを書いたことがある
- C# のピュアクラスを用いた自作クラスを作ったことがある
- クラスの継承という概念は知っている
そのため、脱・初心者
中級者へのステップアップ
として デザインパターンを学ぶ
のが良いと思います。
デザパタ記事リンク
生成系
構造系
様態・ふるまい系
- Chain of Responsibility パターン
- Command パターン
- Interpreter パターン
- Iterator パターン
- Mediator パターン
- Memento パターン
- Observer パターン
- State パターン
- Strategy パターン
- TemplateMethod パターン
- Visitor パターン
Bridge パターンについて
Bridge パターンを端的に説明すると 機能
と 実装
を分離
しよう! というデザインパターンです。
Bridgeパターンを利用しないと困る例
端的に言うと、1つの親クラスから派生するクラスが複数できるような時、派生先で実装したものを別の派生クラスで再利用したい
場合があります。
例えば以下のような例です
武器派生として 剣(Sword)
と 杖(Wand)
があったとします。
剣は 攻撃(Attack)
が可能で、杖は 魔法の詠唱(Cast()
ができます。
魔法の杖なので当然 Fire/Water/lightening (火・水・雷)の属性魔法を撃つことができる杖があります。
ここで、杖のように属性攻撃が可能な 魔法剣
を作ろうとしましょう。
シンプルに考えると、各属性に合わせた魔法剣クラスを実装すると思います。
現在は3種なので良いのですが、これでは魔法の種類が増えるたびにどんどん派生クラスが増えていきます。
また、属性攻撃の計算方法は基本的には同じなため、剣と杖で同じ処理を書くことになります。
これではどんどんコピペコードが増えてメンテナンスするにも対応箇所が多く、無駄に工数が増えます。
これをどうにかしたいのがBridgeパターンです。
Bridge パターンの使い所
Bridgeパターンは複数選択肢があるような場合で、柔軟に選択したい時に効果を発揮します。
上記で言えば全ての武器は 武器種 x 属性数のパターンだけあります。
Bridgeパターンを利用して実装する
さて、どうすればコピペコードを減らせるのでしょうか?
ポイントは 属性攻撃処理を独立化させる
ところです。
武器に依存してしまうとどうしても組合せ爆発が起こります。
そこで属性を管理する基底クラス (or インターフェース) を用意し、属性関連の処理は各属性クラスで行うようにします。
そして、武器クラスでは属性管理用の基底クラスをメンバーに持つことで、定義時にクラスを差し替えるだけで、属性を変更することが可能になります。
具体的に見ていきましょう。
Weaponの基底クラスに BaseElement クラスのメンバ変数を持たせるようにしました。
このBaseElementクラスでは エフェクトの適応
, 属性攻撃のダメージ計算
のメソッドを持っています。
BaseElementクラスを派生したクラスで属性処理を行うことができ、武器側は共通のメソッドを呼び出すだけで、簡単に属性攻撃を実装することが可能です。
このようにFire/Water/Lightening の属性を実装しておくことで、武器側は定義時に属性クラスをセットするだけで利用可能になるため、属性の処理をコピペする必要がなくなります。
public class FireSword{
public FireSword(){
element = new FireElement()
}
}
public class FireWand{
public FireWand(){
element = new FireElement()
}
}
今回追加した BaseElement
クラスが Bridge クラス
そのもので、武器と各種属性の橋渡しな存在になることからこのように呼ばれています。
Adapter パターンとの違い
Adapter パターンも外部SDKを自前システムで利用可能するために 橋渡し
をしているのではないか?という考え方もあります。
結論から言ってしまうと 表現的にはそのように解釈可能
だが、 デザインパターンは異なる
形になります。
Adapterパターンは 別のクラスをラップ
した上で、インターフェースを実装して利用可能にします。
つまり、元々のクラス自体はいじりません。
一方Bridgeクラスは Bridgeクラスのメンバ変数を持たせる
ため、大元のクラスの設計を変更する必要があります。
よって、アプローチの仕方が異なるため、両者の違いはしっかり区別できるようになっておくと良いでしょう。
まとめ
Bridgeパターンは組合せ爆発になりそうな時などで威力を発揮するデザインパターンです。
また、機能
と 実装
の分離は意識的に行うことで 仕様変更による手戻り防止
にもつながるため、基本的には良いことが多いです。(このあたりはSOLID原則やCleanArchitectureで言うところの 詳細は後で
がまさに 実装は後で
に該当します)
慣れている人は無意識的に処理の分割という観点からBridgeパターンを利用するので、1クラスに処理をまとめて書いてしまうことが多い方は是非機能の分割にチャレンジしてみましょう。