💠

【デザインパターン】Factory

2023/03/06に公開約5,600字

Factory Methodパターンについて

Factory Methodパターンは、オブジェクトの生成を専門に行うオブジェクトを用意し、そのオブジェクトに実際のオブジェクト生成を依頼することで、柔軟なオブジェクト生成を実現するデザインパターン
このパターンを使うことで、直接オブジェクトを生成するよりも、より柔軟にオブジェクトを生成できるようになるメリットがあります。
またFactory Methodパターンを使用すると、オブジェクトの生成と利用を分離することができ、プログラムの保守性を高めるこができるというととです。

Factory Methodパターンの構成要素

Factory Methodパターンは、以下の要素で構成されとる
【生成工場】

  • Creator:オブジェクト生成を行うためのインターフェースを提供する抽象クラス
  • ConcreteCreator:Creatorクラスを継承し、具体的なオブジェクト生成を行うクラス

【製品】

  • Product:生成されるオブジェクトの抽象クラス
  • ConcreteProduct:Productクラスを継承し、具体的なオブジェクトを表すクラス、具体的な製品の機能を提供する
    基本的に継承を利用したパターンで、親クラスで枠組みを決定して子クラスで具体的な実装を行う形になる
    生成工場を新しく作ってあげたら良いから、新しいProductを作るのが簡単になるのがメリットで、オブジェクトの利用側とオブジェクトの結びつきを弱くすることができるのがメリットになります。

Factory Methodを採用することのメリット

Factoryを採用すると以下のようなメリットがあります

  • 類似した複数種類のオブジェクトを生成する必要がある場合
  • オブジェクトの生成ロジックが複雑な場合
    • 利用箇所との分離ができているので変更に強い

Factory Methodパターンの実装例

以下は、Factory Methodパターンを使用して、複雑なオブジェクトを生成する例です。例として、複数の種類の動物を生成するクラスを考えます。動物は、食べ物、体重、名前などの情報を持ち、それぞれ異なる種類があります。
Factory Methodパターンを使用することで、複数の種類の動物を生成することができます。
構成要素に当てはめると以下のようになります
【生成工場】

  • Creator:AnimalFactory
  • ConcreCreator:LionFactory、ElephantFactory、GiraffeFactory

【製品】

  • Product:Animal
  • ConcreteProduct:Lion、Elephant、Giraffe
// 製品の抽象クラス
abstract class Animal {
  protected name: string;
  protected food: string;
  protected weight: number;
  public abstract eat(): void;
  public abstract getName(): string;
  public abstract getFood(): string;
  public abstract getWeight(): number;
}

// 具体的な製品クラス
class Lion extends Animal {
  constructor(name: string, food: string, weight: number) {
    super();
    this.name = name;
    this.food = food;
    this.weight = weight;
  }
  public eat(): void {
    console.log(`${this.name} is eating ${this.food}`);
  }
  public getName(): string {
    return this.name;
  }
  public getFood(): string {
    return this.food;
  }
  public getWeight(): number {
    return this.weight;
  }
}

class Elephant extends Animal {
  constructor(name: string, food: string, weight: number) {
    super();
    this.name = name;
    this.food = food;
    this.weight = weight;
  }
  public eat(): void {
    console.log(`${this.name} is eating ${this.food}`);
  }
  public getName(): string {
    return this.name;
  }
  public getFood(): string {
    return this.food;
  }
  public getWeight(): number {
    return this.weight;
  }
}

class Giraffe extends Animal {
  constructor(name: string, food: string, weight: number) {
    super();
    this.name = name;
    this.food = food;
    this.weight = weight;
  }
  public eat(): void {
    console.log(`${this.name} is eating ${this.food}`);
  }
  public getName(): string {
    return this.name;
  }
  public getFood(): string {
    return this.food;
  }
  public getWeight(): number {
    return this.weight;
  }
}

// 生成工場の抽象クラス
abstract class AnimalFactory {
  public abstract createAnimal(name: string, food: string, weight: number): Animal;
}

// 具体的な生成工場クラス
class LionFactory extends AnimalFactory {
  public createAnimal(name: string, food: string, weight: number): Animal {
    return new Lion(name, food, weight);
  }
}

class ElephantFactory extends AnimalFactory {
  public createAnimal(name: string, food: string, weight: number): Animal {
    return new Elephant(name, food, weight);
  }
}

class GiraffeFactory extends AnimalFactory {
  public createAnimal(name: string, food: string, weight: number): Animal {
    return new Giraffe(name, food, weight);
  }
}

// 利用側
const lionFactory = new LionFactory();
const lion = lionFactory.createAnimal("Simba", "meat", 200);
console.log(lion.getName()); // 出力: Simba
console.log(lion.getFood()); // 出力: meat
console.log(lion.getWeight()); // 出力: 200
lion.eat(); // 出力: Simba is eating meat

「ん?Factoryから間接的にインスタンスを生成しているだけなんだったら、インスタンスを直接作成してあげたら良いのでは?」
と考える場合があると思います。
それでは以下のように、Lionクラスの生成方法を変更する場合を考えてみます。
今までLionクラスのインスタンスを生成する際には、name、food、weightを引数にしてLionクラスのコンストラクタを呼び出していました。
しかし、Lionクラスの生成方法が変更になり、name、food、weight以外の引数を必要とするようになった場合を考えてみます。
この場合、直接Lionクラスのインスタンスを生成している場合、変更箇所が複数あり、修正が面倒になります。しかし、Factory Methodパターンを使用している場合、LionFactoryクラスのcreateAnimalメソッドのみを変更することで、Lionクラスの生成方法を変更することができます。

// 製品の抽象クラス
abstract class Animal {
  protected name: string;
  protected food: string;
  protected weight: number;
  public abstract eat(): void;
  public abstract getName(): string;
  public abstract getFood(): string;
  public abstract getWeight(): number;
}

// 具体的な製品クラス
class Lion extends Animal {
  constructor(name: string, food: string, weight: number, sound: string) { // soundを追加した場合
    super();
    this.name = name;
    this.food = food;
    this.weight = weight;
    this.sound = sound;
  }
  public eat(): void {
    console.log(`${this.name} is eating ${this.food}`);
  }
  public getName(): string {
    return this.name;
  }
  public getFood(): string {
    return this.food;
  }
  public getWeight(): number {
    return this.weight;
  }
  public getSound(): string {
    return this.sound;
  }
}

// 生成工場の抽象クラス
abstract class AnimalFactory {
  public abstract createAnimal(name: string, food: string, weight: number): Animal;
}

// 具体的な生成工場クラス
class LionFactory extends AnimalFactory {
  public createAnimal(name: string, food: string, weight: number): Animal {
    // soundを追加した場合、引数にsoundを追加するだけでOK
    return new Lion(name, food, weight, "roar");
  }
}

// 利用側
const lionFactory = new LionFactory();
const lion = lionFactory.createAnimal("Simba", "meat", 200);
console.log(lion.getName()); // 出力: Simba
console.log(lion.getFood()); // 出力: meat
console.log(lion.getWeight()); // 出力: 200
console.log(lion.getSound()); // 出力: roar

このように、Lionクラスの生成方法が変更になった場合でも、Factory Methodパターンを使用することで、LionFactoryクラスのcreateAnimalメソッドのみを変更することで対応できます。

Discussion

ログインするとコメントできます