設計模式之裝飾模式(Decorator)
一、裝飾模式的定義
裝飾(Decorator)模式的定義:指在不改變現有物件結構的情況下,動態地給該物件增加一些職責(即增加其額外功能)的模式,它屬於物件結構型模式。
二、裝飾模式優缺點
裝飾(Decorator)模式的主要優點有:
- 採用裝飾模式擴充套件物件的功能比採用繼承方式更加靈活。
- 可以設計出多個不同的具體裝飾類,創造出多個不同行為的組合。
其主要缺點是:
裝飾模式增加了許多子類,如果過度使用會使程式變得很複雜。
三、裝飾模式的實現
通常情況下,擴充套件一個類的功能會使用繼承方式來實現。但繼承具有靜態特徵,耦合度高,並且隨著擴充套件功能的增多,子類會很膨脹。如果使用組合關係來建立一個包裝物件(即裝飾物件)來包裹真實物件,並在保持真實物件的類結構不變的前提下,為其提供額外的功能,這就是裝飾模式的目標。下面來分析其基本結構和實現方法。
裝飾模式主要包含以下角色。
- 抽象構件(Component)角色:定義一個抽象介面以規範準備接收附加責任的物件。
- 具體構件(Concrete Component)角色:實現抽象構件,通過裝飾角色為其新增一些職責。
- 抽象裝飾(Decorator)角色:繼承抽象構件,幷包含具體構件的例項,可以通過其子類擴充套件具體構件的功能。
- 具體裝飾(ConcreteDecorator)角色:實現抽象裝飾的相關方法,並給具體構件物件新增附加的責任。
裝飾模式的結構圖如圖所示:
我們先來看看我們通過繼承的方式新增特性這種實現方式,比如本例使用煎餅果子,程式碼如下:
/** * 煎餅 */ public class Battercake { protected String getDesc(){ return "煎餅"; } protected int cost(){ return 8; } } /** * 加蛋的煎餅 */ public class BattercakeWithEgg extends Battercake { @Override public String getDesc() { return super.getDesc()+" 加一個雞蛋"; } @Override public int cost() { return super.cost()+1; } } /** * 加蛋加香腸的煎餅 */ public class BattercakeWithEggSausage extends BattercakeWithEgg { @Override public String getDesc() { return super.getDesc()+ " 加一根香腸"; } @Override public int cost() { return super.cost()+2; } } public class Test { public static void main(String[] args) { Battercake battercake = new Battercake(); System.out.println(battercake.getDesc()+" 銷售價格:"+battercake.cost()); Battercake battercakeWithEgg = new BattercakeWithEgg(); System.out.println(battercakeWithEgg.getDesc()+" 銷售價格:"+battercakeWithEgg.cost()); Battercake battercakeWithEggSausage = new BattercakeWithEggSausage(); System.out.println(battercakeWithEggSausage.getDesc()+" 銷售價格:"+battercakeWithEggSausage.cost()); } }
最後測試結果為:
煎餅 銷售價格:8 煎餅 加一個雞蛋 銷售價格:9 煎餅 加一個雞蛋 加一根香腸 銷售價格:11
雖然我們也實現了擴充套件類的功能,但是繼承的方式耦合度高,並且如果新增會無限增加類,如果修改原有類,對後面的類影響很大,因此如果使用裝飾模式,程式碼如下:
public class DecoratorPattern { public static void main(String[] args) { Component p=new ConcreteComponent(); p.operation(); System.out.println("---------------------------------"); Component d=new ConcreteDecorator(p); d.operation(); } }
//抽象構件角色 interface Component { public void operation(); }
//具體構件角色 class ConcreteComponent implements Component { public ConcreteComponent() { System.out.println("建立具體構件角色"); } public void operation() { System.out.println("呼叫具體構件角色的方法operation()"); } }
//抽象裝飾角色 class Decorator implements Component { private Component component; public Decorator(Component component) { this.component=component; } public void operation() { component.operation(); } }
//具體裝飾角色 class ConcreteDecorator extends Decorator { public ConcreteDecorator(Component component) { super(component); } public void operation() { super.operation(); addedFunction(); } public void addedFunction() { System.out.println("為具體構件角色增加額外的功能addedFunction()"); } }
四、裝飾模式的應用場景
前面講解了關於裝飾模式的結構與特點,下面介紹其適用的應用場景,裝飾模式通常在以下幾種情況使用。
- 當需要給一個現有類新增附加職責,而又不能採用生成子類的方法進行擴充時。例如,該類被隱藏或者該類是終極類或者採用繼承方式會產生大量的子類。
- 當需要通過對現有的一組基本功能進行排列組合而產生非常多的功能時,採用繼承關係很難實現,而採用裝飾模式卻很好實現。
- 當物件的功能要求可以動態地新增,也可以再動態地撤銷時。
裝飾模式在Java語言中的最著名的應用莫過於 Java I/O 標準庫的設計了。例如,InputStream 的子類 FilterInputStream,OutputStream 的子類 FilterOutputStream,Reader 的子類 BufferedReader 以及 FilterReader,還有 Writer 的子類 BufferedWriter、FilterWriter 以及 PrintWriter 等,它們都是抽象裝飾類。
下面程式碼是為 FileReader 增加緩衝區而採用的裝飾類 BufferedReader 的例子:
BufferedReader in=new BufferedReader(new FileReader("filename.txtn)); String s=in.readLine();
五、裝飾模式擴充套件
裝飾模式所包含的 4 個角色不是任何時候都要存在的,在有些應用環境下模式是可以簡化的,如以下兩種情況。
- 如果只有一個具體構件而沒有抽象構件時,可以讓抽象裝飾繼承具體構件,其結構圖如圖所示:
- 如果只有一個具體裝飾時,可以將抽象裝飾和具體裝飾合併,其結構圖如圖所示: