三毒费、裝飾者模式(Decorator)

本章可以稱為“給愛用繼承的人一個(gè)全新的設(shè)計(jì)眼界” ,我們即將再度探討典型的繼承濫用問題愈魏。你將在本章學(xué)到如何使用對(duì)象組合的方式蝗罗,做到在運(yùn)行時(shí)裝飾類。一旦你熟悉了裝飾的技巧蝌戒,你將能夠在不修改任何底層代碼的情況下,給你的(或別人的)對(duì)象賦予新的職責(zé)沼琉。

本章目錄如下:

????????一北苟、階段一

????????二、階段二

? ??????三打瘪、階段三

????????四友鼻、java中的裝飾者

????????五傻昙、模式問答

????????六、設(shè)計(jì)原則總結(jié)

本章需求是設(shè)計(jì)一個(gè)星巴克點(diǎn)咖啡應(yīng)用彩扔,并通過代碼迭代妆档、優(yōu)化過程得出前人的設(shè)計(jì)經(jīng)驗(yàn)之一裝飾者模式。首先我們先分析需求的變化之處:創(chuàng)造新調(diào)制方式的咖啡和原料價(jià)格變化虫碉。我們所設(shè)計(jì)的系統(tǒng)必須松耦合贾惦,且能夠很好的適應(yīng)變化才行。下面以代碼的迭代演化過程為線索介紹敦捧。

一须板、階段一

? ? 需求分析(最差設(shè)計(jì)):首先,我們要明確咖啡是一個(gè)對(duì)象兢卵,是一個(gè)由各種原料調(diào)配出來的對(duì)象习瑰,所以不能像超市購物一樣羅列所有原料來計(jì)算價(jià)錢,所以每一種咖啡都對(duì)應(yīng)一個(gè)類秽荤。階段一不把公共操作抽象出來繼承甜奄,每種咖啡各自實(shí)現(xiàn)自己的功能。類圖如下:

????缺點(diǎn):

? ? ? ? ? ? 1窃款、代碼復(fù)用度很低课兄,幾乎沒有。

? ? ? ? ? ? 2雁乡、彈性非常低第喳,一點(diǎn)也不能應(yīng)對(duì)變化。比如某一調(diào)料價(jià)格變化后踱稍,更改每個(gè)咖啡價(jià)格的工作是災(zāi)難性的曲饱。

二、階段二

????需求分析:把公共操作抽象出來珠月,通過繼承實(shí)現(xiàn)每種咖啡扩淀。類圖如下:

? ? 缺點(diǎn):只能適應(yīng)部分變化,如調(diào)料價(jià)格變化啤挎;但是有些變化不適應(yīng)驻谆,如創(chuàng)造新調(diào)制方式的咖啡時(shí)不能復(fù)用代碼。最終要的是違反了“開放—關(guān)閉”原則庆聘。且子類數(shù)量還是爆炸式增長(zhǎng)胜臊。

三、階段三

????繼承的缺點(diǎn)+組合的優(yōu)點(diǎn):利用繼承設(shè)計(jì)子類的行為伙判,是在編譯時(shí)靜態(tài)決定的象对,而且所有的子類都會(huì)繼承到相同的行為。然而宴抚,如果能夠利用組合的做法擴(kuò)展對(duì)象的行為勒魔,就可以在運(yùn)行時(shí)動(dòng)態(tài)地進(jìn)行擴(kuò)展甫煞。即通過動(dòng)態(tài)地組合對(duì)象,可以通過寫新代碼來添加新功能冠绢,而無須修改現(xiàn)有代碼抚吠。這樣就沒有改變現(xiàn)有代碼,那么引進(jìn)bug或產(chǎn)生意外副作用的機(jī)會(huì)將大幅度減少弟胀。

? ? 我的理解:組合并不是拋棄繼承楷力,它是多個(gè)繼承的組合,等于說以前那個(gè)龐大的繼承被拆分了邮利。

????如何評(píng)作設(shè)中的好壞弥雹?對(duì)于變化是否能做到使原有代碼免于修改!

? ? 何為變化延届?從不同緯度看增剪勿、刪、改方庭、查厕吉!

? ??設(shè)計(jì)原則4(開放—關(guān)閉原則):類應(yīng)該對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉械念。該原則目標(biāo)是允許類容易擴(kuò)展头朱,在不修改現(xiàn)有代碼的情況下,就可搭配新的行為龄减。這樣的設(shè)計(jì)具有彈性项钮,可以接受新的功能來應(yīng)對(duì)需求的改變。

? ? 階段三需求分析:以飲料為主體希停,然后在運(yùn)行時(shí)以調(diào)料來“裝飾”飲料烁巫。可以把裝飾對(duì)象理解為一個(gè)空心管宠能,只要符合指定接口的對(duì)象都可以插入空心管進(jìn)行包裝亚隙,包裝后的對(duì)象符合指定接口就可以被再次包裝。比如要制作一個(gè)加奶泡(Whip)加摩卡(mocha)的深焙咖啡(DarkRoast)违崇,那么包裝圖如下:

階段三的類圖如下:

????讀者可能不太理解上圖阿弃,下面我們說明一下裝飾者模式是什么,然后讀者就明白了羞延。

????裝飾者模式定義:裝飾者模式動(dòng)態(tài)地將責(zé)任附加到對(duì)象上渣淳。若要擴(kuò)展功能,裝飾者提供了比繼承更有彈性的替代方案伴箩。裝飾者模式的類圖如下:

? ? 下面說明階段三的代碼:

======================類圖中的Beverage基類=================

public abstract class Beverage {

????String description = "Unknown Beverage";??

????public String getDescription() {

????????return description;

????}?

????public abstract double cost();

}

======================類圖中的調(diào)料抽象類CondimentDecorator=================

public abstract class CondimentDecorator extends Beverage {

????public abstract String getDescription();//使所有的調(diào)料裝飾者都必須重新實(shí)現(xiàn)該方法水由,因?yàn)樵谡{(diào)料具體實(shí)現(xiàn)類中我們要返回調(diào)料名+被裝飾者的getDescription()對(duì)象,所以此處必須抽象化,以保證子類必須實(shí)現(xiàn)該方法

}

======================類圖中的飲料實(shí)現(xiàn)類=================

public class Espresso extends Beverage {? ?//濃縮咖啡

????public Espresso() {

????????description = "Espresso";

????}??

????public double cost() {

????????return 1.99;

????}

}

public class HouseBlend extends Beverage {//綜合咖啡

????public HouseBlend() {

????????description = "House Blend Coffee";

????}?

????public double cost() {????

????????return .89;

????}

}

public class DarkRoast extends Beverage {//深焙咖啡

????public DarkRoast() {

????????description = "Dark Roast Coffee";

????}?

????public double cost() {

????????return .99;

????}

}

public class Decaf extends Beverage {//低咖啡因

????public Decaf() {

????????description = "Decaf Coffee";

????}?

????public double cost() {

????????return 1.05;

????}

}

======================類圖中的裝飾者實(shí)現(xiàn)類砂客,即調(diào)料實(shí)現(xiàn)類=================

public class Mocha extends CondimentDecorator {//摩卡

????Beverage beverage;?

????public Mocha(Beverage beverage) {

????????this.beverage = beverage;

????}?

????public String getDescription() {

????????return beverage.getDescription() + ", Mocha";

????}?

????public double cost() {

????????return .20 + beverage.cost();

????}

}

public class Milk extends CondimentDecorator {//奶

????Beverage beverage;

????public Milk(Beverage beverage) {

????????this.beverage = beverage;

????}

????public String getDescription() {

????????return beverage.getDescription() + ", Milk";

????}

????public double cost() {

????????return .10 + beverage.cost();

????}

}

public class Soy extends CondimentDecorator {//豆?jié){

????Beverage beverage;

????public Soy(Beverage beverage) {????

????????this.beverage = beverage;

????}

????public String getDescription() {

????????return beverage.getDescription() + ", Soy";

????}

????public double cost() {

????????return .15 + beverage.cost();

????}

}

public class Whip extends CondimentDecorator {//奶泡

????Beverage beverage;?

????public Whip(Beverage beverage) {

????????this.beverage = beverage;

????}?

????public String getDescription() {

????????return beverage.getDescription() + ", Whip";

????}?

????public double cost() {

????????return .10 + beverage.cost();

????}

}

======================供應(yīng)咖啡,即點(diǎn)餐=================

public class StarbuzzCoffee {? public static void main(String args[]) {

?????//濃縮咖啡呵恢,不需要調(diào)料

????Beverage beverage = new Espresso();

????System.out.println(beverage.getDescription()? + " $" + beverage.cost());?

????//深焙咖啡鞠值,加雙份摩卡,加一份奶泡

????Beverage beverage2 = new DarkRoast();

????beverage2 = new Mocha(beverage2);

????beverage2 = new Mocha(beverage2);

????beverage2 = new Whip(beverage2);

????System.out.println(beverage2.getDescription()? + " $" + beverage2.cost());?

????//綜合咖啡渗钉,加豆?jié){彤恶、摩卡、奶泡

????Beverage beverage3 = new HouseBlend();

????beverage3 = new Soy(beverage3);

????beverage3 = new Mocha(beverage3);

????beverage3 = new Whip(beverage3);

????System.out.println(beverage3.getDescription()? + " $" + beverage3.cost());

}

}

四鳄橘、java中的裝飾者

java中最常見的裝飾者io類圖如下:

Java IO引出裝飾者模式的“缺點(diǎn)”:

? ? (1)声离、裝飾者模式常常造成設(shè)計(jì)中有大量的小類,可能會(huì)造成使用此API程序員的困擾瘫怜。但是术徊,現(xiàn)在你已經(jīng)了解了裝飾者的工作原理,以后當(dāng)使用別人的大量裝飾的API時(shí)鲸湃,就可以很容易地辨別出他們的裝飾者類是如何組織的赠涮,以方便用包裝方式取得想要的行為。

? ? (2)暗挑、采用裝飾者在實(shí)例化組件時(shí)將增加代碼的復(fù)雜度笋除。因?yàn)橐坏┦褂醚b飾者模式,不只需要實(shí)例化組件炸裆,還要把此組件包裝進(jìn)裝飾者中垃它,可能會(huì)有很多個(gè)。但是烹看,可以通過工廠模式(Factory)/生成器模式(Builder)封裝裝飾者模式的創(chuàng)建過程來解決該問題国拇。

下面我們編寫一個(gè)自己的java/io裝飾者,代碼如下

public class LowerCaseInputStream extends FilterInputStream {

????public LowerCaseInputStream(InputStream in) {

????????super(in);

????}?

????public int read() throws IOException {

????????int c = in.read();

????????return (c == -1 ? c : Character.toLowerCase((char)c));

????}

????public int read(byte[] b, int offset, int len) throws IOException {

????????int result = in.read(b, offset, len);

????????for (int i = offset; i < offset+result; i++) {

????????????b[i] = (byte)Character.toLowerCase((char)b[i]);

????????}

????????return result;

????}

}

public class InputTest {

????public static void main(String[] args) throws IOException {

????????int c;

????????try {

????????????InputStream in =? new LowerCaseInputStream( new BufferedInputStream( new FileInputStream("test.txt")));

????????????while((c = in.read()) >= 0) {

????????????????System.out.print((char)c);

????????????}

????????????in.close();

????????} catch (IOException e) {

????????e.printStackTrace();

????????}

????}

}


五听系、模式問答

1贝奇、如何讓設(shè)計(jì)的每個(gè)部分都遵循開放-關(guān)閉原則?

????答: 通常,你辦不到靠胜。要讓00設(shè)計(jì)同時(shí)具備開放性和關(guān)閉性掉瞳,又不修改現(xiàn)有的代碼,需要花費(fèi)許多時(shí)間和努力浪漠。一般來說陕习,我們實(shí)在沒有閑工夫把設(shè)計(jì)的每個(gè)部分都這么設(shè)計(jì)(而且,就算做得到址愿,也可能只是一種浪費(fèi))该镣。遵循開放-關(guān)閉原則,通常會(huì)引入新的抽象層次响谓,增加代碼的復(fù)雜度损合。你需要把注意力集中在設(shè)計(jì)中最有可能改變的地方省艳,然后應(yīng)用開放-關(guān)閉原則。

2嫁审、使用裝飾者模式跋炕,你必須管理更多的對(duì)象,所以犯錯(cuò)的機(jī)會(huì)會(huì)增加律适。那么如何避免呢辐烂?

????裝飾者通常是用其他類似于工廠或生成器這樣的模式來包裝的。一旦我們講到這兩個(gè)模式捂贿,你就會(huì)明白具體的組件及其裝飾者的創(chuàng)建過程纠修,它們會(huì)“封裝得很好” ,所以不會(huì)有這種問題厂僧。

3扣草、裝飾者知道這一連串裝飾鏈條中其他裝飾者的存在嗎?

? ? 不能吁系。裝飾者該做的事就是增加行為到被包裝對(duì)象上德召。當(dāng)需要窺視裝飾者鏈中的每一個(gè)裝飾者時(shí),這就超出了他們的天賦了汽纤。

六上岗、設(shè)計(jì)原則總結(jié)

設(shè)計(jì)原則1:找出應(yīng)用中可能需要變化之處,把它們獨(dú)立出來蕴坪,不要和那些不需要變化 T個(gè)T的代碼混在一起肴掷。該設(shè)計(jì)原則作用: “把會(huì)變化的部分取出并封裝起來,以便以后可以輕易地改動(dòng)或擴(kuò)充此部分背传,而不影響不需要變化的其他部分”呆瞻。

設(shè)計(jì)原則2:針對(duì)接口編程,而不是針對(duì)實(shí)現(xiàn)編程径玖。===我的理解是這個(gè)原則的使用優(yōu)先級(jí)排在“設(shè)計(jì)原則1:變化原則”之后痴脾,即該原則是一個(gè)在大模式已確定需要實(shí)現(xiàn)具體類時(shí)針對(duì)實(shí)現(xiàn)類采用的原則,針對(duì)范圍比較小梳星。

設(shè)計(jì)原則3:為了交互對(duì)象之間的松耦合設(shè)計(jì)而努力赞赖。總結(jié)為“多用組合少用繼承”冤灾!目前我見過兩種組合方式:實(shí)例作為另外實(shí)例的屬性前域,如策略模式、裝飾者模式韵吨;實(shí)例作為另外實(shí)例的集合屬性的成員匿垄,如觀察者模式

設(shè)計(jì)原則4(開放—關(guān)閉原則):類應(yīng)該對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉椿疗。該原則目標(biāo)是允許類容易擴(kuò)展漏峰,在不修改現(xiàn)有代碼的情況下,就可搭配新的行為变丧。這樣的設(shè)計(jì)具有彈性芽狗,可以接受新的功能來應(yīng)對(duì)需求的改變。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末痒蓬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子滴劲,更是在濱河造成了極大的恐慌攻晒,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件班挖,死亡現(xiàn)場(chǎng)離奇詭異鲁捏,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)萧芙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門给梅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人双揪,你說我怎么就攤上這事动羽。” “怎么了渔期?”我有些...
    開封第一講書人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵运吓,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我疯趟,道長(zhǎng)拘哨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任信峻,我火速辦了婚禮倦青,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘盹舞。我一直安慰自己产镐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開白布矾策。 她就那樣靜靜地躺著磷账,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贾虽。 梳的紋絲不亂的頭發(fā)上逃糟,一...
    開封第一講書人閱讀 51,215評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼绰咽。 笑死菇肃,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的取募。 我是一名探鬼主播琐谤,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼玩敏!你這毒婦竟也來了斗忌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤旺聚,失蹤者是張志新(化名)和其女友劉穎织阳,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體砰粹,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡唧躲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碱璃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弄痹。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖嵌器,靈堂內(nèi)的尸體忽然破棺而出肛真,到底是詐尸還是另有隱情,我是刑警寧澤嘴秸,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布毁欣,位于F島的核電站,受9級(jí)特大地震影響岳掐,放射性物質(zhì)發(fā)生泄漏凭疮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一串述、第九天 我趴在偏房一處隱蔽的房頂上張望执解。 院中可真熱鬧,春花似錦纲酗、人聲如沸衰腌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽右蕊。三九已至,卻和暖如春吮螺,著一層夾襖步出監(jiān)牢的瞬間饶囚,已是汗流浹背帕翻。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留萝风,地道東北人嘀掸。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像规惰,于是被迫代替她去往敵國(guó)和親睬塌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 引言 在介紹裝飾者模式之前歇万,我們先了解一個(gè)設(shè)計(jì)原則: 多用組合揩晴,少用繼承。 在平時(shí)寫代碼時(shí)贪磺,我們應(yīng)該減少類繼承的使...
    Zentopia閱讀 4,098評(píng)論 4 11
  • 代理模式 為其他類提供一個(gè)對(duì)象以控制這個(gè)對(duì)象的訪問缘挽。 這就有意思了,你可以如果你是代理呻粹,那么你可以增強(qiáng)你代理對(duì)象的...
    Myth52125閱讀 185評(píng)論 0 0
  • 一壕曼、定義 裝飾模式:動(dòng)態(tài)地給一個(gè)被裝飾者對(duì)象添加其他兄弟類一些額外的職責(zé),但是不改變被裝飾者類的功能等浊。就增加功能來...
    innovatorCL閱讀 332評(píng)論 0 0
  • 上篇文章提到了Context及其子類源碼分析(一)腮郊,這篇文章我們來講講Context及其子類用到的設(shè)計(jì)思想——裝飾...
    小阿拉閱讀 953評(píng)論 0 1
  • 我,不是大款筹燕,也不是大官轧飞, 我,沒有權(quán)勢(shì)撒踪,也沒有金錢过咬, 我只是一個(gè)平平凡凡的人, 如草一樣普通制妄,如水一樣清澈掸绞。 走...
    乮嵽閱讀 407評(píng)論 2 5