ウェブエンジニア珍道中

日々の技術的に関する経験を書いていきます。脱線もしますが助けになれば幸いです。

typescriptでデザインパターンを書く -Factory Method-

久しぶりのデザパタです、今回はFactory Methodです。

Factory Methodとは

Template Methodパターンをインスタンス生成に適応したものです。

Factory(工場)はインスタンスを作る役割を果たし、作り方の枠組みを示す親クラス(抽象クラス)と実際に特定のクラスを定めて作る子クラスに分けて作成します。

「工場」という抽象クラスを継承してお菓子を作る「お菓子工場とか」を作ってやる感じです。

サンプル

IDカードを作るIDカード工場を作成します。

IDカードは使用するとその旨を伝える文字列をコンソールに表示します。

登場人物

  • Factoryクラス
    • 実際にIDカードを作る工場を作る元になる抽象クラス
  • Productクラス
    • Factoryに作られるクラスの元になる抽象クラス
  • IdCardFactoryクラス
    • Factoryクラスを継承して実際にIDカードを作るクラス
  • IdCard
    • IDカードを示すクラス

ソース

Factory

生成するために呼ぶメソッドcreate()を実装し、createメソッドが呼ぶ実際に作るメソッドcreateProduct()を抽象メソッドとして定義します。

import { Product } from './product';

export abstract class Factory {
    create(owner: string): Product {
        var p: Product = this.createProduct(owner);
        this.registerProduct(p);
        return p;
    }
    protected abstract createProduct(owner: string): Product;
    protected abstract registerProduct(product: Product): void;
}

Product

Factoryによって作られる「製品」を表したクラスです。実際に製品を使う際に呼ばれるuse()という抽象メソッドを定義しています。

export abstract class Product {
    public abstract use(): void;
}

IdCardFactory

実際にIdCardクラスを作る工場です。持ち主をの名前を受け取ってIdカードを作って返しています。

import { IdCard } from './idCard';
import { Factory } from './factory';
import { Product } from './product';

export class IdCardFactory extends Factory {
    public readonly owners: Array<string> = [];

    protected createProduct(owner: string): Product {
        return new IdCard(owner);
    }

    protected registerProduct(product: Product): void {
        this.owners.push((<IdCard>product).owner);
}

IdCard

import { Product } from './product';

export class IdCard extends Product {
    public readonly owner: string;

    constructor(owner: string){
        super();
        console.log(owner + "さんのカードを作ります。");
        this.owner = owner;
    }

    public use(): void {
        console.log(this.owner + "さんのカードを使います。");
    }
}

Main

Factoryを作成してIDカードを作らせた後、それらのカードを使用しています。

その際IdCardクラスの作り方は知らなくても良くてFactoryに任せておけば良いのが分かります。

作られるのがProductクラスを継承したもので、作るのがFactoryを継承したものなので、サブクラスの中身を知らなくても使える状態になってます。

import { IdCardFactory } from './idCardFactory';
import { Factory } from "./factory";
import { Product } from './product';

var factory: Factory = new IdCardFactory();

var card1: Product = factory.create("やまだ");
var card2: Product = factory.create("すずき");
var card3: Product = factory.create("さとう");

card1.use();
card2.use();
card3.use();

実行結果

$ ts-node "factory_method/main.ts"
やまださんのカードを作ります。
すずきさんのカードを作ります。
さとうさんのカードを作ります。
やまださんのカードを使います。
すずきさんのカードを使います。
さとうさんのカードを使います。

Template Methodパターンもそうですが、抽象クラスを上手く使うことでサブクラスの入れ替えを容易にして保守性を高めるというところがポイントだと思いました。抽象クラス大活躍です。