三、裝飾者模式(Decorator)

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

本章目錄如下:

????????一、階段一

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

? ??????三、階段三

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

????????五、模式問(wèn)答

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

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

一、階段一

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

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

? ? ? ? ? ? 1、代碼復(fù)用度很低,幾乎沒(méi)有。

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

二、階段二

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

? ? 缺點(diǎn):只能適應(yīng)部分變化,如調(diào)料價(jià)格變化;但是有些變化不適應(yīng),如創(chuàng)造新調(diào)制方式的咖啡時(shí)不能復(fù)用代碼。最終要的是違反了“開(kāi)放—關(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ò)展。即通過(guò)動(dòng)態(tài)地組合對(duì)象,可以通過(guò)寫新代碼來(lái)添加新功能,而無(wú)須修改現(xiàn)有代碼。這樣就沒(méi)有改變現(xiàn)有代碼,那么引進(jìn)bug或產(chǎn)生意外副作用的機(jī)會(huì)將大幅度減少。

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

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

? ? 何為變化?從不同緯度看增、刪、改、查!

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

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

階段三的類圖如下:

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

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

? ? 下面說(shuō)明階段三的代碼:

======================類圖中的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中最常見(jiàn)的裝飾者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è)。但是,可以通過(guò)工廠模式(Factory)/生成器模式(Builder)封裝裝飾者模式的創(chuàng)建過(guò)程來(lái)解決該問(wèn)題。

下面我們編寫一個(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();

????????}

????}

}


五、模式問(wèn)答

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

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

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

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

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

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

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

設(shè)計(jì)原則1:找出應(yīng)用中可能需要變化之處,把它們獨(dú)立出來(lái),不要和那些不需要變化 T個(gè)T的代碼混在一起。該設(shè)計(jì)原則作用: “把會(huì)變化的部分取出并封裝起來(lái),以便以后可以輕易地改動(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ì)而努力??偨Y(jié)為“多用組合少用繼承”目前我見(jiàn)過(guò)兩種組合方式:實(shí)例作為另外實(shí)例的屬性,如策略模式、裝飾者模式;實(shí)例作為另外實(shí)例的集合屬性的成員,如觀察者模式

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容