Facadeパターン(デザインパターンシリーズ1)

2024/10/06に公開

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パターンのメリット

  1. サブシステムの複雑さを隠す
    Facadeを使うことで、クライアントはサブシステムの詳細を知らなくても、シンプルなインターフェースを通じて複雑な処理を簡単に利用できます。ホームシアターの例では、個別の機器を操作する代わりに、一つのWatchMovieメソッドを呼ぶだけで済みます。

  2. 使いやすさの向上
    Facadeパターンによって、システムの使いやすさが向上します。クライアントはサブシステムのAPIを個別に呼び出す必要がなく、Facadeを通じて簡単に機能を利用できます。

  3. 内部変更に対する柔軟性
    サブシステムの内部実装が変更されても、Facadeのインターフェースが変わらない限り、クライアントコードに影響を与えません。内部の機器やプロセスが更新されても、クライアント側の操作方法は同じです。

  4. 新しいメンバーが理解しやすい
    Facadeパターンは、複雑なシステムを隠すことで、開発チームの新しいメンバーがシステムを理解しやすくなります。新メンバーは、サブシステムのコードを全て読まなくても、Facadeを読めばそのサブシステムの機能概要を把握することができます。

staticなファザードとInterfaceを使ったファザード

Facadeパターンは、複数の機能を統一的なインターフェースで提供するのが目的ですが、その提供の仕方にはバリエーションがあります。その中で、StaticなFacadeとInterfaceを使うFacadeの説明をします。

  1. StaticなFacade
    StaticなFacadeは、インスタンスを生成せずに、サブシステムの機能にアクセスできるFacadeです。メソッドを静的に定義することで、簡潔に利用できます。

メリット:

  • 実装がシンプル
    デメリット:
  • 継承や依存注入などが使えない
  • テストが難しい(特にモックを使ったテストなど)

コード例

public static class StaticFacade
{
    public static void DoSomething()
    {
        FunctionA.TaskA();
        FunctionB.TaskB();
    }
}

// クライアント側の使用例
StaticFacade.DoSomething();
  1. 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