0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TypeScriptで学べるデザインパターンAdvent Calendar 2024

Day 11

TypeScriptで学べるデザインパターン 〜Facadeパターン〜 (シンプルな窓口)

Last updated at Posted at 2024-12-23

Facadeパターンとは?

一言で言うと、

複雑な仕組みをシンプルな窓口(インターフェース)で扱えるようにするデザインパターンです。

詳しく言うと、

Facade(ファサード)とは、フランス語で「正面」「窓口」という意味の単語です。
たくさんの部品がある複雑なシステムを、簡単な操作で動かせるようにする仕組みです。難しいことは裏でやってくれて、表側は「これを押せば全部動くよ!」みたいな感じです。

日常での具体例

自動販売機
お金を入れてボタンを押すだけで飲み物が出てきますが、内部ではお金を数えたり、在庫を確認したりしています。

Facadeパターンのメリット

  1. 使いやすくなる
    ユーザーが複雑な仕組みを意識せずに使えるようになります。

    • 例: テレビのリモコンで「音量を上げる」ボタンを押すだけで音量が上がる。
  2. システムの変更に強い
    内部の部品を変更しても、窓口の仕組みを保てば影響が少ないです。

    • 例: テレビの内部パーツを最新モデルに変えても、リモコンの操作方法は同じ。

Facadeパターンのデメリット

  1. シンプルさの裏で複雑さが隠れる
    表面が簡単になる代わりに、裏の仕組みが複雑になることがあります。

    • 例: 自動販売機が壊れた場合、内部の仕組みが理解できないと直せない。
  2. 拡張性が落ちることがある
    窓口を作ることで、個別の部品を直接操作しにくくなる場合があります。

    • 例: テレビのリモコンでは細かい設定ができない場合がある。

Facadeパターンをコードで説明

今回、想定するケース

以下は、自動販売機を題材にした Facadeパターン の例です。

クラス図

サンプルコード

// お金の管理をするクラス
class MoneyHandler {
  private insertedAmount: number = 0;

  acceptMoney(amount: number) {
    this.insertedAmount += amount;
    console.log(${amount} を受け取りました。現在の合計: ¥${this.insertedAmount}`);
  }

  validateMoney(): boolean {
    if (this.insertedAmount >= 100) {
      console.log("十分なお金が投入されています。");
      return true;
    } else {
      console.log("お金が不足しています。");
      return false;
    }
  }

  resetMoney() {
    this.insertedAmount = 0;
  }
}

// 在庫を管理するクラス
class InventoryManager {
  private inventory: { [key: string]: number } = {
    "コーラ": 5,
    "お茶": 3,
    "": 2,
  };

  checkStock(productName: string): boolean {
    if (this.inventory[productName] && this.inventory[productName] > 0) {
      console.log(`${productName} の在庫があります。`);
      return true;
    } else {
      console.log(`${productName} は在庫切れです。`);
      return false;
    }
  }

  dispenseProduct(productName: string) {
    if (this.inventory[productName] > 0) {
      this.inventory[productName]--;
      console.log(`${productName} を出しました。残り在庫: ${this.inventory[productName]}`);
    }
  }
}

// ディスプレイ用のクラス
class Display {
  showMessage(message: string) {
    console.log(`ディスプレイ: ${message}`);
  }
}

// Facadeクラス
class VendingMachineFacade {
  private moneyHandler: MoneyHandler;
  private inventoryManager: InventoryManager;
  private display: Display;

  constructor() {
    this.moneyHandler = new MoneyHandler();
    this.inventoryManager = new InventoryManager();
    this.display = new Display();
  }

  insertMoney(amount: number) {
    this.moneyHandler.acceptMoney(amount);
  }

  selectProduct(productName: string) {
    this.display.showMessage(`${productName} を選びました。`);

    if (this.moneyHandler.validateMoney() && this.inventoryManager.checkStock(productName)) {
      this.inventoryManager.dispenseProduct(productName);
      this.moneyHandler.resetMoney();
      this.display.showMessage("ありがとうございました!");
    } else {
      this.display.showMessage("操作を完了できませんでした。");
    }
  }
}

// 使用例
const vendingMachine = new VendingMachineFacade();

// お金を入れて商品を購入する例
vendingMachine.insertMoney(50);
vendingMachine.insertMoney(50);
vendingMachine.selectProduct("コーラ");

vendingMachine.insertMoney(100);
vendingMachine.selectProduct("お茶");

vendingMachine.insertMoney(100);
vendingMachine.selectProduct("");

コードの解説

1. クラスごとの役割を解説

MoneyHandlerクラス

お金の管理を担当します。

  • insertedAmount:
    • 現在投入されているお金の合計金額を記録するプライベート変数。
  • acceptMoney(amount: number):
    • 投入された金額をinsertedAmountに加算し、現在の合計金額を表示。
  • validateMoney():
    • insertedAmountが100円以上かどうかをチェックし、十分ならtrue、不足していればfalseを返す。
  • resetMoney():
    • insertedAmountを0にリセット。

InventoryManagerクラス

在庫の管理を担当します。

  • inventory:
    • 商品名をキー、在庫数を値とするオブジェクト。初期在庫は「コーラ: 5本、お茶: 3本、水: 2本」。
  • checkStock(productName: string):
    • 指定された商品が在庫に存在し、かつ在庫数が1以上ならtrueを返す。在庫切れの場合はfalseを返す。
  • dispenseProduct(productName: string):
    • 指定された商品の在庫数を1減らし、残り在庫数を表示。

Displayクラス

ディスプレイ表示を担当します。

  • showMessage(message: string):
    • 指定されたメッセージをコンソールに表示。

VendingMachineFacadeクラス

Facade(統一インターフェース)としての役割を担い、内部の複数のクラスを一元管理します。

  • コンストラクタ:
    • MoneyHandlerInventoryManagerDisplayのインスタンスを生成。
  • insertMoney(amount: number):
    • MoneyHandlerのacceptMoneyメソッドを呼び出してお金を投入。
  • selectProduct(productName: string):
    • 商品を選択し、次の手順を実行:
      1. Displayで選択した商品名を表示。
      2. MoneyHandlervalidateMoneyでお金が十分か確認。
      3. InventoryManagercheckStockで在庫があるか確認。
      4. 両方がtrueなら:
        • 商品を出す。
        • お金をリセット。
        • "ありがとうございました!"と表示。
      5. どちらかがfalseなら:
        • "操作を完了できませんでした。"と表示。

2. 使用例

ケース1: コーラ購入

vendingMachine.insertMoney(50); // ¥50 を投入
vendingMachine.insertMoney(50); // ¥50 を追加投入(合計¥100)
vendingMachine.selectProduct("コーラ"); // コーラを選択
  1. 合計¥100が投入され、validateMoneytrue
  2. コーラの在庫が確認され、在庫があるためcheckStocktrue
  3. コーラが出され、insertedAmountはリセットされる。
  4. "ありがとうございました!"が表示。

ケース2: お茶購入

vendingMachine.insertMoney(100); // ¥100 を投入
vendingMachine.selectProduct("お茶"); // お茶を選択
  1. ¥100が投入され、validateMoneytrue
  2. お茶の在庫が確認され、在庫があるためcheckStocktrue
  3. お茶が出され、insertedAmountはリセットされる。
  4. "ありがとうございました!"が表示。

ケース3: 水購入

vendingMachine.insertMoney(100); // ¥100 を投入
vendingMachine.selectProduct(""); // 水を選択
  1. ¥100が投入され、validateMoneytrue
  2. 水の在庫が確認され、在庫があるためcheckStocktrue
  3. 水が出され、insertedAmountはリセットされる。
  4. "ありがとうございました!"が表示。

Facadeパターンが用いられるケース

  1. 複雑なシステムを簡単に使いたい場合

    • ケース: ソフトウェアの内部が複雑でも、ユーザーにはシンプルなインターフェースを提供したい。
    • 具体例: ゲームエンジンで、「キャラクターを動かす」という簡単なコマンドで内部の物理演算を隠す。
  2. 複数の部品を一括管理したい場合

    • ケース: 複数のコンポーネントをまとめて扱いたい。
    • 具体例: 家のスマートデバイスをアプリで一括操作する。

まとめ

Facadeパターンは、「複雑なものをシンプルに操作する」ための便利な仕組みです。ただし、内部の複雑さが隠れるので、仕組みを理解しておくことも大切です。

参考サイト

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?