ウェブエンジニア珍道中

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

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

今回はprototypeパターンを書いてみました。

prototypeパターンとは

既に作っているインスタンスから別のインスタンスを作るパターンです。

メリット

扱うオブジェクトの種類が多すぎる時に管理しやすい

オブジェクトを30個作って扱う必要がある際にそれぞれ別のクラスにすると管理がとても大変なので、クローンして作れるようにします。

オブジェクト作成処理が重い場合

new Hoge()としてオブジェクトを作る処理がとても重く、クローンして作った方が速い時に使います。

新規に作る度にフィボナッチ数列の計算が走る(無いと思うけど)等する場合は既にできたオブジェクトをコピった方が処理は少ないですね。

サンプル

文字列を文字で囲んで表示したり、下線を付け足して表示したりするものです。

登場人物

  • Product
    • インターフェース
    • クローンするためのメソッドを定めている
  • MessageBox
    • 文字列を文字で囲んで表示する
    • Productを継承してクローンする機能を実装する
  • UnderlinePen
    • 文字列を下線部を付けて表示する
    • Productを継承してクローンする機能を実装する
  • Manager
    • インスタンスの複製メソッドを実際に使う
    • インスタンスを登録して管理する
  • Main
    • メイン処理を実装する

ソース

Product

export interface Product {
    use(s: string): void;
    createClone(): Product;
}

MessageBox

createClone()ではcloneモジュール(https://www.npmjs.com/package/clone)を使って複製を作っています。(UnderlinePenも同様)

import { Product } from './product';
import * as clone from 'clone';

export class MessageBox implements Product {
    private decochar_: string;

    constructor(decochar: string){
        this.decochar_ = decochar;
    }

    public use(s: string) {
        var length = s.length;
        console.log(Array(length + 3).join(this.decochar_));
        console.log(this.decochar_ + s + this.decochar_);
        console.log(Array(length + 3).join(this.decochar_));
    }

    public createClone() {
        return <Product>clone(this);
    }
}

UnderlinePen

import { Product } from './product';
import * as clone from 'clone';

export class UnderlinePen implements Product {
    private ulchar_: string;

    constructor(ulchar: string) {
        this.ulchar_ = ulchar;
    }

    public use(s: string) {
        console.log("\"" + s + "\"");
        console.log(Array(s.length + 3).join(this.ulchar_));
    }

    public createClone(): Product {
        return <Product>clone(this);
    }
}

Manager

register(name)でインスタンスを登録して、create(protoname)で登録された名前に対応したインスタンスを複製して返します。

登録インスタンスはshowcase_にハッシュの形で格納します。

import { Product } from './product';

export class Manager {

    private showcase_: {[name: string]: Product} = {};

    public register(name: string, proto: Product) {
        this.showcase_[name] = proto;
    }
    public create(protoname: string) {
        var proto: Product = <Product>this.showcase_[protoname];
        return proto.createClone();
    }
}

Main

実際にインスタンス(プロトタイプ)を作成し、managerに登録しています。

以降は新しいインスタンス作成の際にはmanagerに複製を作らせています。

import { Manager } from "./manager";
import { MessageBox } from './messageBox';
import { Product } from './product';
import { UnderlinePen } from './underlinePen';

var manager: Manager = new Manager();
var upen: UnderlinePen = new UnderlinePen("~");
var mbox: MessageBox = new MessageBox("*");
var sbox: MessageBox = new MessageBox("/");

manager.register("strong message", upen);
manager.register("warning box", mbox);
manager.register("slash box", sbox);

var p1: Product = manager.create("strong message")
var p2: Product = manager.create("warning box");
var p3: Product = manager.create("slash box");

p1.use("Hello world.");
p2.use("Hello world.");
p3.use("Hello world.");

実行結果

$ ts-node prototype/main.ts
"Hello world."
~~~~~~~~~~~~~~
**************
*Hello world.*
**************
//////////////
/Hello world./
//////////////

まとめ

サンプルだと種類が少なめなのでメリットをあまり感じれないのが正直な所です。とりあえずインスタンス作成の処理がめっちゃ重たい時には使おうかなーと思います。

想像の範囲ですが、フロントエンド周りだと特に同じインスタンスが多めに出てくるケースがあると思うので、その時に使えそうなパターンだと思いました。