まめーじぇんと@Tech

技術ネタに関して (Android, GAE, Angular). Twitter: @mame01122

Javascriptでデザインパターン (その5: Builder)

今回はBuilderパターン。

このシリーズも5回目まで来ました。結構いい感じのペースで更新できてる気がします。今回はBuilderパターン。このパターンは、Androidとかを書いてると結構いろんなところで見ますね。DialogBuilderとか。
今回は、そのBuilderパターンをJavascriptで書いてみようと思います。

そもそもBuilderパターンとは?

wikipediaによれば、" オブジェクトの生成過程を抽象化することによって、動的なオブジェクトの生成を可能にする"とのこと。・・・何のこっちゃですね。

僕の言葉で説明すれば、”何か多くの引数を必要とするインスタンスを作るときに、コンストラクタで全部渡してもいいんだけど、10個も20個も引数に並べると、順番を注意深く確認したりして実装も大変だよね?ということで、必要なものをコンストラクタの引数ではなく、必要なものだけsetできるようにするよ。これだと開発途中に引数が増えてもコンストラクタを修正→そのコンストラクタを使っている他のクラスも一緒に修正という事態を防ぐことができるよ”ですかね。

このデザインパターンが単純なsetterと異なるのは、メソッドチェーンが使えること。setterだけだと、

object.setParamA("hoge");
object.setParamA("foo");
object.setParamA("bar");
.
.

というように必要な数だけsetterを呼ばないといけませんが、Builderパターンを使えば、

object.setParamA("hoge").setParamA("foo").setParamA("bar")...;

というようにシンプルに書くことができます。

またBuilderパターンには、Builderから生成されるインスタンスに余計なsetter系のメソッドを追加しなくて済む、というメリットもあります。

インスタンスに余計なsetterがついていると、(特に大規模開発では)他のプログラマにそれだけ意図せぬコーディングがなされる可能性が出てきてしまいます。”いやー、そこで値をsetしちゃう?”みたいな。

サンプルコード

では、実際の中身です。今回は、家を作ることにしました。家は、ドアと、壁と、窓が3枚と、煙突を持つことにします。家はデフォルトのオプションで見た目が決まっていますが、Builderパターンを使ってそれらを自由にカスタマイズする、ということにしましょう。

まずは、家を定義します。

// Product
var House = function(argDoor, argWall, argWindowA, argWindowB, argWindowC, argChimney){
	this.door = argDoor;
	this.wall = argWall;
	this.windowA = argWindowA;
	this.windowB = argWindowB;
	this.windowC = argWindowC;
	this.chimney = argChimney;
};

House.prototype = {

	getDoor : function(){
		return this.door;
	},

	getWall : function(){
		return this.wall;
	},
	getWindowA : function(){
		return this.windowA;
	},
	getWindowB : function(){
		return this.windowB;
	},
	getWindowC : function(){
		return this.windowC;
	},
	getChimney : function(){
		return this.chimney;
	}
};


キモは、家のインスタンスにsetter系のメソッドがないこと、です(理由は上記のとおり)

続いて、Builderを定義します。コード自体は多少長いですが、同じような内容が続くのでそんなに見づらくはないかと思います。

// Builder
var HouseBuilder = function(){
	this.house = new House("my Door", "my wall", "my window A", "my window B", "my window C", "my chimney");
};

// Concrete Builder
HouseBuilder.prototype = {

	setDoor : function(door){
		console.log("setDoor");
		this.house.door = door;
		return this;
	},

	setWall : function(wall){
		console.log("setWall");
		this.house.wall = wall;
		return this;
	},

	setWindowA: function(windowA){
		console.log("setWindowA");
		this.house.windowA = windowA;
		return this;
	},

	setWindowB: function(windowB){
		console.log("setWindowB");
		this.house.windowB = windowB;
		return this;
	},

	setWindowC: function(windowC){
		console.log("setWindowC");
		this.house.windowC = windowC;
		return this;
	},

	setChimney : function(chimney){
		console.log("setChimney");
		this.house.chimney = chimney;
		return this;
	},

	getConstructedInstance : function(){
		console.log("getConstructedInstance");
		var tmp = this.house;
		return tmp.getDoor() + " / " + tmp.getWall() + " / " + tmp.getWindowA() + " / " + tmp.getWindowB() + " / " + tmp.getWindowC() + " / " + tmp.getChimney();
	}
};

ここでのキモは、HouseBuilder.prototype内のsetter系のメソッドが、すべて自分自身(this)を返している部分です。ここでthisを返すことにより、メソッドチェーンを実現することができます。

最後に、これを呼び出す部分(BuilderパターンではDirectorと呼ばれる)を定義します。

// Director
var builder = new HouseBuilder();
var result = builder.setDoor("My Brown door").setWall("My white wall").setWindowA("My great window A").setWindowB("My miracle window B").setWindowC("My super window C").setChimney("My big chimney").getConstructedInstance();
console.log("result: " + result);

見てわかるように、builderのインスタンスを作った後、自分好みにsetXXXを呼び出してカスタマイズしています。その後、getConstructedInstance()を呼び出して出来上がったインスタンス(ここでは文字列にしてしまっていますが)を取得しています。

以上でBuilderパターンは終了です!