【オブジェクト指向のこころ】11章 Abstract Factoryパターン
「デザインパターンとともに学ぶオブジェクト指向のこころ」の学習記録。
「第11章 Abstract Factoryパターン」のまとめ。
Abstract Factoryパターンの目的
GoFによるAbstract Factoryパターンの目的は次の通り。
- 関連、または依存しあうオブジェクトのファミリを、その具象クラスを指定することなしに生成するインタフェースを提供する
つまり状況に応じてオブジェクト群を使い分けるために使用する。
※ファミリ構成員: ある状況や実行機能に現れる互いに関連のある要素のこと
適用例
PCの性能によって性能の異なるディスプレイドライバ、プリンタドライバを選択する例。
- LRDD(Low Resolution Display Driver)
- HRDD(High Resolution Display Driver)
- LRPD(Low Resolution Printer Driver)
- HRDD(High Resolution Printer Driver)
初期の案はswitchでドライバを選択するというもの。
コード例:
class APControl { : public void doDraw() { switch (RESOLUTION) { case LOW: // LRDDを使用 case HIGH: //HRDDを使用 } public void doPrint() { switch (RESOLUTION) { case LOW: // LRPDを使用 case HIGH: //HRPDを使用 } }
このコードの問題点は、ドライバを決定する規則と、実際にドライバを使用するコードが一体化している。
結合度が高く、凝縮度が低いと言える。
例えば新たにMIDDLEが追加された場合、ドライバの決定だけでなく、ドライバを使用するコードまで修正が必要になる。
switchは抽象化の必要を示す赤信号である。
ポリモーフィズムの導入、責務の見直しを行うべき、つまり抽象化を行ったり、他のオブジェクトに責務を委譲することを考える。
LRDDとHRDDはどちらもDisplayDriver、LRPDとHRPDはどちらもPrinterDriverである。
次のように定義することでAPControlはswitchを使わずDisplayDriver、PrinterDriverを使用でき、低解像度か高解像度かは知らなくて良くなる。
Abstract Factoryパターンを適用する
次に考えるのが、適切なオブジェクトを誰が生成するか、である。
APControlが行うこともできるが、新たなオブジェクト、Middle Resolution Display Driverなどが必要になったとき、毎回APControlの修正が必要になる。
そこで必要なオブジェクトを実体化する工場(factory)オブジェクトを用意する。
これにより新たにオブジェクトのファミリが必要になった場合も修正を局所化することができる。
APControlはファクトリオブジェクトからドライバを取得する。
使用ドライバの決定という責務はResFactoryに一任されている。
ResFactoryは適切なドライバの生成に注力し、ApControlはそれらの使用に注力することで凝縮度が高まっている。
Adapterパターンを組み合わせる
ここまでは、LRDDとHRDDが同じ抽象クラスから派生していると想定していた。
仮に派生していないとしても、Adapterパターンを知っていれば問題にはならない。
ファクトリクラスで生成するクラスを、ドライバそのものではなく、それを適合させる(ラップする)ためのクラスに置き換える。
この例では、低解像度と高解像度という2つの状況でファクトリを分けた。
しかし実世界ではもっと多くの状況を扱わなければならないことがあり、状況が増えるたび、つまりファミリが増えるたびにファクトリが増えることは好ましくない。
そのような場合は1つのファクトリで全てのファミリを生成した方が良い。
そうすると、どのオブジェクトを生成するかという決定にはswitchが必要になる。ここでの関心事は、必要なオブジェクトを取得するためにはどうしたらよいか、switchの変数は何にすべきか、ということになる。
冒頭の悩みのように、こういった関心事は最初から存在している。
Abstract Factoryは次のことを教えてくれている。
- オブジェクトの生成責任を負っているオブジェクトにその選択肢を格納すること
- オブジェクトの使用とオブジェクトの生成は分けておくこと
switchに依存してしまうことには変わりないのでは?という疑問が生じるが、switchが諸悪の根源ではない。
様々な問題は、ある変数が複数のswitchの変数として用いられるなど、switch同士が結合したときに生まれる。
つまりswitchの結合により複雑さとバグをもたらす依存性が生まれる。
適用した例では、インスタンスを生成するファクトリは完全に独立している。
どのような方法でファクトリを実装したとしても、他の部分に影響は及ばない。
補足
Abstract Factoryと呼ばれる所以は、ファクトリが抽象クラスとなっているためではなく、ファクトリが生成する対象が抽象化されている(先の例ではDisplayDriverとPriterDriver)ためである。
まとめ
Abstract Factoryは、オブジェクトのファミリを特定の状況に従って実体化する必要がある際に使用できる。
これによりクライアントオブジェクトが使用するオブジェクト群の実体化方法に関する規則を、その使用から切り離す方法を与えてくれる。