1 概述
在一個(gè)項(xiàng)目中,你會(huì)有非常多的因素考慮不到,特別是業(yè)務(wù)的變更,不時(shí)的冒出一個(gè)需求是很正常的情況。有三個(gè)繼承關(guān)系的類(lèi):Father、Son、GrandSon,我們要在Son類(lèi)上增強(qiáng)一些功能怎么辦?給Son類(lèi)增加方法嗎?那對(duì)GrandSon的影響呢?特別是對(duì)GrandSon有多個(gè)的情況,你會(huì)怎么辦?認(rèn)真看完本文,你會(huì)找到你的答案。JavaIO中,像下面的嵌套語(yǔ)句是不是很常見(jiàn),為什么要怎樣定義呢?理解裝飾模式后,你會(huì)找到答案。DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(“FileTest.Java”)));
2《設(shè)計(jì)模式之禪》中的例子
成績(jī)單需要父母簽名這事很多人都經(jīng)歷過(guò),這舉這樣一個(gè)例子:

代碼清單1 抽象成績(jī)單
//抽象成績(jī)單
public abstract class SchoolReport {
//展示成績(jī)情況
public abstract void report();
//家長(zhǎng)簽字
public abstract void sign(String name);
}
代碼清單2 四年級(jí)成績(jī)單
//四年級(jí)成績(jī)單
public class FouthGradeSchoolReport extends SchoolReport{
//我的成績(jī)單
public void report(){
//成績(jī)單的格式是這個(gè)樣子的
System.out.println("尊敬的XXX家長(zhǎng):");
System.out.println("······");
System.out.println("語(yǔ)文62 數(shù)學(xué)65 體育98 自然63");
System.out.println("······");
System.out.println(" 家長(zhǎng)簽字");
}
//家長(zhǎng)簽名
public void sign(String name){
System.out.println("家長(zhǎng)簽字為:" + name);
}
}
代碼清單3 老爸查看成績(jī)單
//老爸查看成績(jī)單
public class Father {
public static void main(String[] args) {
//把成績(jī)單拿過(guò)來(lái)
SchoolReport sr = new FouthGradeSchoolReport();
//看成績(jī)單
sr.report();
//簽名? 休想!
}
}
/*Output:
尊敬的XXX家長(zhǎng):
······
語(yǔ)文62 數(shù)學(xué)65 體育98 自然63
······
家長(zhǎng)簽字
*/
就這成績(jī)還要我簽字?!老爸就開(kāi)始找掃帚,我開(kāi)始做準(zhǔn)備:深呼吸,繃緊肌肉,提臀,收腹。 哈哈,幸運(yùn)的是,這個(gè)不是當(dāng)時(shí)的真實(shí)情況,我沒(méi)有直接把成績(jī)單交給老爸,而是在交給他之前做了點(diǎn)技術(shù)工作,我要把成績(jī)單封裝一下,封裝分類(lèi)兩步來(lái)實(shí)現(xiàn), 如下所示。
- 匯報(bào)最高成績(jī)
跟老爸說(shuō)各個(gè)科目的最高分,語(yǔ)文最高是75,數(shù)學(xué)是78,自然是80,然后老爸覺(jué)得我的成績(jī)與最高分?jǐn)?shù)相差不多,考的還是不錯(cuò)的嘛!這個(gè)是實(shí)情,但是不知道是什么原因,反正期末考試都考得不怎么樣,但是基本上都集中在70分以上,我這60多分基本上還是墊底的角色。 -
匯報(bào)排名情況
在老爸看完成績(jī)單后,告訴他我在全班排第38名,這個(gè)也是實(shí)情,為啥呢?有將近十個(gè)同學(xué)退學(xué)了! 這個(gè)情況我是不會(huì)說(shuō)的。 不知道是不是當(dāng)時(shí)第一次發(fā)成績(jī)單時(shí)學(xué)校沒(méi)有考慮清楚,沒(méi)有寫(xiě)上總共有多少同學(xué),排第幾名,反正是被我鉆了個(gè)空子。
那修飾是說(shuō)完了,我們看看類(lèi)圖如何修改,如下圖所示:
代碼清單4 修飾成績(jī)單
//修飾成績(jī)單
public class SugarFouthGradeSchoolReport extends FouthGradeSchoolReport {
// 首先要定義你要美化的方法, 先給老爸說(shuō)學(xué)校最高成績(jī)
private void reportHighScore() {
System.out.println("這次考試語(yǔ)文最高是75, 數(shù)學(xué)是78, 自然是80");
}
// 在老爸看完畢成績(jī)單后,我再匯報(bào)學(xué)校的排名情況
private void reportSort() {
System.out.println("我是排名第38名...");
}
// 由于匯報(bào)的內(nèi)容已經(jīng)發(fā)生變更,那所以要重寫(xiě)父類(lèi)
@Override
public void report() {
this.reportHighScore(); // 先說(shuō)最高成績(jī)
super.report(); // 然后老爸看成績(jī)單
this.reportSort(); // 然后告訴老爸學(xué)習(xí)學(xué)校排名
}
}
代碼清單5 老爸查看修飾后的成績(jī)單
public class Father2 {
public static void main(String[] args) {
// 把美化過(guò)的成績(jī)單拿過(guò)來(lái)
SchoolReport sr = new SugarFouthGradeSchoolReport();
// 看成績(jī)單
sr.report();
// 然后老爸, 一看, 很開(kāi)心, 就簽名了
sr.sign("老三"); // 我叫小三, 老爸當(dāng)然叫老三
}
}
/*
這次考試語(yǔ)文最高是75, 數(shù)學(xué)是78, 自然是80
尊敬的XXX家長(zhǎng):
······
語(yǔ)文62 數(shù)學(xué)65 體育98 自然63
······
家長(zhǎng)簽字
我是排名第38名...
家長(zhǎng)簽字為:老三
* */
通過(guò)繼承確實(shí)能夠解決這個(gè)問(wèn)題,老爸看成績(jī)單很開(kāi)心,然后就給簽字了,但現(xiàn)實(shí)的情況是很復(fù)雜的,可能老爸聽(tīng)我匯報(bào)最高成績(jī)后,就直接樂(lè)開(kāi)花了,直接簽名了,后面的排名就沒(méi)必要看了,或者老爸要先看排名情況,那怎么辦? 繼續(xù)擴(kuò)展?你能擴(kuò)展多少個(gè)類(lèi)?這還是一個(gè)比較簡(jiǎn)單的場(chǎng)景,一旦需要裝飾的條件非常多,比如20個(gè),你還通過(guò)繼承來(lái)解決,你想象的子類(lèi)有多少個(gè)? 你是不是馬上就要崩潰了!
好,你也看到通過(guò)繼承情況確實(shí)出現(xiàn)了問(wèn)題,類(lèi)爆炸,類(lèi)的數(shù)量激增,光寫(xiě)這些類(lèi)不累死你才怪,而且還要想想以后維護(hù)怎么辦,誰(shuí)愿意接收這么一大攤本質(zhì)相似的代碼維護(hù)工作?并且在面向?qū)ο蟮脑O(shè)計(jì)中,如果超過(guò)兩層繼承,你就應(yīng)該想想是不是出設(shè)計(jì)問(wèn)題了,是不是應(yīng)該重新找一條康莊大道了,這是經(jīng)驗(yàn)值,不是什么絕對(duì)的,繼承層次越多以后的維護(hù)成本越多,問(wèn)題這么多,那怎么辦?好辦,我們定義一批專(zhuān)門(mén)負(fù)責(zé)裝飾的類(lèi),然后根據(jù)實(shí)際情況來(lái)決定是否需要進(jìn)行裝飾,類(lèi)圖稍做修正,如圖17-4所示。

增加一個(gè)抽象類(lèi)和兩個(gè)實(shí)現(xiàn)類(lèi),其中Decorator的作用是封裝SchoolReport類(lèi),如果大家還記得代理模式,那么很容易看懂這個(gè)類(lèi)圖,裝飾類(lèi)的作用也就是一個(gè)特殊的代理類(lèi),真實(shí)的執(zhí)行者還是被代理的角色FouthGradeSchoolReport。
/**
* 抽象類(lèi),也是最核心的頂層對(duì)象
*/
public abstract class SchoolReport {
// 報(bào)告成績(jī)
public abstract void report();
// 家長(zhǎng)簽字
public abstract void sign(String name);
}
/**
* 最核心、最原始、最基本的接口或抽象類(lèi)的實(shí)現(xiàn),你要裝飾的就是它
*/
public class ConcreateSchoolReport extends SchoolReport {
@Override
public void report() {
System.out.println("報(bào)告家長(zhǎng),您的孩子成績(jī)不及格");
}
@Override
public void sign(String name) {
System.out.println("家長(zhǎng)簽名:" + name);
}
}
/**
* 裝飾的抽象類(lèi),這是一個(gè)特殊的代理類(lèi)(代理模式),具體執(zhí)行的方法還是由傳入進(jìn)來(lái)的SchoolReport(具體實(shí)現(xiàn)類(lèi))決定
*/
public abstract class Decorator extends SchoolReport {
private SchoolReport schoolReport;
// 構(gòu)造函數(shù),傳遞SchoolReport對(duì)象并保存在私有屬性中
public Decorator(SchoolReport schoolReport){
this.schoolReport = schoolReport;
}
// 具體執(zhí)行的report方法由傳進(jìn)來(lái)的SchoolReport決定
public void report() {
this.schoolReport.report();
}
public void sign(String name) {
this.schoolReport.sign(name);
}
}
/**
* 具體的裝飾類(lèi),就是由他進(jìn)行對(duì)象的裝飾
*/
public class HighScoreDecorator extends Decorator {
// 由于父類(lèi)顯示的定義了構(gòu)造方法,這里也必須顯示定義
public HighScoreDecorator(SchoolReport schoolReport) {
super(schoolReport);
}
// 自定義的修飾方法,此方法用于修飾具體的SchoolReport對(duì)象ConcreateSchoolReport
public void highCore(){
System.out.println("其實(shí)大家都不及格,你兒子分算高的");
}
//重寫(xiě)report方法,將自定義方法放在父類(lèi)的report方法執(zhí)行之前執(zhí)行,用于修飾
@Override
public void report() {
highCore();
super.report();
}
}
public class OrderSchoolReport extends Decorator {
// 由于父類(lèi)顯示的定義了構(gòu)造方法,這里也必須顯示定義
public OrderSchoolReport(SchoolReport schoolReport) {
super(schoolReport);
}
// 自定義的修飾方法,此方法用于修飾具體的SchoolReport對(duì)象ConcreateSchoolReport
public void order(){
System.out.println("而且你兒子的分?jǐn)?shù)排名全班第一");
}
//重寫(xiě)report方法,將自定義方法放在父類(lèi)的report方法執(zhí)行之前執(zhí)行,用于修飾
@Override
public void report() {
order();
super.report();
}
}
/**
* 客戶(hù)端調(diào)用
*/
public class Client {
public static void main(String[] args) {
SchoolReport report = new ConcreateSchoolReport();
report = new HighScoreDecorator(report);
report = new OrderSchoolReport(report);
report.report();
report.sign("NB");
}
}
3 裝飾模式的定義
裝飾模式(Decorator Pattern)是一種比較常見(jiàn)的模式,其定義如下:Attach additionalresponsibilities to an object dynamically keeping the same interface.Decoratorsprovide a flexible alternative to subclassing for extending functionality.( 動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。就增加功能來(lái)說(shuō),裝飾模式相比生成子類(lèi)更為靈活。)
裝飾模式的通用類(lèi)圖如圖5所示。

在類(lèi)圖中,有四個(gè)角色需要說(shuō)明:
- Component抽象構(gòu)件
Component是一個(gè)接口或者是抽象類(lèi),就是定義我們最核心的對(duì)象,也就是最原始的對(duì)象,如上面的成績(jī)單。
注意 在裝飾模式中,必然有一個(gè)最基本、最核心、最原始的接口或抽象類(lèi)充當(dāng)Component抽象構(gòu)件。 - ConcreteComponent 具體構(gòu)件
ConcreteComponent是最核心、最原始、最基本的接口或抽象類(lèi)的實(shí)現(xiàn),你要裝飾的就是它。 - Decorator裝飾角色
一般是一個(gè)抽象類(lèi),做什么用呢?實(shí)現(xiàn)接口或者抽象方法,它里面可不一定有抽象的方法呀,在它的屬性里必然有一個(gè)private變量指向Component抽象構(gòu)件。 - 具體裝飾角色
ConcreteDecoratorA和ConcreteDecoratorB是兩個(gè)具體的裝飾類(lèi),你要把你最核心的、最原始的、最基本的東西裝飾成其他東西,上面的例子就是把一個(gè)比較平庸的成績(jī)單裝飾成家長(zhǎng)認(rèn)可的成績(jī)單。
理解client中的new
public class Client {
public static void main(String[] args) {
//這里創(chuàng)建了一個(gè)ConcreateComponent對(duì)象,返回對(duì)象的引用component
Component component = new ConcreateComponent();
//這里將上述對(duì)象傳入ConcreateDecoratorA中,通過(guò)其構(gòu)造方法將component傳入到Decorato中
//對(duì)象:一個(gè)Decorato對(duì)象,其中私有屬性component保存為ConcreateComponent
// 一個(gè)ConcreateDecoratorA對(duì)象component
component = new ConcreateDecoratorA(component);
//這里將ConcreateDecoratorA傳入ConcreateDecoratorB中,通過(guò)其構(gòu)造方法將其傳入到Decorato中
//對(duì)象:一個(gè)Decorato對(duì)象,其中私有屬性component保存為ConcreateDecoratorA
// 一個(gè)ConcreateDecoratorB對(duì)象component
component = new ConcreateDecoratorB(component);
//先執(zhí)行ConcreateDecoratorB中的operate方法,再執(zhí)行其父類(lèi)Decorato中的operate,注意此時(shí)的Decorato方法operate會(huì)調(diào)用ConcreateDecoratorA中的operate,
//該方法又會(huì)調(diào)用ConcreateComponent中的operate方法,故只需要在ConcreateDecoratorA、B兩個(gè)修飾類(lèi)的operate中加上具體修飾的方法即可完成鏈?zhǔn)降恼{(diào)用
component.operate();
}
}
4 半透明的裝飾模式
4.1 裝飾模式的簡(jiǎn)化
如果只有一個(gè)ConcreteComponent類(lèi),那么可以考慮去掉抽象的Component類(lèi)(接口),把Decorator作為一個(gè)ConcreteComponent子類(lèi)。如下圖所示:

如果只有一個(gè)ConcreteDecorator類(lèi),那么就沒(méi)有必要建立一個(gè)單獨(dú)的Decorator類(lèi),而可以把Decorator和ConcreteDecorator的責(zé)任合并成一個(gè)類(lèi)。甚至在只有兩個(gè)ConcreteDecorator類(lèi)的情況下,都可以這樣做。如下圖所示

4.2 透明性的要求
裝飾模式對(duì)客戶(hù)端的透明性要求程序不要聲明一個(gè)ConcreteComponent類(lèi)型的變量,而應(yīng)當(dāng)聲明一個(gè)Component類(lèi)型的變量。用上面成績(jī)單的例子來(lái)說(shuō)有:SchoolReport sr;sr = new FouthGradeSchoolReport();sr = new HighScoreDecorator(sr);sr = new SortDecorator(sr);而下面的做法是不對(duì)的:HighScoreDecorator hd = new HighScoreDecorator(sr);SortDecorator sd = new SortDecorator(sr);
4.3 半透明的裝飾模式
然而,純粹的裝飾模式很難找到。裝飾模式的用意是在不改變接口的前提下,增強(qiáng)所考慮的類(lèi)的性能。在增強(qiáng)性能的時(shí)候,往往需要建立新的公開(kāi)的方法。上面成績(jī)單的例子中,顯示前十名學(xué)生信息。這就意味著SortDecorator類(lèi)中應(yīng)當(dāng)有一個(gè)新的displayTopTen()方法。再比如,顯示顯示各科最高分學(xué)生信息,這就意味著在HighScoreDecorator類(lèi)里應(yīng)當(dāng)有一個(gè)新的showTop()方法。這就導(dǎo)致了大多數(shù)的裝飾模式的實(shí)現(xiàn)都是“半透明”的,而不是完全透明的。換言之,允許裝飾模式改變接口,增加新的方法。這意味著客戶(hù)端可以聲明ConcreteDecorator類(lèi)型的變量,從而可以調(diào)用ConcreteDecorator類(lèi)中才有的方法:SchoolReport sr = new SortDecorator();SortDecorator sd = new SortDecorator();sd.displayTopTen();半透明的裝飾模式是介于裝飾模式和適配器模式之間的。適配器模式的用意是改變所考慮的類(lèi)的接口,也可以通過(guò)改寫(xiě)一個(gè)或幾個(gè)方法,或增加新的方法來(lái)增強(qiáng)或改變所考慮的類(lèi)的功能。大多數(shù)的裝飾模式實(shí)際上是半透明的裝飾模式,這樣的裝飾模式也稱(chēng)做半裝飾、半適配器模式。裝飾模式和適配器模式都是“包裝模式(Wrapper Pattern)”,它們都是通過(guò)封裝其他對(duì)象達(dá)到設(shè)計(jì)的目的的,但是它們的形態(tài)有很大區(qū)別。理想的裝飾模式在對(duì)被裝飾對(duì)象進(jìn)行功能增強(qiáng)的同時(shí),要求具體構(gòu)件角色、裝飾角色的接口與抽象構(gòu)件角色的接口完全一致。而適配器模式則不然,一般而言,適配器模式并不要求對(duì)源對(duì)象的功能進(jìn)行增強(qiáng),但是會(huì)改變?cè)磳?duì)象的接口,以便和目標(biāo)接口相符合。裝飾模式有透明和半透明兩種,這兩種的區(qū)別就在于裝飾角色的接口與抽象構(gòu)件角色的接口是否完全一致。透明的裝飾模式也就是理想的裝飾模式,要求具體構(gòu)件角色、裝飾角色的接口與抽象構(gòu)件角色的接口完全一致。相反,如果裝飾角色的接口與抽象構(gòu)件角色接口不一致,也就是說(shuō)裝飾角色的接口比抽象構(gòu)件角色的接口寬的話(huà),裝飾角色實(shí)際上已經(jīng)成了一個(gè)適配器角色,這種裝飾模式也是可以接受的,稱(chēng)為“半透明”的裝飾模式,如下圖所示。

在適配器模式里面,適配器類(lèi)的接口通常會(huì)與目標(biāo)類(lèi)的接口重疊,但往往并不完全相同。換言之,適配器類(lèi)的接口會(huì)比被裝飾的目標(biāo)類(lèi)接口寬。顯然,半透明的裝飾模式實(shí)際上就是處于適配器模式與裝飾模式之間的灰色地帶。如果將裝飾模式與適配器模式合并成為一個(gè)“包裝模式”的話(huà),那么半透明的裝飾模式倒可以成為這種合并后的“包裝模式”的代表。
5 裝飾模式應(yīng)用
5.1 裝飾模式的優(yōu)點(diǎn)
裝飾類(lèi)和被裝飾類(lèi)可以獨(dú)立發(fā)展,而不會(huì)相互耦合。換句話(huà)說(shuō),Component類(lèi)無(wú)須知道Decorator類(lèi),Decorator類(lèi)是從外部來(lái)擴(kuò)展Component類(lèi)的功能,而Decorator也不用知道具體的構(gòu)件。
裝飾模式是繼承關(guān)系的一個(gè)替代方案。我們看裝飾類(lèi)Decorator,不管裝飾多少層,返回的對(duì)象還是Component,實(shí)現(xiàn)的還是is-a的關(guān)系。
裝飾模式與繼承關(guān)系的目的都是要擴(kuò)展對(duì)象的功能,但是裝飾模式可以提供比繼承更多的靈活性。裝飾模式允許系統(tǒng)動(dòng)態(tài)決定“貼上”一個(gè)需要的“裝飾”,或者除掉一個(gè)不需要的“裝飾”。繼承關(guān)系則不同,繼承關(guān)系是靜態(tài)的,它在系統(tǒng)運(yùn)行前就決定了。
過(guò)使用不同的具體裝飾類(lèi)以及這些裝飾類(lèi)的排列組合,設(shè)計(jì)師可以創(chuàng)造出很多不同行為的組合。
5.2 裝飾模式的缺點(diǎn)
由于使用裝飾模式,可以比使用繼承關(guān)系需要較少數(shù)目的類(lèi)。使用較少的類(lèi),當(dāng)然使設(shè)計(jì)比較易于進(jìn)行。但是,在另一方面,使用裝飾模式會(huì)產(chǎn)生比使用繼承關(guān)系更多的對(duì)象。更多的對(duì)象會(huì)使得查錯(cuò)變得困難,特別是這些對(duì)象看上去都很相像。
多層的裝飾是比較復(fù)雜的。
5.3 裝飾模式的使用場(chǎng)景
需要擴(kuò)展一個(gè)類(lèi)的功能,或給一個(gè)類(lèi)增加附加功能。
需要?jiǎng)討B(tài)地給一個(gè)對(duì)象增加功能,這些功能可以再動(dòng)態(tài)地撤銷(xiāo)。
需要為一批的兄弟類(lèi)進(jìn)行改裝或加裝功能,當(dāng)然是首選裝飾模式。
裝飾模式在Java語(yǔ)言中的最著名的應(yīng)用:Java I/O標(biāo)準(zhǔn)庫(kù)的設(shè)計(jì)了
