まめージェント

Android, GAE, AngularJSの技術ネタ中心。Twitter: @mame01112

Javascriptでデザインパターン (その8: Decorator)

今回はDecoratorパターン。

8回目は、Decoratorパターン。compositeパターンとよく似て、中身と外側(今回は中身と装飾)を同一視することで、使う側が中身を意識する必要がなくなるメリットがあります。

そもそもDecoratorパターンは?

Wikipediaによれば、"このパターンは、既存のオブジェクトに新しい機能や振る舞いを動的に追加することを可能にする"とのこと。

僕の言葉でいえば、
・核となるオブジェクトがあってそれに微妙な変更を頻繁に/動的に加えたいときに使う
・(これはクラスベースの言語特有の説明かもですが)通常核となるオブジェクトがある場合は、継承(extend)することで機能拡張をするが、そのパターンが多いときは"継承の継承の継承・・・"というように、そのパターンがネズミ算的に増えてしまう。
・そこでDecoratorパターンでは、その"微妙な変更"を1つのクラスとして切り出して上記のようなパターンを減らすことを目的とするですかね。

サンプルコード

サンプルコードです。
何か実践に近い例はないかなぁ・・・と探した結果、Photoshopのような画像編集ソフトの例を思いつきました。
Photoshopのような編集ソフトは、効果として、元の画像に何回も/何種類もエフェクトを追加すると思います。そのエフェクトの管理は、このDecoratorパターンで実現できますね。

下記の例では、Contentという元々の画像に対して、"ColorVisualEffect", "AlphaVisualEffect"を加える例です。(本当はもっと多く書いてもいいのですが、コードが長くなってしまうので2つだけに絞っています)

まずは、核となる元画像(Content)を作ります。

// Component
var Content = function(){
};

Content.prototype = {
	show : function(){
		console.log("Original image");
	}
};

特に変わったところはないですね。show()は、画像を表示するメソッドです。

続いて、ColorVisualEffectを定義します。

var ColorVisualEffect = function(contentArg, paramArg){
	this.content = contentArg;
	this.param = paramArg;
};

ColorVisualEffect.prototype = {
	show : function(){
		this.content.show();
		console.log("Color value: " + this.param);
	}
};

ここでのキモは、"show()"のメソッドがContentと同じであること(中身とエフェクトを同一視している)、そしてContentをメンバ変数として持つことです。メンバ変数として持つ理由は、後ほど。

続いて、AlphaVisualEffect。これはColorVisualEffectとほぼ同じなので、特に説明はいらないかと思います。

var AlphaVisualEffect = function(contentArg, paramArg){
	this.content = contentArg;
	this.param = paramArg;
};

AlphaVisualEffect.prototype = {
	show : function(param){
		this.content.show();
		console.log("Alpha value: " + this.param);
	}
};

そして、これを実際に表示してみます。まずは、元画像のみ。

var result1 = new Content();
result1.show();

結果。

Original image

そりゃそうだ。

続いて、これにColorVisualEffectを追加します。
ここで、ColorVisualEffectのコンストラクタにContentを渡してメンバ変数に保持している部分が効果を発揮します。ColorVisualEffectのshow()が呼ばれると、Contentのshow()も呼ばれるようになっています。

var result2 = new ColorVisualEffect(new Content(), 50);
result2.show();

その結果。

Original image
Color value: 50

ちゃんと元画像と、ColorVisualEffectも適用されています(値の50というのは適当です)

最後に、ColorVisualEffectをかけつつ、AlphaVisualEffectもかけてみます。それぞれのコンストラクタに同じContentの型を渡しているおかげで、つなげてエフェクトを適用することが可能になっています。

var result3 = new AlphaVisualEffect(new ColorVisualEffect(new Content(), 50), 25);
result3.show();

結果。

Original image
Color value: 50
Alpha value: 25

ColorVisualEffectもAlhpaVisualEffectも適用されましたね。

このパターンは、もちろん同じインスタンスを2回作成すれば、同じエフェクトを2回適用することも可能です。

new AlphaVisualEffect(new AlphaVisualEffect(new Content(), 80), 45);

とやると、透明度を80%にした後に、さらに45%にする・・・という処理になります。

余談・・・

Photoshopでエフェクトを重ねがけしようとすると、”新しいインスタンスが作成されます”という感じの注意がなされますが、これって、おそらくDecoratorパターンを使って、新しいエフェクトのインスタンスを作ろうとしているんじゃないかと思っています。もちろん、Photoshopソースコードなんて見たことがないので推測ですが。

以上、Decoratorパターンでした!