Decorator很好用,不過我初次接觸時就搞不太懂什麼叫做「一次增加一點職責」,感覺很抽象。不過後來使用過幾個Java中的I/O類別就明白了,它真的很好用,而且有彈性,耦合也低。
結構
GOF的Decorator模式,需要用兩個方法做出來,一個是要有一致的操作,這樣才能把多個操作串接起來;另外一個是要用Constructor來把要串接的物件串接起來,這樣語法才會簡潔。
簡單的decorator大概像這樣:
<script>
function base (str) {
this._str = str;
this.render = function() {
return this._str;
};
}
function decorator1 (obj) {
this._obj = obj;
this.render = function () {
return "{" + this._obj.render() + "}";
};
}
function decorator2 (obj) {
this._obj = obj;
this.render = function () {
return "[" + this._obj.render() + "]";
};
}
function decorator3 (obj) {
this._obj = obj;
this.render = function () {
return "(" + this._obj.render() + ")";
};
}
var a = new decorator3(
new decorator2(
new decorator1(
new base("this is a base for other decorator.")
)
)
);
alert(a.render());
</script>
他的好處就是可以視需要再加以組合,每一個decorator都只加上一點「裝飾」。依照組合的順序,就會做出不同的效果。
應用
我自己曾經把它拿來做decoder的組合,例如用ajax從伺服器取到的是一個編碼過的字串,解碼過會是一個CSV格式的字串,然後還需要把它解碼成陣列。額外的需求是編碼方法會變動,CSV格式可能也會有調整,所以基於滿足這些彈性的需求,我用Decorator來把這個過程實作出來。
以下是一個簡單的範例(我沒有把真正實作細節的程式放進去,所以請用想像力了):
var Parser = {
base64Parser: {
encode: function(str){return encoded_str;},
decode: function(str){return decoded_str;}
},
escapeParser: {
encode: function(str){return encoded_str;},
decode: function(str){return decoded_str;}
},
AESParser: {
encode: function(str,key){return encoded_str;},
decode: function(str,key){return decoded_str;}
},
CSVParser = {
encode: function(arr){return encoded_str;},
decode: function(str){return decoded_arr;}
}
CSV1Parser = {
encode: function(arr){return encoded_str;},
decode: function(str){return decoded_arr;}
}
};
var Reader = {
base: function(path){
this.read = function() {
return $.ajax(path).responseText;
};
},
base64Reader: function(obj) {
var _obj = obj;
this.read = function() {
return Parser.base64Parser.decode(obj.read());
};
},
escapeReader: function(obj) {
var _obj = obj;
this.read = function() {
return Parser.escapeParser.decode(obj.read());
};
},
AESReader: function(obj, key) {
var _obj = obj;
this.read = function() {
return Parser.AESParser.decode(obj.read(), key);
};
},
CSVReader: function(obj) {
var _obj = obj;
this.read = function() {
return Parser.CSVParser.decode(obj.read());
};
},
CSV1Reader: function(obj) {
var _obj = obj;
this.read = function() {
return Parser.CSV1Parser.decode(obj.read());
};
},
};
在使用上,就依照需求適當組合就可以使用,例如伺服器提供的是一個base64編碼字串,解碼成的csv格式需要用CSVParser解碼,那可以這樣用:
var arr = new CSVReader(
new base64Reader(
new base('http://localhost/dataprovider.php?id=xxx')
)
);
var data = new ArrayDataStore(arr.read());
如果伺服器端改成用AES編碼,那只要調一下:
var arr = new CSVReader(
new AESReader(
new base('http://localhost/dataprovider.php?id=xxx'), key
)
);
var data = new ArrayDataStore(arr.read());
這樣,即使不必為未來的需求做太多設計,系統也可以有彈性應付。