1 概述
在一個項目中企锌,你會有非常多的因素考慮不到衅檀,特別是業(yè)務(wù)的變更,不時的冒出一個需求是很正常的情況霎俩。有三個繼承關(guān)系的類:Father哀军、Son、GrandSon打却,我們要在Son類上增強(qiáng)一些功能怎么辦杉适?給Son類增加方法嗎?那對GrandSon的影響呢柳击?特別是對GrandSon有多個的情況猿推,你會怎么辦?認(rèn)真看完本文,你會找到你的答案蹬叭。JavaIO中藕咏,像下面的嵌套語句是不是很常見,為什么要怎樣定義呢秽五?理解裝飾模式后孽查,你會找到答案。DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(“FileTest.Java”)));
2《設(shè)計模式之禪》中的例子
成績單需要父母簽名這事很多人都經(jīng)歷過坦喘,這舉這樣一個例子:
代碼清單1 抽象成績單
//抽象成績單
public abstract class SchoolReport {
//展示成績情況
public abstract void report();
//家長簽字
public abstract void sign(String name);
}
代碼清單2 四年級成績單
//四年級成績單
public class FouthGradeSchoolReport extends SchoolReport{
//我的成績單
public void report(){
//成績單的格式是這個樣子的
System.out.println("尊敬的XXX家長:");
System.out.println("······");
System.out.println("語文62 數(shù)學(xué)65 體育98 自然63");
System.out.println("······");
System.out.println(" 家長簽字");
}
//家長簽名
public void sign(String name){
System.out.println("家長簽字為:" + name);
}
}
代碼清單3 老爸查看成績單
//老爸查看成績單
public class Father {
public static void main(String[] args) {
//把成績單拿過來
SchoolReport sr = new FouthGradeSchoolReport();
//看成績單
sr.report();
//簽名盲再? 休想!
}
}
/*Output:
尊敬的XXX家長:
······
語文62 數(shù)學(xué)65 體育98 自然63
······
家長簽字
*/
就這成績還要我簽字瓣铣?答朋!老爸就開始找掃帚,我開始做準(zhǔn)備:深呼吸棠笑,繃緊肌肉梦碗,提臀,收腹蓖救。 哈哈叉弦,幸運(yùn)的是,這個不是當(dāng)時的真實情況藻糖,我沒有直接把成績單交給老爸淹冰,而是在交給他之前做了點技術(shù)工作,我要把成績單封裝一下巨柒,封裝分類兩步來實現(xiàn)樱拴, 如下所示。
- 匯報最高成績
跟老爸說各個科目的最高分洋满,語文最高是75晶乔,數(shù)學(xué)是78,自然是80牺勾,然后老爸覺得我的成績與最高分?jǐn)?shù)相差不多正罢,考的還是不錯的嘛!這個是實情驻民,但是不知道是什么原因翻具,反正期末考試都考得不怎么樣,但是基本上都集中在70分以上回还,我這60多分基本上還是墊底的角色裆泳。 -
匯報排名情況
在老爸看完成績單后,告訴他我在全班排第38名柠硕,這個也是實情工禾,為啥呢?有將近十個同學(xué)退學(xué)了! 這個情況我是不會說的闻葵。 不知道是不是當(dāng)時第一次發(fā)成績單時學(xué)校沒有考慮清楚民泵,沒有寫上總共有多少同學(xué),排第幾名槽畔,反正是被我鉆了個空子栈妆。
那修飾是說完了,我們看看類圖如何修改竟痰,如下圖所示:
代碼清單4 修飾成績單
//修飾成績單
public class SugarFouthGradeSchoolReport extends FouthGradeSchoolReport {
// 首先要定義你要美化的方法签钩, 先給老爸說學(xué)校最高成績
private void reportHighScore() {
System.out.println("這次考試語文最高是75掏呼, 數(shù)學(xué)是78坏快, 自然是80");
}
// 在老爸看完畢成績單后,我再匯報學(xué)校的排名情況
private void reportSort() {
System.out.println("我是排名第38名...");
}
// 由于匯報的內(nèi)容已經(jīng)發(fā)生變更憎夷,那所以要重寫父類
@Override
public void report() {
this.reportHighScore(); // 先說最高成績
super.report(); // 然后老爸看成績單
this.reportSort(); // 然后告訴老爸學(xué)習(xí)學(xué)校排名
}
}
代碼清單5 老爸查看修飾后的成績單
public class Father2 {
public static void main(String[] args) {
// 把美化過的成績單拿過來
SchoolReport sr = new SugarFouthGradeSchoolReport();
// 看成績單
sr.report();
// 然后老爸莽鸿, 一看, 很開心拾给, 就簽名了
sr.sign("老三"); // 我叫小三祥得, 老爸當(dāng)然叫老三
}
}
/*
這次考試語文最高是75, 數(shù)學(xué)是78蒋得, 自然是80
尊敬的XXX家長:
······
語文62 數(shù)學(xué)65 體育98 自然63
······
家長簽字
我是排名第38名...
家長簽字為:老三
* */
通過繼承確實能夠解決這個問題级及,老爸看成績單很開心,然后就給簽字了额衙,但現(xiàn)實的情況是很復(fù)雜的饮焦,可能老爸聽我匯報最高成績后,就直接樂開花了窍侧,直接簽名了县踢,后面的排名就沒必要看了,或者老爸要先看排名情況伟件,那怎么辦硼啤? 繼續(xù)擴(kuò)展?你能擴(kuò)展多少個類斧账?這還是一個比較簡單的場景谴返,一旦需要裝飾的條件非常多,比如20個咧织,你還通過繼承來解決亏镰,你想象的子類有多少個? 你是不是馬上就要崩潰了拯爽!
好索抓,你也看到通過繼承情況確實出現(xiàn)了問題,類爆炸,類的數(shù)量激增逼肯,光寫這些類不累死你才怪耸黑,而且還要想想以后維護(hù)怎么辦,誰愿意接收這么一大攤本質(zhì)相似的代碼維護(hù)工作篮幢?并且在面向?qū)ο蟮脑O(shè)計中大刊,如果超過兩層繼承,你就應(yīng)該想想是不是出設(shè)計問題了三椿,是不是應(yīng)該重新找一條康莊大道了缺菌,這是經(jīng)驗值,不是什么絕對的搜锰,繼承層次越多以后的維護(hù)成本越多伴郁,問題這么多,那怎么辦蛋叼?好辦焊傅,我們定義一批專門負(fù)責(zé)裝飾的類,然后根據(jù)實際情況來決定是否需要進(jìn)行裝飾狈涮,類圖稍做修正狐胎,如圖17-4所示。
增加一個抽象類和兩個實現(xiàn)類歌馍,其中Decorator的作用是封裝SchoolReport類握巢,如果大家還記得代理模式,那么很容易看懂這個類圖松却,裝飾類的作用也就是一個特殊的代理類暴浦,真實的執(zhí)行者還是被代理的角色FouthGradeSchoolReport。
/**
* 抽象類玻褪,也是最核心的頂層對象
*/
public abstract class SchoolReport {
// 報告成績
public abstract void report();
// 家長簽字
public abstract void sign(String name);
}
/**
* 最核心肉渴、最原始、最基本的接口或抽象類的實現(xiàn)带射,你要裝飾的就是它
*/
public class ConcreateSchoolReport extends SchoolReport {
@Override
public void report() {
System.out.println("報告家長同规,您的孩子成績不及格");
}
@Override
public void sign(String name) {
System.out.println("家長簽名:" + name);
}
}
/**
* 裝飾的抽象類,這是一個特殊的代理類(代理模式)窟社,具體執(zhí)行的方法還是由傳入進(jìn)來的SchoolReport(具體實現(xiàn)類)決定
*/
public abstract class Decorator extends SchoolReport {
private SchoolReport schoolReport;
// 構(gòu)造函數(shù)券勺,傳遞SchoolReport對象并保存在私有屬性中
public Decorator(SchoolReport schoolReport){
this.schoolReport = schoolReport;
}
// 具體執(zhí)行的report方法由傳進(jìn)來的SchoolReport決定
public void report() {
this.schoolReport.report();
}
public void sign(String name) {
this.schoolReport.sign(name);
}
}
/**
* 具體的裝飾類,就是由他進(jìn)行對象的裝飾
*/
public class HighScoreDecorator extends Decorator {
// 由于父類顯示的定義了構(gòu)造方法灿里,這里也必須顯示定義
public HighScoreDecorator(SchoolReport schoolReport) {
super(schoolReport);
}
// 自定義的修飾方法关炼,此方法用于修飾具體的SchoolReport對象ConcreateSchoolReport
public void highCore(){
System.out.println("其實大家都不及格,你兒子分算高的");
}
//重寫report方法匣吊,將自定義方法放在父類的report方法執(zhí)行之前執(zhí)行儒拂,用于修飾
@Override
public void report() {
highCore();
super.report();
}
}
public class OrderSchoolReport extends Decorator {
// 由于父類顯示的定義了構(gòu)造方法寸潦,這里也必須顯示定義
public OrderSchoolReport(SchoolReport schoolReport) {
super(schoolReport);
}
// 自定義的修飾方法,此方法用于修飾具體的SchoolReport對象ConcreateSchoolReport
public void order(){
System.out.println("而且你兒子的分?jǐn)?shù)排名全班第一");
}
//重寫report方法社痛,將自定義方法放在父類的report方法執(zhí)行之前執(zhí)行见转,用于修飾
@Override
public void report() {
order();
super.report();
}
}
/**
* 客戶端調(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)是一種比較常見的模式,其定義如下:Attach additionalresponsibilities to an object dynamically keeping the same interface.Decoratorsprovide a flexible alternative to subclassing for extending functionality.( 動態(tài)地給一個對象添加一些額外的職責(zé)蒜哀。就增加功能來說斩箫,裝飾模式相比生成子類更為靈活。)
裝飾模式的通用類圖如圖5所示撵儿。
在類圖中乘客,有四個角色需要說明:
- Component抽象構(gòu)件
Component是一個接口或者是抽象類,就是定義我們最核心的對象淀歇,也就是最原始的對象易核,如上面的成績單。
注意 在裝飾模式中房匆,必然有一個最基本耸成、最核心报亩、最原始的接口或抽象類充當(dāng)Component抽象構(gòu)件浴鸿。 - ConcreteComponent 具體構(gòu)件
ConcreteComponent是最核心、最原始弦追、最基本的接口或抽象類的實現(xiàn)岳链,你要裝飾的就是它。 - Decorator裝飾角色
一般是一個抽象類劲件,做什么用呢掸哑?實現(xiàn)接口或者抽象方法,它里面可不一定有抽象的方法呀零远,在它的屬性里必然有一個private變量指向Component抽象構(gòu)件苗分。 - 具體裝飾角色
ConcreteDecoratorA和ConcreteDecoratorB是兩個具體的裝飾類,你要把你最核心的牵辣、最原始的摔癣、最基本的東西裝飾成其他東西,上面的例子就是把一個比較平庸的成績單裝飾成家長認(rèn)可的成績單纬向。
理解client中的new
public class Client {
public static void main(String[] args) {
//這里創(chuàng)建了一個ConcreateComponent對象择浊,返回對象的引用component
Component component = new ConcreateComponent();
//這里將上述對象傳入ConcreateDecoratorA中,通過其構(gòu)造方法將component傳入到Decorato中
//對象:一個Decorato對象逾条,其中私有屬性component保存為ConcreateComponent
// 一個ConcreateDecoratorA對象component
component = new ConcreateDecoratorA(component);
//這里將ConcreateDecoratorA傳入ConcreateDecoratorB中琢岩,通過其構(gòu)造方法將其傳入到Decorato中
//對象:一個Decorato對象,其中私有屬性component保存為ConcreateDecoratorA
// 一個ConcreateDecoratorB對象component
component = new ConcreateDecoratorB(component);
//先執(zhí)行ConcreateDecoratorB中的operate方法师脂,再執(zhí)行其父類Decorato中的operate担孔,注意此時的Decorato方法operate會調(diào)用ConcreateDecoratorA中的operate江锨,
//該方法又會調(diào)用ConcreateComponent中的operate方法,故只需要在ConcreateDecoratorA糕篇、B兩個修飾類的operate中加上具體修飾的方法即可完成鏈?zhǔn)降恼{(diào)用
component.operate();
}
}
4 半透明的裝飾模式
4.1 裝飾模式的簡化
如果只有一個ConcreteComponent類泳桦,那么可以考慮去掉抽象的Component類(接口),把Decorator作為一個ConcreteComponent子類娩缰。如下圖所示:
如果只有一個ConcreteDecorator類灸撰,那么就沒有必要建立一個單獨的Decorator類,而可以把Decorator和ConcreteDecorator的責(zé)任合并成一個類拼坎。甚至在只有兩個ConcreteDecorator類的情況下浮毯,都可以這樣做。如下圖所示
4.2 透明性的要求
裝飾模式對客戶端的透明性要求程序不要聲明一個ConcreteComponent類型的變量泰鸡,而應(yīng)當(dāng)聲明一個Component類型的變量债蓝。用上面成績單的例子來說有:SchoolReport sr;sr = new FouthGradeSchoolReport();sr = new HighScoreDecorator(sr);sr = new SortDecorator(sr);而下面的做法是不對的:HighScoreDecorator hd = new HighScoreDecorator(sr);SortDecorator sd = new SortDecorator(sr);
4.3 半透明的裝飾模式
然而,純粹的裝飾模式很難找到盛龄。裝飾模式的用意是在不改變接口的前提下饰迹,增強(qiáng)所考慮的類的性能。在增強(qiáng)性能的時候余舶,往往需要建立新的公開的方法啊鸭。上面成績單的例子中,顯示前十名學(xué)生信息匿值。這就意味著SortDecorator類中應(yīng)當(dāng)有一個新的displayTopTen()方法赠制。再比如,顯示顯示各科最高分學(xué)生信息挟憔,這就意味著在HighScoreDecorator類里應(yīng)當(dāng)有一個新的showTop()方法钟些。這就導(dǎo)致了大多數(shù)的裝飾模式的實現(xiàn)都是“半透明”的,而不是完全透明的绊谭。換言之政恍,允許裝飾模式改變接口,增加新的方法达传。這意味著客戶端可以聲明ConcreteDecorator類型的變量篙耗,從而可以調(diào)用ConcreteDecorator類中才有的方法:SchoolReport sr = new SortDecorator();SortDecorator sd = new SortDecorator();sd.displayTopTen();半透明的裝飾模式是介于裝飾模式和適配器模式之間的。適配器模式的用意是改變所考慮的類的接口趟大,也可以通過改寫一個或幾個方法鹤树,或增加新的方法來增強(qiáng)或改變所考慮的類的功能。大多數(shù)的裝飾模式實際上是半透明的裝飾模式逊朽,這樣的裝飾模式也稱做半裝飾罕伯、半適配器模式。裝飾模式和適配器模式都是“包裝模式(Wrapper Pattern)”叽讳,它們都是通過封裝其他對象達(dá)到設(shè)計的目的的追他,但是它們的形態(tài)有很大區(qū)別坟募。理想的裝飾模式在對被裝飾對象進(jìn)行功能增強(qiáng)的同時,要求具體構(gòu)件角色邑狸、裝飾角色的接口與抽象構(gòu)件角色的接口完全一致懈糯。而適配器模式則不然,一般而言单雾,適配器模式并不要求對源對象的功能進(jìn)行增強(qiáng)赚哗,但是會改變源對象的接口,以便和目標(biāo)接口相符合硅堆。裝飾模式有透明和半透明兩種屿储,這兩種的區(qū)別就在于裝飾角色的接口與抽象構(gòu)件角色的接口是否完全一致。透明的裝飾模式也就是理想的裝飾模式渐逃,要求具體構(gòu)件角色够掠、裝飾角色的接口與抽象構(gòu)件角色的接口完全一致。相反茄菊,如果裝飾角色的接口與抽象構(gòu)件角色接口不一致疯潭,也就是說裝飾角色的接口比抽象構(gòu)件角色的接口寬的話,裝飾角色實際上已經(jīng)成了一個適配器角色面殖,這種裝飾模式也是可以接受的竖哩,稱為“半透明”的裝飾模式,如下圖所示畜普。
在適配器模式里面期丰,適配器類的接口通常會與目標(biāo)類的接口重疊群叶,但往往并不完全相同吃挑。換言之,適配器類的接口會比被裝飾的目標(biāo)類接口寬街立。顯然舶衬,半透明的裝飾模式實際上就是處于適配器模式與裝飾模式之間的灰色地帶。如果將裝飾模式與適配器模式合并成為一個“包裝模式”的話赎离,那么半透明的裝飾模式倒可以成為這種合并后的“包裝模式”的代表逛犹。
5 裝飾模式應(yīng)用
5.1 裝飾模式的優(yōu)點
裝飾類和被裝飾類可以獨立發(fā)展,而不會相互耦合梁剔。換句話說虽画,Component類無須知道Decorator類,Decorator類是從外部來擴(kuò)展Component類的功能荣病,而Decorator也不用知道具體的構(gòu)件码撰。
裝飾模式是繼承關(guān)系的一個替代方案。我們看裝飾類Decorator个盆,不管裝飾多少層脖岛,返回的對象還是Component朵栖,實現(xiàn)的還是is-a的關(guān)系。
裝飾模式與繼承關(guān)系的目的都是要擴(kuò)展對象的功能柴梆,但是裝飾模式可以提供比繼承更多的靈活性陨溅。裝飾模式允許系統(tǒng)動態(tài)決定“貼上”一個需要的“裝飾”,或者除掉一個不需要的“裝飾”绍在。繼承關(guān)系則不同门扇,繼承關(guān)系是靜態(tài)的,它在系統(tǒng)運(yùn)行前就決定了偿渡。
過使用不同的具體裝飾類以及這些裝飾類的排列組合悯嗓,設(shè)計師可以創(chuàng)造出很多不同行為的組合。
5.2 裝飾模式的缺點
由于使用裝飾模式卸察,可以比使用繼承關(guān)系需要較少數(shù)目的類脯厨。使用較少的類,當(dāng)然使設(shè)計比較易于進(jìn)行坑质。但是合武,在另一方面,使用裝飾模式會產(chǎn)生比使用繼承關(guān)系更多的對象涡扼。更多的對象會使得查錯變得困難稼跳,特別是這些對象看上去都很相像。
多層的裝飾是比較復(fù)雜的吃沪。
5.3 裝飾模式的使用場景
需要擴(kuò)展一個類的功能汤善,或給一個類增加附加功能。
需要動態(tài)地給一個對象增加功能票彪,這些功能可以再動態(tài)地撤銷红淡。
需要為一批的兄弟類進(jìn)行改裝或加裝功能,當(dāng)然是首選裝飾模式降铸。
裝飾模式在Java語言中的最著名的應(yīng)用:Java I/O標(biāo)準(zhǔn)庫的設(shè)計了