Facade
一言でいうと
Facade (ファサード) は、 構造に関するデザインパターンの一つで、 ライブラリー、 フレームワーク、 その他のクラスの複雑な組み合わせに対し、 簡素化されたインターフェースを提供します。
![Facade デザインパターン](/images/patterns/content/facade/facade.png?id=1f4be17305b6316fbd548edf1937ac3b)
問題
高度なライブラリーやフレームワークに属する広範なオブジェクトを自分のコードに組み込む必要があるとします。 通常は、 これらすべてのオブジェクトを初期化し、 依存関係を追跡し、 正しい順序でメソッドを実行するなどを行う必要があります。
その結果、 自分のコード中のクラスのビジネス上のロジックが、 外部のクラスの実装の詳細と密に結合されることになり、 コードの理解と維持が困難になります。
解決策
ファサードは、 大変入り組んで複雑なサブシステムへの単純なインターフェースを提供するクラスです。 ファサードは、 直接サブシステムとやりとりするのに比べると限られた機能しか提供しないかもしれません。 しかし、 そこにはクライアントにとって本当に関心のある機能のみが含まれています。
とても多くの機能を備えた高級ライブラリーとアプリを統合する必要がありますが、 そのうちのほんのちょっとの機能しか必要ない場合、 ファサードが便利です。
たとえば、 猫の面白い短編ビデオをソーシャルメディアにアップロードするアプリは、 プロ用のビデオ変換ライブラリーを使用するかもしれません。 しかし、 本当に必要なのは、 ただ一つのメソッド encode(filename, format)
を持つクラスだけです。 このようなクラスを作成し、 ビデオ変換ライブラリーと繋げると、 最初のファサードができました。
現実世界でのたとえ
![電話注文を受ける例](/images/patterns/diagrams/facade/live-example-ja.png?id=6e22261746fea8da67e403a7fb5f0286)
電話で注文
店に電話して注文をする時、 電話オペレーターは、 すべてのサービスやデパートに対するファサードです。 オペレーターは、 注文システム、 支払いゲートウェイ、 配達サービスに対して簡単な音声インターフェースを提供します。
構造
![Facade デザインパターンの構造](/images/patterns/diagrams/facade/structure.png?id=258401362234ac77a2aaf1cde62339e7)
![Facade デザインパターンの構造](/images/patterns/diagrams/facade/structure-indexed.png?id=2da06d6b850701ea15cf72f9d2642fb8)
-
ファサード (Facade) は、 サブシステムの機能の特定の部分への便利なアクセスを提供します。 クライアントからのリクエストをどこにどう送り、 どういう細かい操作をすればいいのかを知っています。
-
追加のファサード (Additional Facade) クラスを作成すると、 関係ない機能によって単一のファサードが汚染され、 さらに複雑な構造のクラスがまた一つできてしまうのを防止することができます。 追加のファサードは、 クライアントからでも、 他のファサードからでも使用できます。
-
複雑なサブシステムは、 何十もの様々なオブジェクトで構成されています。 それらを使って何か意味のあることをするためには、 サブシステムの実装の詳細について熟知する必要があります。 たとえば、 オブジェクトを正しい順序で初期化し、 適切な形式のデータを提供する必要があります。
サブシステム内のクラスは、 ファサードの存在を認識していません。 クラスはシステム内で動作し、 お互いに直接連携して機能します。
-
クライアントは、 サブシステムのオブジェクトを直接呼び出す代わりにファサードを使用します。
擬似コード
この例では、 Facade パターンを使い、 複雑なビデオ変換フレームワークとのやりとりを簡素化します。
![Facade パターン例の構造](/images/patterns/diagrams/facade/example.png?id=2249d134e3ff83819dfc19032f02eced)
単一のファサード・クラス内で複数の依存関係を分離する例
フレームワークの何十ものクラスと直接やりとりするコードを書く代わりに、 ファサードのクラスを作り、 機能をカプセル化してコードの残りの部分から隠します。 こうしておくと、 フレームワークの将来のバージョンへのアップグレードや、 他のものと置き換える作業の労力を最小に抑えることができます。 アプリケーション内で変更が必要なのは、 ファサードのメソッドの実装だけだからです。
// 以下は、外部作成のビデオ変換フレームワークのクラス。自分たちのものでは
// ないので、単純化できない。
class VideoFile
// ……
class OggCompressionCodec
// ……
class MPEG4CompressionCodec
// ……
class CodecFactory
// ……
class BitrateReader
// ……
class AudioMixer
// ……
// フレームワークの複雑さを隠して、簡素なインターフェースとするためにファ
// サード・クラスを作成。機能性とシンプルさとの間の取引。
class VideoConverter is
method convert(filename, format):File is
file = new VideoFile(filename)
sourceCodec = (new CodecFactory).extract(file)
if (format == "mp4")
destinationCodec = new MPEG4CompressionCodec()
else
destinationCodec = new OggCompressionCodec()
buffer = BitrateReader.read(filename, sourceCodec)
result = BitrateReader.convert(buffer, destinationCodec)
result = (new AudioMixer()).fix(result)
return new File(result)
// アプリケーションのクラスは、複雑なフレームワークが提供する何億ものクラ
// スに依存しない。フレームワークを違うものに切り替える場合は、ファサード・
// クラスの書き換えだけで済む。
class Application is
method main() is
convertor = new VideoConverter()
mp4 = convertor.convert("funny-cats-video.ogg", "mp4")
mp4.save()
適応性
複雑なサブシステムへの限定はされているが簡潔なインターフェースが必要な場合に、 Facade パターンを使用します。
多くの場合、 サブシステムは時間の経過とともにより複雑になります。 いろいろなデザインパターンを適用しても、 クラス数が増える傾向があります。 サブシステムは、 様々な状況で柔軟で再利用しやすくなるかもしれません。 しかしクライアントが行わなければならない構成と定型コードの量は増加し続けます。 Facade は、 ほとんどのクライアントが最も使用するサブシステム機能への近道を提供することによって、 この問題を解決しようとします。
サブシステムを多層構造にするために、 ファサードを使用します。
サブシステムの各レベルへのエントリー・ポイントを定義するために、 ファサードを作成します。 ファサードを通してのみやりとりをすることで、 サブシステム間の結合度を減らすことができます。
ビデオ変換フレームワークの話に戻りましょう。 これは、 ビデオと音声の二つの層に分割することができます。 各層で一つのファサードを作成し、 各レイヤーのクラスをそれらのファサードを介してのみ相互に通信するようにします。 このやり方は、 Mediator パターンによく似ています。
実装方法
-
既存のサブシステムよりも簡単なインターフェースを提供することが可能かどうかを確認します。 このインターフェースにより、 クライアント・コードを多くのサブシステムのクラスから独立させられれば、 うまい方向に向いています。
-
このインターフェースを宣言し、 新ファサード・クラスとして定義します。 クライアントからの呼び出しは、 サブシステムの適切なオブジェクトに再送されます。 クライアント・コードがすでにそれを行ってなければ、 サブシステムの初期化やそれ以降のライフサイクル管理は、 ファサードの責任です。
-
このパターンを最大限に活用するためには、 すべてのクライアント・コードがファサード経由でサブシステムと通信するようにします。 これで、 クライアント・コードはサブシステムのコードの変更から保護されます。 たとえば、 サブシステムが新しいバージョンにアップグレードされた場合、 ファサードのコードを変更するだけですみます。
-
ファサードが過大になった場合は、 ファサードの振る舞いの一部を抽出して、 新規の精製ファサード・クラス作成を検討してみてください。
長所と短所
- サブシステムの複雑さからコードを分離可能。
- ファサードが、 アプリのすべてのクラスに結合された神オブジェクトになる可能性あり。
他のパターンとの関係
-
Facade が既存のオブジェクトに対して新しいインターフェースを定義するのに対し、 Adapter は既存のインターフェースを使えるようにしようとするものです。 Adapter は通常一つのオブジェクトだけを包み込みますが、 Facade は複数のオブジェクトからなるサブシステム全体を相手にします。
-
サブシステムがオブジェクトを作成する方法をクライアントから隠蔽することだけが目的なら、 Abstract Factory を Facade の代わりに使えます。
-
Flyweight は多くの小さなオブジェクトを作る方法についてですが、 Facade はサブシステム全体を表す単一のオブジェクトを作る方法に関してです。
-
Facade と Mediator は、 多くの密に結合されたクラス間の協力関係を組織するという似た目的があります。
- Facade は、 オブジェクトのサブシステムへの簡略化されたインターフェースを定義しますが、 新しい機能が導入されるわけではありません。 サブシステム自体はファサードを認識しません。 サブシステム内のオブジェクト同士は直接やりとりします。
- Mediator は、 システムのコンポーネント間の通信を一元化します。 コンポーネントはメディエーター・オブジェクトについてのみ知っており、 直接やりとりしません。
-
多くの場合、 ファサード・オブジェクトは一つだけあれば十分なので、 Facade はしばしば Singleton に変換可能です。
-
Facade は、 どちらも複雑な実体の緩衝材として機能し、 初期化も行うという意味で、 Proxy と似てます。 Facade と異なり、 Proxy はそのサービス・オブジェクトと同じインターフェースを持ち、 両者は交換可能です。