ほとんどの開発者はデザインパターンについて聞いたことがあるでしょう。 GoF(Gang of Four) のパターンが最も広く知られており、開発者ごとにそれらを学ぶ方法があります。たとえば:
- 書籍を読む。
- Webサイトで学ぶ。
- 同僚から教わる。
- トレーニングコースを受講する。
どの方法を選ぶにしても、パターンを暗記し、そのUML図を覚えるために何時間も費やすことはできます。しかし、それらを実際のプロジェクトへ適用するのは、より難しい場合があります。
最も重要なのは、正確なパターン名を暗記したり、ドキュメントに書かれているとおりに正確に実装したりすることではありません。より重要なのは、各パターンの背景にある動機を理解することです。パターンは、こうした動機から生まれるものです。
これらのパターンの背景にある動機をよりよく理解する良い方法は、実際のプロジェクトでそれらを研究することです。それがこの記事の目的です。ここでは、デザインパターンを多用しているオープンソースプロジェクトのソースコードを見ていきます。
Rigs of Rodsの分析
Rigs of Rods(「RoR」) は、ソフトボディ物理を使用して車両の動きと変形をシミュレートするオープンソースのマルチシミュレーションゲームです。このゲームはBeamと呼ばれる専用のソフトボディ物理エンジンを使って構築されており、相互接続されたノードのネットワーク(シャーシと車輪を形成)をシミュレートし、変形可能なオブジェクトを表現できます。このエンジンにより、車両とその積荷は応力が加わるとたわみ、変形します。壁や地形への衝突は、車両を永続的に変形させる可能性があります。

RoRで使用されているGoFデザインパターンのいくつかを見ていきましょう。
Singleton
Singletonは、最も一般的で広く使用されているデザインパターンの1つです。RoRでは、各Singletonクラスで同じコードを繰り返さないように、ジェネリックなSingletonを使用しています。新しいインスタンスを作成するSingletonと、作成済みのインスタンスを割り当てるSingletonという2つのバリアントが定義されています。

RoRのすべてのSingletonを検索してみましょう。そのために CQLinq:
from t in Types where t.DeriveFrom(“RoRSingletonNoCreation“) || t.DeriveFrom(“RoRSingleton“)
select t
動機:InputEngine Singletonを例に取ってみましょう。RoRは、キーボード、マウス、ジョイスティックに関するデータを保存する必要があり、これらは初期化時にInputEngineクラスによって検出されます。多くのクラスが同じ入力デバイスデータを必要とするため、複数のインスタンスを作成する必要はありません。したがって、主な動機は次のとおりです: 「InputEngineクラスのインスタンスを1つ作成する」。
ただし、Singletonの使用は議論の対象となっており、すべてのアーキテクトやデザイナーが推奨しているわけではありません。Singletonをめぐる議論に関する 記事 はこちらです。
Factory Method
ファクトリに神秘性はありません。その目的は単純で、インスタンスを作成することです。CreateInstanceメソッドを含む単純なファクトリでも、この目的を達成できます。しかしRoRでは、単純なファクトリではなく、すべてのファクトリにFactory Methodパターンを使用しています。
動機:このパターンをよりよく理解するために、RoRがそれを使用するシナリオを見てみましょう。
- RoRはグラフィックスエンジン OGREを使用しており、OGREはParticleEmitter系のクラスをインスタンス化する必要があります。
- RoRは、ParticleEmitterを継承した独自のParticleEmitterクラスBoxEmitterを定義しており、OGREにこの新しいクラスをParticleEmitterとして使用させる必要があります。
- OGREはRoRについて何も知りません。
ここで問題となるのは、OGREがRoRの新しいBoxEmitterクラスをどのようにインスタンス化して使用するのかという点です。ここで「Factory Method」パターンの出番です。
OGREには、CreateEmitterメソッドを提供するParticleEmitterFactoryという抽象クラスがあります。OGREがその役割を果たすには、具象ファクトリが必要です。RoRはParticleEmitterFactoryを継承した新しいファクトリBoxEmitterFactoryを定義し、CreateEmitterメソッドをオーバーライドします。
RoRは、ParticleSystemManager::addEmitterFactory(ParticleEmitterFactory *factory)を使用して、このファクトリをOGREへ提供します。OGREがParticleEmitterのインスタンスを必要とするたびに、BoxEmitterFactoryが呼び出されてそれが作成されます。
最も重要な動機は 低結合です。実際、OGREはRoRについて何も知らなくても、RoRのクラスをインスタンス化できます。
もう1つの動機は、インスタンス化を特定のファクトリクラスへ委譲することで 凝集性 を高めることです。
単純なファクトリを使用することは、インスタンス化ロジックを分離して凝集性を高めるうえで有用ですが、低結合も必要な場合には「Factory Method」パターンの方が適しています。
Template Method
Template Methodは、メソッド内でアルゴリズムの骨格を定義し、一部の手順をサブクラスへ委譲します。Template Methodにより、サブクラスはアルゴリズムの構造を変えずに、その一部の手順を再定義できます。
目的は、サブクラスが実装の一部を提供する一方で、アルゴリズムの構造が変わらないようにすることです。

Template Methodパターンを使用しているすべてのクラスを検出するために CQLinq を使いましょう。そのためには、サブクラスで実装されたメソッド(下のUML図のprimitive1とprimitive2)を使用する1つ以上のメソッド(図のtemplateMethod())を持つ抽象クラス(下のUML図のAbstractクラス)を検索します。
from t in Types where t.IsAbstract && t.Methods.Where(a=> a.NbLinesOfCode>0 && a.MethodsCalled.Where(b=>b.IsPureVirtual && b.ParentType==t).Count()>0).Count()>0 select t
動機:IRCWrapperクラスを例に取ってみましょう。そのprocessメソッドには、受信したIRCイベントを処理するロジックが含まれています。そこから呼び出されるメソッドは次のとおりです:

このメソッドは、IRCWrapperの派生クラスで実装される必要がある純粋仮想メソッドprocessIRCEventを呼び出します。LobbyGuiはそのようなクラスの1つで、受信したIRCイベントを処理する必要があるため、processIRCEventメソッドをオーバーライドして固有の振る舞いを実装します。
このパターンを使用すると、アルゴリズムの骨格を変えずに、その実装を簡単に変更できます。定型コードを削減し、これらのクラスの保守を容易にします。
また、クライアントが具象クラスではなく抽象クラスだけを参照できるため、 低結合も促進されます。
Strategy
クラスが振る舞いだけで異なる状況は数多くあります。このような場合、実行時に異なるアルゴリズムを選択できるよう、アルゴリズムを別々のクラスへ分離するのは良い考えです。
CQLinqを使って、Strategyパターンを使用しているすべてのクラスを検出しましょう。そのためには、複数の派生クラスを持ち、クライアントが具象実装ではなく抽象クラスを参照している抽象クラスを検索します。
from t in Types where t.IsAbstract && t.DirectDerivedTypes.Count()>1 !t.IsThirdParty
let tt=t.DirectDerivedTypes
from db in tt where db.Methods.Where(a=>a.NbMethodsCallingMe!=0 !a.IsStatic).Count()==0
select new {db,t}
動機:カメラは、固定、フリー、静的、等角など、複数の振る舞いを持つことができ、その振る舞いは動的に変更できます。将来、さらに振る舞いを追加することも可能です。
CameraManagerは抽象クラスIBehaviorを使用します。IBehaviorを使用しているCameraManagerのすべてのメソッドは次のとおりです。

ご覧のとおり、振る舞いを動的に変更するswitchBehaviorというメソッドがあります。
このパターンは、外部ライブラリとの 低結合 を促進します。実際、CameraManagerは具象的な振る舞いを知りません。また、 高凝集も促進します。特定の振る舞いはそれぞれ分離されたクラスに実装されているからです。
State
Stateパターンは、アーキテクチャの観点からはStrategyデザインパターンに似ています。このため、Strategyパターンを検索した前のCQLinqクエリでは、Stateクラスも見つかりました。
ただし、それらの目的は異なります。Strategyパターンは、1つ以上のIStrategy実装を使用するアルゴリズムを表します。これらの異なる振る舞いの間に相関はありません。一方、Stateパターンでは、最終目的を達成するためにある状態から別の状態へ遷移するため、異なる状態の間には関係があります。
抽象クラスAppStateを継承しているすべてのStateクラスは次のとおりです。

Strategyパターンと同様に、他のクラスは抽象クラスだけを参照します。AppStateを使用しているすべてのメソッドは次のとおりです。

ご覧のとおり、AppStateManagerには状態ライフサイクルを管理するための複数のメソッドが含まれています。
動機:
Strategyパターンと同様に、このパターンは 低結合 を促進します。AppStateManagerは具象的な状態を知りません。また、 高凝集 も促進します。各操作が対応する状態に分離されているからです。
Facade
Facadeは、クラスライブラリなど、より大きなコード本体への簡素化されたインターフェースを提供するオブジェクトです。使用されているFacadeを特定する簡単な方法は、プロジェクトから参照されている外部コードを検索することです。
RoRプロジェクトで使用されているすべての名前空間は次のとおりです:

Caelum名前空間を例に取り、それを使用しているRoRクラスを検索してみましょう。
from m in Methods where m.IsUsing (“Caelum“)
select new { m }
Caelum名前空間を直接使用しているのはSkyManagerだけです。したがって、これがCaelumのFacadeを表しています。
動機
コードと強く結合した外部ライブラリ、つまり多くのクラスが直接使用しているライブラリを使う場合、それを置き換えるのは非常に困難になります。しかし、Facadeを使用していれば、外部ライブラリを置き換えたい場合に変更が必要になるのはその実装だけです。
このパターンは、外部ライブラリとの 低結合 を促進します。
結論
GoFパターンを学んだ後は、コードでそれらを使用する動機を理解することが有用です。有名なオープンソースプロジェクトでパターンがどのように実装されているかを調べると、その価値をよりよく理解できます。
