Facadeパターン
Facade(ファサード)パターンは、複数のAPIやクラスからなる複雑なサブシステムを、統一されたシンプルなインターフェースを通じて操作できるようにするデザインパターンです。これにより、クライアント(利用者)はサブシステムの詳細な実装に触れることなく、シンプルで高レベルなAPIを使って機能を利用できます。
以下では、Facadeパターンの具体的な例とともに、メリットについて説明していきます。
ファザードパターンのイメージ
Facadeパターンを理解しやすくするために、まずコード例を見てみましょう。
サブシステムの例
ここでは、ホームシアターシステムを例に、各機能が複雑に分かれているサブシステムを考えます。
// 機能1: DVDプレイヤー
public class DvdPlayer {
public void On() { Console.WriteLine("DVDプレイヤーを起動"); }
public void Play(string movie) { Console.WriteLine($"{movie}を再生中"); }
public void Off() { Console.WriteLine("DVDプレイヤーを停止"); }
}
// 機能2: プロジェクター
public class Projector {
public void On() { Console.WriteLine("プロジェクターを起動"); }
public void Off() { Console.WriteLine("プロジェクターを停止"); }
}
// 機能3: スクリーン
public class Screen {
public void Down() { Console.WriteLine("スクリーンを降ろす"); }
public void Up() { Console.WriteLine("スクリーンを上げる"); }
}
// 機能4: ライト
public class TheaterLights {
public void Dim(int level) { Console.WriteLine($"ライトの明るさを{level}%に設定"); }
}
このように、ホームシアターを操作するためには、DVDプレイヤー、プロジェクター、スクリーン、ライトといった機能を一つ一つ操作する必要があります。これでは使い勝手が悪く、複雑です。どのクラスのどのメソッドを、どの順番から呼べば良いのか、このサブシステムを使う側からは全くわかりません。
Facadeを導入する
Facadeパターンを使って、この複雑な操作を一つの簡単なインターフェースにまとめてみます。
public class HomeTheaterFacade {
private DvdPlayer dvdPlayer;
private Projector projector;
private Screen screen;
private TheaterLights lights;
public HomeTheaterFacade(DvdPlayer dvdPlayer, Projector projector,
Screen screen, TheaterLights lights) {
this.dvdPlayer = dvdPlayer;
this.projector = projector;
this.screen = screen;
this.lights = lights;
}
// 映画を開始する簡単なメソッド
public void WatchMovie(string movie) {
Console.WriteLine("映画を再生します...");
lights.Dim(10); // ライトを暗くする
screen.Down(); // スクリーンを降ろす
projector.On(); // プロジェクターを起動
dvdPlayer.On(); // DVDプレイヤーを起動
dvdPlayer.Play(movie); // 映画を再生
}
// 映画を終了する簡単なメソッド
public void EndMovie() {
Console.WriteLine("映画を終了します...");
dvdPlayer.Off(); // DVDプレイヤーを停止
projector.Off(); // プロジェクターを停止
screen.Up(); // スクリーンを上げる
lights.Dim(100); // ライトを明るくする
}
}
クライアントコード
Facadeを利用するクライアントコードは非常にシンプルになります。複雑なサブシステムの操作を、WatchMovieメソッドやEndMovieメソッドで一括して行えます。
public class Program {
public static void Main(string[] args) {
// サブシステムのインスタンスを作成
DvdPlayer dvdPlayer = new DvdPlayer();
Projector projector = new Projector();
Screen screen = new Screen();
TheaterLights lights = new TheaterLights();
// Facadeのインスタンスを作成
HomeTheaterFacade homeTheater = new HomeTheaterFacade(dvdPlayer,
projector, screen, lights);
// 映画を開始する
homeTheater.WatchMovie("Inception");
// 映画を終了する
homeTheater.EndMovie();
}
}
Facadeパターンの具体例: カフェでの注文
もう一つ日常的な例を挙げると、カフェでコーヒーを注文する場面がわかりやすいです。
- あなたがカフェで「カフェラテをください」と注文すると、バリスタは裏で複数のステップを経てコーヒーを作ります。
- コーヒー豆を挽く
- ミルクを温める
- コーヒーを抽出する
- ミルクとコーヒーを混ぜる
- カップに注ぐ
これらの複雑なプロセスを、あなたが一つ一つ指示するわけではなく、シンプルな「カフェラテください」というインターフェースで利用しているわけです。バリスタがFacadeの役割を果たしていると言えます。Facadeは、「建物の正面」的な意味なので、その単語の意味がなんとなく分かりますね。
Facadeパターンのメリット
-
サブシステムの複雑さを隠す
Facadeを使うことで、クライアントはサブシステムの詳細を知らなくても、シンプルなインターフェースを通じて複雑な処理を簡単に利用できます。ホームシアターの例では、個別の機器を操作する代わりに、一つのWatchMovieメソッドを呼ぶだけで済みます。 -
使いやすさの向上
Facadeパターンによって、システムの使いやすさが向上します。クライアントはサブシステムのAPIを個別に呼び出す必要がなく、Facadeを通じて簡単に機能を利用できます。 -
内部変更に対する柔軟性
サブシステムの内部実装が変更されても、Facadeのインターフェースが変わらない限り、クライアントコードに影響を与えません。内部の機器やプロセスが更新されても、クライアント側の操作方法は同じです。 -
新しいメンバーが理解しやすい
Facadeパターンは、複雑なシステムを隠すことで、開発チームの新しいメンバーがシステムを理解しやすくなります。新メンバーは、サブシステムのコードを全て読まなくても、Facadeを読めばそのサブシステムの機能概要を把握することができます。
staticなファザードとInterfaceを使ったファザード
Facadeパターンは、複数の機能を統一的なインターフェースで提供するのが目的ですが、その提供の仕方にはバリエーションがあります。その中で、StaticなFacadeとInterfaceを使うFacadeの説明をします。
- StaticなFacade
StaticなFacadeは、インスタンスを生成せずに、サブシステムの機能にアクセスできるFacadeです。メソッドを静的に定義することで、簡潔に利用できます。
メリット:
- 実装がシンプル
デメリット: - 継承や依存注入などが使えない
- テストが難しい(特にモックを使ったテストなど)
コード例
public static class StaticFacade
{
public static void DoSomething()
{
FunctionA.TaskA();
FunctionB.TaskB();
}
}
// クライアント側の使用例
StaticFacade.DoSomething();
- Interfaceを使ったFacade
もう一つは、インターフェース(Interface)を使うFacadeです。この方法では、Facadeクラスにインターフェースを定義し、クライアントがそのインターフェースを通じてサブシステムにアクセスできるようにします。これにより、Facadeの実装を後から差し替えることができたり、テストがしやすくなります。
メリット:
- インターフェースを使うことで柔軟性が高く、依存注入(DI)やモックを使ったテストが容易になる。
- 実装を切り替えられるので、拡張性が高い。
デメリット: - 実装がStaticなFacadeと比較して少し複雑になる
コード例
public interface IFacade
{
void DoSomething();
}
public class ConcreteFacade : IFacade
{
private readonly FunctionA _functionA;
private readonly FunctionB _functionB;
public ConcreteFacade(FunctionA functionA, FunctionB functionB)
{
_functionA = functionA;
_functionB = functionB;
}
public void DoSomething()
{
_functionA.TaskA();
_functionB.TaskB();
}
}
// クライアント側の使用例
IFacade facade = new ConcreteFacade(new FunctionA(), new FunctionB());
facade.DoSomething();
- StaticなFacadeはシンプルで使いやすく、特に小規模なシステムやユーティリティクラスに向いています。
- Interfaceを使ったFacadeは拡張性と柔軟性を備えており、テストがしやすく、依存注入に対応できます。複雑なシステムや、将来の変更に強い設計が求められる場合に適しています。
Discussion