#はじめに
詳しいことや他のパターンは**デザインパターンをJavaScriptとJavaでの実装を比較して理解する**に書いていきます。
JavaScriptの例はJavaのを見て書きました。
クラス型・プロトタイプ型、型付の強弱、アクセス修飾子など特徴の違いなどは活かしていません。
ご了承ください。
#Decorator
スポンジケーキが1つあり、クリームを塗ればショートケーキ、それにイチゴを載せればイチゴショートケーキに
チョコを塗ればチョコケーキになる
オブジェクトも似ていて、中心となるスポンジケーキのようなオブジェクトがあり、それに飾りつけとなる機能を一皮一皮かぶせていって、目的にあったオブジェクトに仕上げていく
このような、オブジェクトにどんどんデコレーションをするデザインパターン
decoratorとは「decorate(飾り付け)するもの」という意味
###コード
public class Main {
public static void main(String[] args) {
Display b1 = new StringDisplay("Hello, world.");
Display b2 = new SideBorder(b1, '#');
Display b3 = new FullBorder(b2);
b1.show();
b2.show();
b3.show();
Display b4 =
new SideBorder(
new FullBorder(
new FullBorder(
new SideBorder(
new FullBorder(
new StringDisplay("こんにちは。")
)
, '*')
)
)
, '/');
b4.show();
}
}
public abstract class Display {
public abstract int getColumns();
public abstract int getRows();
public abstract String getRowText(int row);
public final void show() {
for (int i = 0; i < getRows(); i++) {
System.out.println(getRowText(i));
}
}
}
public class StringDisplay extends Display {
private String string;
public StringDisplay(String string) {
this.string = string;
}
public int getColumns() {
return string.getBytes().length;
}
public int getRows() {
return 1;
}
public String getRowText(int row) {
if (row == 0) {
return string;
} else {
return null;
}
}
}
public abstract class Border extends Display{
protected Display display;
protected Border(Display display) {
this.display = display;
}
}
public class SideBorder extends Border {
private char borderChar;
public SideBorder(Display display, char ch) {
super(display);
this.borderChar = ch;
}
public int getColumns() {
return 1 + display.getColumns() + 1;
}
public int getRows() {
return display.getRows();
}
public String getRowText(int row) {
return borderChar + display.getRowText(row) + borderChar;
}
}
public class FullBorder extends Border {
public FullBorder(Display display) {
super(display);
}
public int getColumns() {
return 1 + display.getColumns() + 1;
}
public int getRows() {
return 1 + display.getRows() + 1;
}
public String getRowText(int row) {
if (row == 0) {
return "+" + makeLine('-', display.getColumns()) + "+";
} else if (row == display.getRows() + 1) {
return "+" + makeLine('-', display.getColumns()) + "+";
} else {
return "|" + display.getRowText(row - 1) + "|";
}
}
private String makeLine(char ch, int count) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < count; i++) {
buf.append(ch);
}
return buf.toString();
}
}
##JavaScript
※consoleの仕様なのかわからないのですが、2byteのものを入れるとレイアウトが崩れてしまいます
###コード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Decorator</title>
</head>
<body>
<script src="Main.js"></script>
<script src="StringDisplay.js"></script>
<script src="SideBorder.js"></script>
<Script src="FullBorder.js"></script>
<script src="Display.js"></script>
</body>
</html>
MAIN = {};
MAIN.init = function() {
var b1 = new StringDisplay("Hello world.");
var b2 = new SideBorder(b1, '#');
var b3 = new FullBorder(b2);
b1.show();
b2.show();
b3.show();
var b4 =
new SideBorder(
new FullBorder(
new FullBorder(
new SideBorder(
new FullBorder(
new StringDisplay("Hello")
), '*'
)
)
), '/'
);
b4.show();
};
window.addEventListener("load", MAIN.init);
var StringDisplay = function(string) {
this.string = string;
};
StringDisplay.prototype = {
constructor: "StringDisplay",
getColumns: function() {
return this.getBytes(this.string);
},
getRows: function() {
return 1;
},
getRowText: function(row) {
if (row == 0) {
return this.string;
} else {
return null;
}
},
getBytes: function(str) {
var len = 0;
str = escape(str);
for (i = 0; i < str.length; i++, len++) {
if (str.charAt(i) == "%") {
if (str.charAt(++i) == "u") {
i += 3;
len++
}
i++;
}
}
return len;
}
};
var SideBorder = function(display, ch) {
this.display = display;
this.borderChar = ch;
};
SideBorder.prototype = {
constructor: "SideBorder",
getColumns: function() {
return 1 + this.display.getColumns() + 1;
},
getRows: function() {
return this.display.getRows();
},
getRowText(row) {
return this.borderChar + this.display.getRowText(row) + this.borderChar;
}
}
var FullBorder = function(display) {
this.display = display;
};
FullBorder.prototype = {
constructor: "FullBorder",
getColumns: function() {
return 1 + this.display.getColumns() + 1;
},
getRows: function() {
return 1 + this.display.getRows() + 1;
},
getRowText: function(row) {
if (row == 0) {
return "+" + this.makeLine('-', this.display.getColumns()) + "+";
} else if (row == this.display.getRows() + 1) {
return "+" + this.makeLine('-', this.display.getColumns()) + "+";
} else {
return "|" + this.display.getRowText(row - 1) + "|";
}
},
makeLine: function(ch, count) {
var stringBuffer = "";
for (var i = 0; i < count; i++) {
stringBuffer += '-';
}
return stringBuffer;
}
}
StringDisplay.prototype.show =
SideBorder.prototype.show =
FullBorder.prototype.show = function() {
for (var i = 0; i < this.getRows(); i++) {
console.log(this.getRowText(i));
}
};
##Decoratorパターンの登場人物
###Componentの役
機能を追加するときの核になる役
インタフェース(API)だけを定める
サンプルプログラム⇒Display(class)
###ConcreateComponentの役
Component役のインタフェース(API)を実装している
サンプルプログラム⇒StringDisplay(class)
###Decorator(装飾者)の役
Component役のインタフェース(API)を実装している
そして、Component役を持つ
サンプルプログラム⇒Border(class)
###ConcreateDecorator(具体的な装飾者)の役
具体的なDecoratorの役
サンプルプログラム⇒SideBorder(class)
FullBorder(class)
##Decoratorパターンの必要性
Decoratorパターンを使うことにより、具体的な飾り枠(ConcreateDecorator)役をたくさん作って、これらを組み合わせることにより様々な種類のオブジェクトを作成することができる
そして中身を変更することなく機能の追加を行うことができ、動的に機能の追加できるところも特徴である
##Decoratorパターンの使い時
再帰的な構造を扱い同一視できるという特徴がCompositeと似ているが、目的が異なる
Compositeは内側に再帰的な構造
Decoratorは外側に再帰的な構造
##関連しているパターン
- Adapterパターン
- Strategyパターン