ホーム < ゲームつくろー! < デザインパターン習得編

Bridge
  〜インターフェイスと実装の分離入れ替え自由自在


@ 実装部分をとっかえひっかえ

 インターフェイスが同じで機能が違うオブジェクトを作るとき、真っ先に思いつくのは「継承」です。オブジェクト指向最大の特徴ですね。しかし、この継承というのは、クラスの結びつきを恐ろしいほど強めます。そりゃそうです。親クラスの機能を子クラスが全部引き継ぐのですから。

 ゲームには「乱数」が必要になることが多々あります。パズルゲームでは次の駒を決定する際に乱数が使用されますし、STGも乱数によって弾の吐き方が変わります。RPGでは敵の攻撃、こちらの逃げる確率、etc...、あらゆるところで使用されます。

 パズルゲームで次の駒を決定するクラスを作ったとします。

class BlockFactory
{
public:
   virtual Block* GetNextBlock(){
      int r = rand() % 7;
      switch(r)
      {
         case 0:  // 赤いブロック
         return new RedBlock;
         break;
         //* 以下続く・・・ *//
      }
   }
};

 太文字部分が乱数です。rand()はCの標準ライブラリですね。7種類のブロックを生成する典型的なファクトリクラスです。

 パズルゲームは時に人工的な乱数になると面白みが格段に高くなったりします。テトリスでボーナスとしてテトリス棒ばかり出るようにすると、いいことも悪いことも起こります。では、それを実装するにはどうしたら良いでしょうか?例えば次のような派生クラスを作るかもしれません。

class TetrisBarBlockFactory : public BlockFactory
{
public:
   virtual Block* GetNextBlock(){
      return new RedBlock;
   }
}

 これは別に悪いことではありません。ただ、この実装の本質は「乱数」が異なっていれば良いわけで、BlockFactoryを派生させるほど大それたものではありません。そこで、BlockFactoryクラスを少し変更します。

class BlockFactory
{
protected:
   Random* m_pRnd;

public:
   virtual Block* GetNextBlock(){
      int r = m_pRnd.Intrand() % 7;
      switch(r)
      {
         case 0:  // 赤いブロック
         return new RedBlock;
         break;
         //* 以下続く・・・ *//
      }
   }
};

 Randomというオブジェクトへのポインタ変数が新たに定義され、乱数を引く部分がそのオブジェクトに委譲されています。こうすることにより、Randomクラスを親とする子クラスをたくさん作って、そのオブジェクトをとっかえひっかえすることで、BlockFactory自体を変えることなく、いろいろなブロックの出し方を再現することが可能になります。このRandomクラスの振る舞いのように、あるクラスの一部の機能を別のクラスが委譲し、継承ではなく入れ替えによりその実装を変えることが可能であるパターンがBridgeパターンです。

 Bridgeパターンの関係図は次のようになります。


 こう見ると複雑そうですが、先ほどのブロックファクトリーに照らし合わせると「な〜んだ、いつもやってるじゃん」と思うかもしれません。



A 継承じゃなくて委譲なわけ

 上のような機能の変更について、「別に継承でもいいじゃない」と思うかもしれません。しかし、このBridgeパターンに見られる委譲(デリゲート:Delegate)と継承には大きな違いがあります。上の関係図のCounterBlockFactoryは、BlockFactoryの派生クラスで、ブロックの生成数をカウントするものです。もし、まともに継承をしてしまうと、テトリス棒ばかりでるカウント付きのファクトリーが欲しい場合、CounterTetrisBarBlockFactoryクラスのような派生が必要になります。カウンター付きで紫と黄緑のブロックが交互に出るとか、カウンター付きで赤青黄の順で出るとか、乱数部分を変えるためだけに、BlockFactoryクラスの派生クラスを作っていくのは、何ともダメな作業ですよね。

 Bridgeパターンのような委譲をすると、実装がコンポジションとして保持しているオブジェクトにあるので、取替えが容易です。何より、クライアント側(BlockFactory)を変更しなくても良いというのが大きな違いです。乱数の部分を気にすることなく、いくらでも派生が出来ます。派生クラスでも、委譲しているオブジェクトを取り替えれば、機能をどんどん切り替えることが出来るのです。つまり、何か機能を変更したい時は、継承よりもまず委譲を考えてみた方が柔軟性と拡張性を高めるためにも有用であると言えます。