-
罪惡的成績單
考試成績單以及成績排名,大家都懂得,以前上學(xué)的時候,這玩意往家里寄是真的要命。成績單還需要家長簽字,那么,我們來模擬將成績單給家長簽字這一情景,如圖17-1:

我們先來看成績單的抽象類SchoolReport,代碼如下:
public abstract class SchoolReport {
//顯示成績情況
public abstract void report();
//要家長簽字
public abstract void sign();
}
有抽象類了,在來看看具體的四年級成績單FouthGradeSchoolReport,代碼如下:
public class FouthGradeSchoolReport extends SchoolReport {
@Override
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("家長簽名:");
}
@Override
public void sign(String name) {
System.out.println("家長簽名:" + name);
}
}
成績單出來62、65之類的在小學(xué)基本就是墊底,那么我們把成績單帶回去給家長看,修改一下類圖,如圖17-2:

這是原裝的成績單,沒有動過的,來看Father類代碼如下:
public class Father {
public static void main(String[] args) {
SchoolReport sc = new FouthGradeSchoolReport();
//看成績單
sc.report();
//簽名?休想,一頓胖揍
}
}
這個成績直接拿出來肯定是找打,我們把成績單封裝一下,封裝分為兩步來實(shí)現(xiàn):
- 匯報最高成績
跟家長說各個科目的最高分,語文最高75,數(shù)學(xué)最高78,自然是80;那么家長一看你的成績和最高分差不多,也還行;實(shí)際普遍在70以上,這個60多還是墊底 -
匯報排名
全班排第38名,但是不能說參加考試的只有40個人,反正成績單上沒寫多少人,相比較之前的40多名還是有進(jìn)步的,哈哈
這就是加上了一些修飾,我們看看類圖如何修改,如圖17-3:
17-3
這應(yīng)該是常規(guī)的處理方式了,直接增加一個子類,覆寫report方法,我們看具體的實(shí)現(xiàn),代碼如下:
public class SugarFouthGradeSchoolReport extends FouthGradeSchoolReport {
private void reportHighScore(){
System.out.println("這次考試語文最高是75,數(shù)學(xué)是78,自然是80");
}
private void reportSort(){
System.out.println("我是排名第38名。。。");
}
@Override
public void report(){
this.reportHighScore();
super.report();
this.reportSort();
}
}
通過繼承確實(shí)能夠解決這個問題,但是這個只能作為應(yīng)急的一種方式,如果需要繼續(xù)加裝飾那怎么辦,而且裝飾條件不是一次就明確的,可能過一段時間加一個,那一直通過繼承來處理肯定是不合理的。在面向?qū)ο蟮脑O(shè)計中,如果超過兩層,是不是該考慮設(shè)計的問題了,這是經(jīng)驗(yàn),不是絕對,繼承的層次越多以后的維護(hù)成本越多,那這么解決這個裝飾的問題呢,我們專門定義一批負(fù)責(zé)裝飾的類,然后根據(jù)實(shí)際情況來決定是否需要進(jìn)行裝飾,類圖如17-4:

增加一個抽象類和兩個實(shí)現(xiàn)類,其中Decorator的作用是封裝SchoolReport類,如果大家還記得代理模式,那么很容易看懂這個類圖,裝飾類的作用也是一個特殊的代理類,真實(shí)的執(zhí)行者還是被代理的角色FouthGradeSchoolReport,代碼如下:
public abstract class Decorator extends SchoolReport {
//首先得知道是哪個成績單
private SchoolReport schoolReport;
public Decorator(SchoolReport schoolReport){
this.schoolReport = schoolReport;
}
@Override
public void report(){
this.schoolReport.report();
}
@Override
public void sign(String name){
this.schoolReport.sign(name);
}
}
裝飾類還是把動作的執(zhí)行委托給需要裝飾的對象,Decorator抽象類的目的很簡單,就是要讓子類來封裝SchoolReport的子類,先看HighScoreDecorator實(shí)現(xiàn)類,代碼如下:
public class HighScoreDecorator extends Decorator {
public HighScoreDecorator(SchoolReport schoolReport) {
super(schoolReport);
}
private void reportHighScore(){
System.out.println("這次考試語文最高是75,數(shù)學(xué)是78,自然是80");
}
@Override
public void report() {
this.reportHighScore();
super.report();
}
}
重寫了report方法,先調(diào)用具體裝飾類的裝飾方法reportHighScore,然后再調(diào)用具體構(gòu)件的方法,我們再來看怎么匯報學(xué)校排序情況SortDecorator代碼,代碼如下:
public class SortDecorator extends Decorator {
public SortDecorator(SchoolReport schoolReport) {
super(schoolReport);
}
private void reportSort(){
System.out.println("我是排名第38名。。。");
}
@Override
public void report(){
super.report();
this.reportSort();
}
}
通過這兩個強(qiáng)力的修飾工具,然后就可以拿一份"漂亮"的成績單出來了,代碼如下:
public class Client {
public static void main(String[] args) {
SchoolReport schoolReport;
schoolReport = new FouthGradeSchoolReport();
schoolReport = new HighScoreDecorator(schoolReport);
schoolReport = new SortDecorator(schoolReport);
schoolReport.report();
schoolReport.sign("老三");
}
}
這就是裝飾者模式,可以處理一些用繼承來處理的問題,但是比繼承更加的靈活,但是裝飾過多層之后就不太友好了,就像繼承過多之后可讀性就變的很差了。
-
裝飾者模式的定義
裝飾模式(Decorator Pattern)是一種比較常見的模式,其定義如下:Attach additional responsibilities to an object dynamically keeping the same interface.Decorators provide a flexible alternative to subclassing for extending functionality.(動態(tài)地給一個對象添加一些額外的職責(zé)。就增加功能來說,裝飾模式相比生成子類更為靈活。)
類圖17-5:

在類圖中,有四個角色需要說明:
- Component抽象構(gòu)件
Component是一個接口或者是抽象類,就是定義我們最核心的對象,也就是最原始的對象,如上面的成績單。
注意 在裝飾模式中,必然有一個最基本、最核心、最原始的接口或抽象類充當(dāng) Component抽象構(gòu)件(這個抽象構(gòu)件應(yīng)該是描述我們想要裝飾的部分) - ConcreteComponent具體構(gòu)件
ConcreteComponent是最核心、最原始、最基本的接口或抽象類的實(shí)現(xiàn),就是我們要裝飾的類,就是對應(yīng)的例子中的FouthGradeSchoolReport
-Decorator裝飾角色
一般是一個抽象類,實(shí)現(xiàn)接口或抽象方法,在他的屬性里必然有一個private變量指向Component抽象構(gòu)件。(裝飾者模式的核心,和被裝飾者實(shí)現(xiàn)或繼承相同抽象,然后私有屬性指向被裝飾者)
-具體裝飾角色
ConcreteDecoratorA和ConcreteDecoratorB是兩個具體的裝飾類,這個就是用來裝飾的了,想從哪個方面哪個角度去裝飾,就實(shí)現(xiàn)Decorator去寫自己的裝飾方法就好了。
我們來看一下裝飾者模式的通用代碼:
public abstract class Component {
public abstract void operation();
}
public class ConcreateComponent extends Component {
@Override
public void operation() {
//需要被裝飾的方法
System.out.println("do something");
}
}
public class Decorator extends Component {
private Component component;
public Decorator(Component component){
this.component = component;
}
@Override
public void operation() {
//委托給裝飾者執(zhí)行
component.operation();
}
}
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
private void method(){
//do something
}
@Override
public void operation() {
this.method();
super.operation();
}
}
(這個裝飾者模式和代理模式非常相近,而且都是用來增強(qiáng)被裝飾類(被代理類)的;但是這兩者的具體的區(qū)別和用法又是怎么樣的呢)
-
裝飾者模式應(yīng)用
3.1裝飾者模式的優(yōu)點(diǎn)
- 裝飾類和被裝飾類可以獨(dú)立發(fā)展,而不會互相耦合。換句話說,Component類無須知道Decorator類,Decorator類是從外部來擴(kuò)展Component類的功能,而Decorator也不用知道具體的構(gòu)建。
- 裝飾者模式是繼承模式的一個替代方案。我們看裝飾類Decorator,不管裝飾多少層,返回的對象還是Component,實(shí)現(xiàn)的還是is-a的關(guān)系。
- 裝飾模式可以動態(tài)地擴(kuò)展一個實(shí)現(xiàn)類的功能,而且還不會影響到被擴(kuò)展的類,非常符合開閉原則
3.2裝飾模式的缺點(diǎn)
裝飾者模式雖然可以很好的替代繼承的方式處理問題,并且比繼承更加靈活 ,但是也同樣帶來了和繼承類似的問題,那就是裝飾層數(shù)過多的問題,這個就只能靈活處理;
3.3 裝飾模式的使用場景 - 需要擴(kuò)展一個類的功能,或給一個類增加附加功能;
- 需要動態(tài)地給一個對象增加功能,這些功能還可以動態(tài)的撤銷;
- 需要為一批的兄弟類進(jìn)行改裝或加裝功能,當(dāng)然是首選裝飾模式
-
最佳實(shí)踐
裝飾模式是對繼承的有力補(bǔ)充,而且裝飾模式比繼承更加的靈活,易維護(hù),易擴(kuò)展,衣服用;這個之前學(xué)習(xí)例子的時候就有很好的體會。
(這里有一點(diǎn)就是裝飾模式和代理模式的對比,這一章中并沒有提出,有興趣可以百度一下;我們學(xué)完23種設(shè)計模式后,會接著學(xué)后續(xù)的相似的設(shè)計模式之前的對比。)
內(nèi)容來自《設(shè)計模式之禪》
