本系列文章由阿里大神 **anly_jun **授權(quán)發(fā)布
前情提要
上集講到, 小光利用策略模式搞起了回饋顧客的活動(dòng). 還別說(shuō), 客流量增大不少.
然而, 隨之而來(lái)的, 顧客的聲音也不少:
- 可樂(lè)能不能加冰啊
- 綠豆湯加點(diǎn)糖唄
......
眾口難調(diào)嘛, 大家的需求不一, 有的要冰有的不要, 有的加糖有的不要... 小光帶著客戶的意見(jiàn), 開(kāi)始了飲品的改進(jìn)之路.
改進(jìn)之路
第一套方案
很快, 小光想出了第一套的解決方案: 我把加冰和不加冰的的飲料看成是兩種不同的飲料, 借助上次設(shè)計(jì)的工廠方法模式的飲料機(jī), 可以很輕易的擴(kuò)展實(shí)現(xiàn)用戶不同需求, 而不修改原來(lái)的實(shí)現(xiàn)啊.
小光的第一套方案:
然而, 還沒(méi)有投入使用呢, 小光就發(fā)現(xiàn)了問(wèn)題:
我這不定期會(huì)從飲料商戶那兒拿到一些不同的飲品, 每種都有可能要加冰, 加糖, 還可能加蜂蜜呢...那我不用干別的啦, 天天折騰這些飲料機(jī)就夠夠的啦.
裝飾出場(chǎng)
那么有什么更好的辦法呢? 小光又陷入了冥想.
這時(shí), 表妹過(guò)來(lái)了, 興高采烈的, 看到小光傻傻的樣子, 問(wèn): "表哥, 你想啥呢, 看看我剛新買的頭花好看不?"
小光: "你怎么又換頭花了啊?"
表妹: "女孩子嘛, 當(dāng)然要換著花樣打扮裝飾自己啊, 好不好看嘛?"
"好看好看." 小光敷衍答道.
突然, 小光像是想起了什么, 喃喃自語(yǔ)道, "打扮", "裝飾", "對(duì)啊, 我也可以用冰啊, 糖啊, 蜂蜜什么的來(lái)裝飾我的飲料啊."
說(shuō)做就做, 小光先從可樂(lè)入手:
良好的面向接口編程習(xí)慣, 小光抽象了一個(gè)飲料類Drink
public interface Drink {
String make();
}
實(shí)現(xiàn)一杯可樂(lè):
public class Coke implements Drink {
@Override
public String make() {
return "這是一杯可樂(lè)";
}
}
加了冰塊的飲料還應(yīng)該是飲料, 故而, 冰塊這個(gè)裝飾也實(shí)現(xiàn)了Drink接口, 加冰這個(gè)裝飾是在飲料的基礎(chǔ)上的, 所以我們還持有了一個(gè)Drink對(duì)象:
public class Ice implements Drink {
private Drink originalDrink;
public Ice(Drink originalDrink) {
this.originalDrink = originalDrink;
}
@Override
public String make() {
return originalDrink.make() + ", 加一塊冰";
}
}
來(lái)實(shí)驗(yàn)下:
public class XiaoGuang {
public static void main(String[] args) {
Drink coke = new Coke();
System.out.println(coke.make());
Drink iceCoke = new Ice(new Coke());
System.out.println(iceCoke.make());
}
}
結(jié)果:
這是一杯可樂(lè)
這是一杯可樂(lè), 加一塊冰
持續(xù)改進(jìn)
小光看到實(shí)驗(yàn)結(jié)果, 哈哈大笑, 實(shí)驗(yàn)成功, 且看我推廣到所有飲料, 所有加料中.
小光想到, 加料可能是糖, 可能是冰, 還可能蜂蜜, 還有很多未知的, 為了滿足開(kāi)閉原則, 我還是先抽象一個(gè)配料出來(lái)吧:
public abstract class Stuff implements Drink {
private Drink originalDrink;
public Stuff(Drink originalDrink) {
this.originalDrink = originalDrink;
}
@Override
public String make() {
return originalDrink.make() + ", 加一份" + stuffName();
}
abstract String stuffName();
}
冰塊原料改為繼承Stuff:
public class Ice extends Stuff {
public Ice(Drink originalDrink) {
super(originalDrink);
}
@Override
String stuffName() {
return "冰";
}
}
小光還據(jù)此增加了糖Sugar, 和蜂蜜Honey原料.
具體代碼就不在此貼出來(lái)了, 全部代碼在這里.
投入使用
經(jīng)過(guò)大量驗(yàn)證后, 小光將新的程序投入了使用, 現(xiàn)在是這樣子的...
"老板, 來(lái)一杯可樂(lè), 加冰"
Drink iceCoke = new Ice(new Coke());
System.out.println(iceCoke.make());
這是一杯可樂(lè), 加一份冰
"老板, X飲料, 加冰, 加糖"
Drink iceSugarXDrink = new Ice(new Sugar(new XDrink()));
System.out.println(iceSugarXDrink.make());
這是一杯X牌飲料, 加一份糖, 加一份冰
"可樂(lè), 加兩份冰, 加蜂蜜"
Drink doubleIceHoneyCoke = new Ice(new Ice(new Honey(new Coke())));
System.out.println(doubleIceHoneyCoke.make());
這是一杯可樂(lè), 加一份蜂蜜, 加一份冰, 加一份冰
完美表現(xiàn).
小光再也不怕顧客們的各種奇怪的加料要求了.
故事之后
照例, 故事之后, 我們用UML類圖來(lái)梳理下上述的關(guān)系:
其中Stuff類中值得注意的兩個(gè)關(guān)系:
我們的Stuff(料)也是實(shí)現(xiàn)了Drink接口的, 這是為了說(shuō)明加了料(Stuff)的飲料還是飲料.
Stuff中還聚合了一個(gè)Drink(originalDrink)實(shí)例, 是為了說(shuō)明這個(gè)料是加到飲料中的.
對(duì), 這個(gè)就是一個(gè)標(biāo)準(zhǔn)的裝飾者模式的類圖.
裝飾者模式就是用來(lái)動(dòng)態(tài)的給對(duì)象加上額外的職責(zé).
Drink是被裝飾的對(duì)象, Stuff作為裝飾類, 可以動(dòng)態(tài)地給被裝飾對(duì)象添加特征, 職責(zé).
擴(kuò)展閱讀一
有的同學(xué)可能會(huì)說(shuō), 我完全可以通過(guò)繼承關(guān)系, 在子類中添加職責(zé)的方式給父類以擴(kuò)展啊. 是的, 沒(méi)錯(cuò), 繼承本就是為了擴(kuò)展.
然而, 裝飾者模式和子類繼承擴(kuò)展的最大區(qū)別在于:
裝飾者模式強(qiáng)調(diào)的是動(dòng)態(tài)的擴(kuò)展, 而繼承關(guān)系是靜態(tài)的.
由于繼承機(jī)制的靜態(tài)性, 我們會(huì)為每個(gè)擴(kuò)展職責(zé)創(chuàng)建一個(gè)子類, 例如IceCoke, DoubleIceCoke, SugarXDrink, IceSugarXDrink等等...會(huì)造成類爆炸.
另外, 這里引入一條新的面向?qū)ο缶幊淘瓌t:
組合優(yōu)于繼承, 大家自行體會(huì)下.
擴(kuò)展閱讀二
還有的同學(xué)說(shuō), 這種按需定制的方式貌似跟之前講的Builder模式有點(diǎn)像啊, 那為什么不用Builder模式呢.
這里先說(shuō)明下二者的本質(zhì)差異:
Builder模式是一種創(chuàng)建型的設(shè)計(jì)模式. 旨在解決對(duì)象的差異化構(gòu)建的問(wèn)題.
裝飾者模式是一種結(jié)構(gòu)型的設(shè)計(jì)模式. 旨在處理對(duì)象和類的組合關(guān)系.
實(shí)際上在這個(gè)例子中, 我們是可以用Builder模式的, 但就像使用繼承機(jī)制一樣, 會(huì)有些問(wèn)題.
首先, Builder模式是構(gòu)建對(duì)象, 那么實(shí)際上要求我們是必須事先了解有哪些屬性/職責(zé)供選擇. 這樣我們才可以在構(gòu)建對(duì)象時(shí)選擇不同的Build方式. 也就是說(shuō):
Builder模式的差異化構(gòu)建是可預(yù)見(jiàn)的, 而裝飾者模式實(shí)際上提供了一種不可預(yù)見(jiàn)的擴(kuò)展組合關(guān)系.
例如, 如果用戶要了一杯帶蜂蜜的X飲料, 如果是Builder模式, 我們可能是這樣的:
XDrink xDrink = new XDrink.Builder()
.withHoney()
.build()
但是如果飲料拿出來(lái)后, 用戶還想加冰怎么辦呢? 這就是Builder模式在這種動(dòng)態(tài)擴(kuò)展情景下的局限. 看看裝飾模式怎么做:
// 加蜂蜜的X飲料
Drink honeyXDrink = new Honey(new XDrink());
System.out.println(honeyXDrink.make());
這是一杯X牌飲料, 加一份蜂蜜
// 還要加冰
Drink iceHoneyXDrink = new Ice(honeyXDrink);
System.out.println(iceHoneyXDrink.make());
這是一杯X牌飲料, 加一份蜂蜜, 加一份冰
直接在honeyXDrink的基礎(chǔ)上再裝飾一份冰即可.
另外, 大家也都看到了, 由于我們的面向接口編程方式, 裝飾者(冰塊, 蜂蜜, 糖)可不是只能用來(lái)裝飾特定的被裝飾對(duì)象(諸如可樂(lè)), 它們可以被用來(lái)裝飾所有種類的飲料(Drink)對(duì)象. 個(gè)中好處大家自行體會(huì)下.
擴(kuò)展閱讀三
我們一直在說(shuō)的, 裝飾者模式是動(dòng)態(tài)給對(duì)象加上額外的職責(zé), 這個(gè)職責(zé)實(shí)際上包括修飾(屬性), 也包括行為(方法).
例如我們上面討論的例子, 就是單純得給飲料加上了一些修飾, 是飲料編程了加冰的飲料, 加糖的飲料. 我們也可以給對(duì)象加上行為, 例如, 一部手機(jī), 我們可以通過(guò)裝飾模式給它加上紅外遙控的功能, 還可以給他加上NFC支付的功能.
Android中鼎鼎大名的Context體系, 實(shí)際上就是裝飾模式的體現(xiàn), 通過(guò)裝飾模式來(lái)給Context擴(kuò)展了一系列的功能. 如下:
還是比較清晰的, 典型的使用裝飾者模式來(lái)擴(kuò)展功能的實(shí)踐, 相比于我們的例子, 更注重功能擴(kuò)展, 體現(xiàn)了開(kāi)閉原則.
再重申下, 面向?qū)ο缶幊淌且环N思想, 設(shè)計(jì)模式都是這些思想的具體實(shí)踐和體現(xiàn). 學(xué)習(xí)設(shè)計(jì)模式可以讓我們更好的理解編程思想, 而非固定套用. 我們的目的是無(wú)招勝有招.
另外, 具體的環(huán)境中設(shè)計(jì)模式一般不是單一地用的, 往往是各種設(shè)計(jì)模式融合在一起的. 例如我們這個(gè)裝飾者里面, 各種new對(duì)象, 實(shí)際上就可以用到我們學(xué)習(xí)的工廠方法的模式進(jìn)行處理.
好了, 讓我們充分發(fā)揮自己的想象力, 多弄些原料來(lái)裝飾我們的飲料吧, 說(shuō)不定我們能調(diào)出一種前所未有的飲品呢, 哈哈.
140套Android優(yōu)秀開(kāi)源項(xiàng)目源碼狭姨,領(lǐng)取地址:http://mp.weixin.qq.com/s/afPGHqfdiApALZqHsXbw-A