什麼是裝飾者模式 今天我們來講另外一個非常實用的設計模式: 。這個名字聽上去有些莫名其妙,不著急,我們先來記住它的一個別名: 。 我們記著這兩個名字來開始今天的文章。 首先還是上《設計模式》一書中的經典定義: 1. 動態地給一個對象添加一些額外的職責。 2. 就增加功能來說,裝飾者模式相比生成子類更 ...
什麼是裝飾者模式
今天我們來講另外一個非常實用的設計模式:裝飾者模式
。這個名字聽上去有些莫名其妙,不著急,我們先來記住它的一個別名:包裝器模式
。
我們記著這兩個名字來開始今天的文章。
首先還是上《設計模式》一書中的經典定義:
- 動態地給一個對象添加一些額外的職責。
- 就增加功能來說,裝飾者模式相比生成子類更為靈活。
我們來分析一下這個定義。
給對象添加一些新的職責,我們很容易想到創建子類來繼承父類,然後在子類上增加額外的職責。
那什麼是動態地
呢?應該就是說這些新添加的職責在類一開始創建的時候我們並不知道,而是在使用過程根據需要而添加的。
相比生成子類更為靈活,這句話讓裝飾者模式和子類繼承赤裸裸的刀兵相見了。沒有對比就沒有傷害,那我們就用例子來驗證這句話。
傳統面向對象的實現
我們假設你是以為已經走上人生巔峰的汽車生產商,你的公司生產各種用途的汽車,某一天一個客戶下單了四種汽車,分別是家用轎車、SUV、旅行車和跑車。我們很輕鬆地像下麵這樣進行交付了。
var Car = function(){}
Car.prototype.start = function(){
console.log("轟轟轟,啟動正常!")
}
var Sedan = new car();// 小轎車
var Suv = new Car();// SUV
var Wagon=new Car();// 旅行車
var Roadster=new Car();// 跑車
//是不是又學會了幾個英文單詞?
過了幾天客戶找來了,說最近人們愛上了西藏自駕游,人們都希望能夠選裝一些方便越野和載物的功能,比如加裝雪地胎、行李箱,升高底盤。
有經驗的你滿口答應下來,這個簡單,於是你交付了下麵的代碼:
//SUV
Suv.prototype.changeTire = function(){
console.log("我換了雪地胎");
}
Suv.prototype.addHeight = function(){
console.log("我升高了底盤");
}
Suv.prototype.addBox = function(){
console.log("我安裝了行李箱");
}
//Wagon
Wagon.prototype.changeTire = function(){
console.log("我換了雪地胎");
}
Wagon.prototype.addHeight = function(){
console.log("我升高了底盤");
}
Wagon.prototype.addBox = function(){
console.log("我安裝了行李箱");
}
//Sedan
Sedan.prototype.changeTire = function(){
console.log("我換了雪地胎");
}
Sedan.prototype.addHeight = function(){
console.log("我升高了底盤");
}
Sedan.prototype.addBox = function(){
console.log("我安裝了行李箱");
}
// 使用
var suv = new Suv();
suv.changeTire();
suv.addHeight();
suv.addBox();
suv.start();
...
你增加了多少種特性?3x3=9種。
你又問,我直接把這三個特性加在Car上不行嗎?就不用這麼麻煩了。
當然不行,因為我們還有一種車:Roadster跑車。
你能想象法拉利換了雪地胎背上行李箱升高底盤是個什麼死樣子嗎?這麼乾的人肯定瘋了。
如果我們把特性一股腦加在Car上,就避免不了這種情況的發生。
這個時候,就體現出子類繼承的不靈活之處。
下麵,裝飾者模式就要正式登場了。
var Car=function (){}
Car.prototype.start=function(){
console.log("轟轟轟,啟動正常!")
}
// 創建裝飾類(包裝類)
var ChangeTireDec=function(car){
this.car=car;
}
var AddHeightDec=function(car){
this.car=car;
}
var AddBoxDec=function(car){
this.car=car;
}
// 裝飾類具有和Car同樣的特性,只不過額外執行了一些其他的操作
ChangeTireDec.prototype.start=function(){
console.log("我換了雪地胎");
this.car.start();
}
AddHeightDec.prototype.start=function(car){
console.log("我升高了底盤");
this.car.start();
}
AddBoxDec.prototype.start=function(car){
console.log("我安裝了行李箱");
this.car.start();
}
// 使用
var suv=new Suv();
suv=new ChangeTireDec(suv);
suv=new AddHeightDec(suv);
suv=new AddBoxDec(suv);
suv.start();
上面的代碼你增加了幾種特性?只有三種!而且不管你是給SUV還是Wagon還是Sedan加裝,都不需要再增加特性的代碼。
這,就是裝飾者模式的優勢所在。
現在我們再回過頭來看看GoF的定義:
- 動態地給一個對象添加一些額外的職責。
- 就增加功能來說,裝飾者模式相比生成子類更為靈活。
怎麼樣,是不是如同1+1=2一樣簡單了?現在你應該也明白了為什麼裝飾者模式又叫座包裝器模式了。因為它將類的原有特性包裝起來,添加其他的特性,就像一個箱子一樣。而且實現過程中,還滿足了封閉-開放
原則。
JavaScript的實現
上面的例子中,我們是模擬了傳統的面向對象語言來解釋什麼是裝飾者模式。我們都知道,要動態改變JavaScript對象非常容易,可以向操作變數一個操作對象,我們再來改寫下上面的例子,讓它更javasripty
var car = {
start: function(){
console.log("轟轟轟,正常啟動!");
}
}
var ChangeTireDec = function(){
console.log("我換了雪地胎");
}
var AddHeightDec = function(){
console.log("我升高了底盤");
}
var AddBoxDec = function(){
console.log("我安裝了行李箱");
}
var start1 = car.start;
car.start=function(){
ChangeTireDec();
start1();
}
var start2=car.start;
car.start = function(){
AddHeightDec();
start2();
}
var start3=car.start();
car.start = function(){
AddBoxDec();
start3();
}
// 執行
car.start();
實際中的應用
從上面的例子我們可以看出來,我們不斷的將car.start
的引用賦值給臨時變數,然後將原來的car.start
指向新的對象--包含了原來對象的引用和新的特性的對象。這很好的保證了代碼的開放-封閉原則
,這是今天第二次提到這個原則了,就是對修改封閉,對新增開放。
特別當你要重構一個非常複雜的多人項目時,如果你不想因為修改了同事的一行代碼而引起“蝴蝶效應”,那麼將他的方法整個打包賦值然後用裝飾者模式增加新的功能,是一種非常安全而且高效的做法。
下一步,我們可以愉快的去使用裝飾者模式啦!