【TS】今さら聞けないビルダーパターン
はじめに
今回はビルダーパターン(Builder Pattern
)について解説します。
インスタンス生成方法を切り出すことで取り回しが効くようにするデザインパターンです。
ビルダーパターンとは?
TECHSCORE
さんの解説があります。
以下、引用です。
例えば、家を建てることを考えてみます。完成する家がどのような家になるかというのは「家の構築過程」と「素材」大きく2つの要素で決定されると考えてみてください。「作成過程」とは、「どのような順番で、どこに何を配置していくか」というようなことであり、「素材」とは、「柱には何を使って、壁には何を使って・・・」ということであると考えてください。
このとき、「作成過程」には、"平屋を建てるための作成過程" や "2階建ての家を建てるための作成過程"、または "少し変わった平屋を建てるための作成過程" など様々なものが考えられます。同様に、「素材」にも、"柱は木で、壁は土壁、屋根は瓦" などのような和風の家を建てるための素材を用いることもあれば、"柱は鉄で壁と屋根はコンクリート" といった場合も考えられます。
これらをそれぞれ用意しておくことで、『 "ちょっと変わった平屋を建てる作成過程" で "柱は鉄で壁と屋根はコンクリート" の家を建ててください』という要望に柔軟に応えることができるようになります。
Builder
パターンとは、このような、「作成過程」を決定するDirector
と呼ばれるものと「表現形式」を決定するBuilder
と呼ばれるものを組み合わせることで、オブジェクトの生成をより柔軟にし、そのオブジェクトの「作成過程」をもコントロールすることができるようにするためのパターンです。
例題
以下のように四角形を表すクラスがあるとします。
起点となる座標と縦横の幅は必須項目とし、塗り潰しやラベル等のオプション項目を持っています。
class Rect {
x: number;
y: number;
width: number;
height: number;
fill: boolean = false;
fillColor: string = 'transparent';
lineColor: string = '#000';
lineWidth: number = 1;
label: string;
labelColor: string = '#000';
constructor(x: number, y: number, width: number, height: number) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
setFillColor = (color: string) => {
this.fill = true;
this.fillColor = color;
}
setLabel = (label: string, labelColor?: string) => {
this.label = label;
if(labelColor) this.labelColor = labelColor;
}
disp = () => {
console.log(`position:`, `{${this.x},${this.y}}`);
console.log(`fill:`,`${this.fill}`);
console.log(`width:`,`${this.width}`);
console.log(`height:`,`${this.height}`);
console.log(`fill:`,`${this.fill}`);
console.log(`fillColor:`,`${this.fillColor}`);
console.log(`lineColor:`,`${this.lineColor}`);
console.log(`lineWidth:`,`${this.lineWidth}`);
console.log(`label:`,`${this.label}`);
console.log(`labelColor:`,`${this.labelColor}`);
}
}
この場合、「赤く塗り潰した、座標(40,20)
のwidth100
,height400
の四角形」インスタンスを生成する場合は以下のように記載します。
const rect: Rect = new Rect(40,20,100,400);
rect.fill = true;
rect.fillColor = 'red';
rect.disp();
出力
position: {40,20}
fill: true
width: 100
height: 400
fill: true
fillColor: red
lineColor: #000
lineWidth: 1
label: undefined
labelColor: #000
これと同じ四角形を「何個も」作る必要があったり、「色だけ差し替えたもの」や「さらにラベルをつけたもの」を作ることになった場合に「ビルダーパターン」が効果を発揮します。
ビルダーパターンで実装
まず、Rect
インスタンスを生成するビルダーのインターフェースとなるIBuilder
を作ります。
interface IBuilder {
init():void;
setInfo(x: number, y: number, width: number, height: number):void;
setFillColor(color: string):void;
setLabel(label: string):void;
getResult():Rect;
}
そして、IBuilder
を実装したRedRectBuilder
を作ります。
その名の通り「赤い四角形」のRect
インスタンスを生成します。
class RedRectBuilder implements IBuilder {
private rect: Rect;
constructor() {
this.init();
}
init = () => {
this.rect = new Rect(0,0,0,0);
this.fill = true;
this.fillColor = 'red';
}
setInfo = (x: number, y: number, width: number, height: number) => {
this.rect.x = x;
this.rect.y = y;
this.rect.width = width;
this.rect.height = height;
}
setFillColor = (color: string) => {
this.rect.fill = true;
this.rect.fillColor = color;
}
setLabel = (label: string) => {
this.rect.label = label;
}
getResult = (): Rect => {
const result = this.rect;
this.init();
return result;
}
}
さらにこのRedRectBuilder
を使用するDirector
クラスを作成します。
このDirector
は座標(0,0)
で幅100
の正方形を生成するものとします。
class Director {
private _builder: IBuilder;
constructor(builder: IBuilder) {
this._builder = builder;
}
construct = () => {
this._builder.setInfo(0,0,100,100);
}
}
これらを使って「赤い正方形」を生成するコードは以下になります。
const redRectBuilder: RedRectBuilder = new RedRectBuilder();
const director: Director = new Director(redRectBuilder);
director.construct();
const result: Rect = redRectBuilder.getResult();
result.disp();
出力
position: {0,0}
fill: true
width: 100
height: 100
fill: true
fillColor: red
lineColor: #000
lineWidth: 1
label: undefined
labelColor: #000
この実装方法のメリットは、生成したいインスタンスの種類に合わせてBulder
とDirector
をカスタマイズすれば対応できる点です。
例えば青いラベル付きのRect
インスタンスを生成したい場合は、RedRectBuilder
を予め作成しておいたBlueLabelBuilder
に変えるだけで済みます。
Builder
のconstruct
を修正して座標やラベルや塗りつぶし設定を変えることもできます。
このようにDirector
とBuilder
を組み合わせることで、Rect
インスタンスを柔軟に生成できるようになります。
まとめ
今回はTypescript
でデザインパターンの一つである Builder Pattern
について紹介しました。
このパターンを導入する目安となるのが、 「生成するインスタンスのプロパティの多さ」 と 「類似したインスタンスを何個も生成する可能性の高さ」 だと思います。
何回も似たような書き方をしてちょっと違うインスタンスを生成しているな、と感じた時には導入するといいかもしれません。
Discussion