💀

デザインパターン:Abstract Factoryパターン

2024/07/19に公開

これは何?

関連する一連のオブジェクトを一括で生成するためのインターフェイス。

(Flutterみたいなことするマン)

Flutter: Flutterのコード書いてたら、Android, iOS, Web, … 用のビルドが一気にできちゃう

開発時にはGUIFactoryで得られるButtonという抽象クラスの扱いのみを考えれば良い
→Buttonの実際の実装は知らなくて良い
ビルド時にGUIFactoryの設定(AndroidGUIFactoryとか)を行う

// Abstract Product
interface Button {}

// Abstract Factory
interface GUIFactory{
    createButton(): Button
}

// Concrete Product
class AndroidButton implements Button {}

// Concrete Factory
class AndroidGUIFactory implements GUIFactory {
    createButton(): Button {
        return new AndroidButton()
    }
}

何が嬉しいのか

問題

ダンジョン探索ゲームを作ろうとしているとする

プレイヤーが新しいフロアに進むごとに、そのフロアに応じた敵やアイテムがランダムに生成されたりする

interface Enemy {}
interface Item {}

class Slime implements Enemy {}
class BasicHealthPotion implements Item {}

class Dragon implements Enemy {}
class AdvancedHealthPotion implements Item {}

function populateFloorWithoutFactory(floorType: string) {
  let enemy: Enemy;
  let item: Item;

  if (floorType === 'easy') {
    enemy = new Slime();
    item = new BasicHealthPotion();
  } else if (floorType === 'hard') {
    enemy = new Dragon();
    item = new AdvancedHealthPotion();
  }

  // 以降、アイテムや敵をランダム配置したりする
}

要件が増えて複雑性が増したときに、面倒&フロア設定ミスしがち

解決

各フロアの共通概念を用意するDungeonFactoryを生成

このinterfaceに沿うように改修していくと、フロア追加や仕様変更もしやすい。

// Abstract Factory
interface DungeonFactory {
  createEnemy(): Enemy;
  createItem(): Item;
}

// Concrete Factories
class EasyFloorFactory implements DungeonFactory {
  createEnemy(): Enemy {
    return new Slime();
  }
  createItem(): Item {
    return new BasicHealthPotion();
  }
	}

class HardFloorFactory implements DungeonFactory {
  createEnemy(): Enemy {
    return new Dragon();
  }
  createItem(): Item {
    return new AdvancedHealthPotion();
  }
}

// Client Code
function populateFloorWithFactory(factory: DungeonFactory) {
  const enemy = factory.createEnemy();
  const item = factory.createItem();
  // 以降、アイテムや敵をランダム配置したりする
}

その他の推しポイント

  • 責任が分離されて嬉しい
    • 「フロアの生成」→「フロアの設定」+「フロアの生成ロジック」
    • クライアント側のコードはオブジェクト生成と疎結合に
  • テストしやすい
  • 新しい仕様を追加した時、既存コードを弄らなくて良い
  • Factoryから得られる製品同士は、互換があることが保証される

動く実装例

https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgKIggWwJ7IN4BQyyA9CcoAByge2qDmDIJbWg8gyB7DIJ0MgDqZHIDucANgNYAKAJQAuEAFdMAI2icyyAIy1AcHKAJBkDRDIAGGQIcMgH4ZAHQyc4YRELGSZ0ZAQC+BUJFiIUASUiZ89ggl5wAzv7IAMq8wJgo4QAOvFgQ4EHoWLiExDwCIqnEyFAQYBJQIEqc9sQmZpmcxLn5hUoADCV2Pn6ByABCAcAIABIQfGAAFgAKAPZgwKNF0bERCcjuWF4ELQFBACJQcADmU8gzcfNJOPic6eZZ1XkFRQDMTWWmCBdVOdd1AKyNxPb2vmvIACCABMAG5wEBIYF9AYjcaTaaYGKHMBBRaePDeBSA6T+MBbBBgZAAMUQYFGUGwDnA0HgSGQ6wkIG2ECmpMJFJSnAQuRMEGO2BEojQGBwAG5ubzIOihQsPBKseQAMJTHl5FDs8lQYAQfyrNqoALYYm8UYUzWc-ZI2bxVEMpkstlky1ZNV8gWygWnbJvWpFDBcEJhCIiCU-SX9aUeWXo73ZGo3ZABjpdXr9XhDMYTKahpref5tHpwKDAk1mqAWylW5FzO2M5mskCVrnEN2QD1iEXJONXP1JiCBzY7HPCMPIUrINsQGWd2OXX2J5Mg8GQiDQ9OZ+Ejse-FYKJVhW3IFXAiAEGBMwkI5BRUZRCR+SBlikAdWAQ2bgjpWuwwvrjqbZ1KWEOMECmPFkHibsAF5kG-TkADopw7McwJACD3yWWD4MpJCpWnaNR3kchAFO5QBZNMAQAZACKGQAShkAMYZAAOGQAQhkAV1NACSGQBLhkAZ4YtAYwBZRMAO39AHUGQB9BkAKIZAE0GDQVlbcDRliBDTW2QQoJwAAafYPGEZoCAUQAqRSEwARBkAVYZAFuGKifHAol+n8Y1TXNIDcFg5NDVs58K0ckRLPQ+SIEU0ZlIAAwAWlCgzDNC4LAu02970fCB3LfD9PJsuzy2bbS0P8Xz-KCyLIuivdyEAZuUjLMiysqJQZi1LeyPI5KtnIHZAixLdzP0yuSFKUwQQtC0qItCwrYofPlEvfQZP2qtq6oy7zsu6gLevyobhCAA

参考文献

Factory Methodとの違い

Factory Methodは一つ一つのオブジェクト生成方法を定義する

Abstract Factoryは関連する一連のオブジェクト群を作成するためのインターフェースを定義

→レイヤーが異なる、両立する概念

// 例えばこんな感じに(めちゃくちゃ適当だから自信ない)

abstract class DragonFactory {
  abstract create(level: Level): Dragon;
}

class Dragon implements Enemy {}
class FireDragon extends Dragon {}
class FireDragonFactory extends DragonFactory {
  create(level: Level): FireDragon {
    return new FireDragon();
  }
}

class HardFloorFactory implements DungeonFactory {
  private fireDragonFactory: FireDragonFactory;

  constructor() {
    this.fireDragonFactory = new FireDragonFactory();
  }

  createEnemy(): Enemy {
    return this.fireDragonFactory.create(new Level());
  }

  createItem(): Item {
    return new AdvancedHealthPotion();
  }
}

より複雑なオブジェクトを作りたい時

引数のバリエーションがタイプによって増えるとか

そういう時は、(多分)次回のBuilderを使ったりする

Abstract Factoryはオブジェクトの集団作成に特化しているためすぐにプロダクトを返すが、Builderはプロダクト取得前にいろんな操作が必要

その他

  • interface?abstract class?
    • 初期値を設定したいとかだったら、abstract classとかにした方が良い
    • 1対多の時はinterfaceで良い
Tokyo, inc. Engineers

Discussion