一、什麼是裝飾模式 裝飾模式(Decorator),動態地給一個對象添加一些額外的職責,就增加功能來說,裝飾模式比生成子類更靈活。UML結構圖如下: 其中,Component是抽象構件,定義一個對象介面,可以給這些對象動態地添加職責;ConreteComponent定義一個具體對象,也可以給這個對象 ...
一、什麼是裝飾模式
還記得我的一個長輩曾經買了一部手機,買的時候還好好的新新的,剛拿到家就壞了,怎麼回事呢?其實就是一個假手機,把一個已經報廢的舊機子改了改,外面加了個新殼子罷了,這就是一個裝飾模式,在原有的基礎上加了些東西。
裝飾模式(Decorator),動態地給一個對象添加一些額外的職責,就增加功能來說,裝飾模式比生成子類更靈活。UML結構圖如下:
其中,Component是抽象構件,定義一個對象介面,可以給這些對象動態地添加職責;ConreteComponent定義一個具體對象,也可以給這個對象添加一些職責;Decorator是裝飾抽象類,實現介面或抽象方法;ConreteDecorator是具體裝飾對象,起到給Component添加職責的功能。。
下麵我們通過代碼實現上面的UML圖。
1. Component抽象類
Component是一個介面或是抽象類,就是定義我們最核心的對象,也就是最原始的對象。
1 public abstract class Component { 2 3 public abstract void operation(); 4 5 }
2. ConretetComponent類
具體構件,通過繼承實現Component抽象類中的抽象方法。是最核心、最原始、最基本的介面或抽象類的實現,我們要裝飾的就是它。
1 public class ConcreteComponent extends Component { 2 3 @Override 4 public void operation() { 5 System.out.println("具體對象的操作"); 6 } 7 8 }
3. Decorator裝飾類
一般是一個抽象類,在其屬性里必然有一個private變數指向Component抽象構件。
1 public abstract class Decorator extends Component { 2 3 private Component component = null; 4 5 //通過構造函數傳遞給被修飾者 6 public Decorator(Component component) { 7 this.component = component; 8 } 9 10 //委托給被修飾者執行 11 @Override 12 public void operation() { 13 if(component != null) { 14 this.component.operation(); 15 } 16 } 17 18 }
4. ConcreteDecorator類
我們可以寫多個具體實現類,把最核心的、最原始的、最基本的東西裝飾成其它東西。
這裡就寫兩個類,稍改一下二者的實現順序,看看結果。
A類,它的operation()方法先執行了method1()方法,再執行了Decorator的operation()方法。
1 public class ConcreteDecoratorA extends Decorator { 2 3 //定義被修飾者 4 public ConcreteDecoratorA(Component component) { 5 super(component); 6 } 7 8 //定義自己的修飾方法 9 private void method1() { 10 System.out.println("method1 修飾"); 11 } 12 13 @Override 14 public void operation() { 15 this.method1(); 16 super.operation(); 17 } 18 19 }
B類,它的operation()方法先執行了Decorator的operation()方法,再執行了method2()方法。
1 public class ConcreteDecoratorB extends Decorator { 2 3 //定義被修飾者 4 public ConcreteDecoratorB(Component component) { 5 super(component); 6 } 7 8 //定義自己的修飾方法 9 private void method2() { 10 System.out.println("method2 修飾"); 11 } 12 13 @Override 14 public void operation() { 15 super.operation(); 16 this.method2(); 17 } 18 19 }
5. Client客戶端
1 public class Client { 2 3 public static void main(String[] args) { 4 Component component = new ConcreteComponent(); 5 //第一次修飾 6 component = new ConcreteDecoratorA(component); 7 //第二次修飾 8 component = new ConcreteDecoratorB(component); 9 //修飾後運行 10 component.operation(); 11 } 12 13 }
運行結果如下:
如果我們將B的運算順序改為與A相同的,即先this再super,運行結果如下:
所以我們可以知道,原始方法和裝飾方法的執行順序在具體的裝飾類是固定的,可以通過方法重載實現多種執行順序。
至於上面的具體對象操作為什麼只輸出了一次,因為在裝飾者類中,我們有一個“component != null“的判斷條件,控制了對象的引用,更多類似的內容可參考單例模式。
二、裝飾模式的應用
1. 何時使用
- 在不想增加很多子類的情況下擴展類時
2. 方法
- 將具體功能職責劃分,同時繼承裝飾者模式
3. 優點
- 裝飾類和被裝飾類可以獨立發展,而不會相互耦合。它有效地把類的核心職責和裝飾功能分開了
- 裝飾模式是繼承關係的一個替代方案
- 裝飾模式可以動態地擴展一個實現類的功能
4. 缺點
- 多層裝飾比較複雜。比如我們現在有很多層裝飾,除了問題,一層一層檢查,最後發現是最裡層的裝飾出問題了,想想工作量都害怕
5. 使用場景
- 需要擴展一個類的功能時
- 需要動態地給一個對象增加功能,並可以動態地撤銷時
- 需要為一批的兄弟類進行改裝或加裝功能時
6. 應用實例
- 舊機包裝成新機,手機/電腦內部配件不變,只是換個外殼
- 換衣小游戲,人還是那個人,不斷給她換衣服,還可以一層套一層的
- 孫悟空有72變,變成什麼後就有了它的功能,但本質還是一隻猴子
三、裝飾模式的實現
下麵我們看一個例子,我們就以上面說的換裝為例。我們先分析一下,換裝需要有一個人類用於指定是誰換裝、一個服裝類為具體服裝類的父類、以及服裝類下的各種具體服裝的類。UML圖如下:
1. 人類(Person類)
通過構造方法獲取人,再通過show()方法傳遞出去。
1 public class Person { 2 3 private String name; 4 5 public Person() {} 6 7 public Person(String name) { 8 this.name = name; 9 } 10 11 public void show() { 12 System.out.println(name + "的裝扮:"); 13 } 14 }
2. 服裝類(Finery類)
通過構造方法傳遞參數給show()方法,show()方法為重寫父類Person類的方法。
1 public class Finery extends Person { 2 3 protected Person component; 4 5 public void Decorate(Person component) { 6 this.component = component; 7 } 8 9 @Override 10 public void show() { 11 if(component != null) { 12 component.show(); 13 } 14 } 15 16 }
3. 具體服裝類
上述UML圖中我給了6種服裝,這個可以自行設計,內部實現都是相同的,這裡就放一個TShirt類,過多的就不贅餘了。
1 public class TShirts extends Finery { 2 3 @Override 4 public void show() { 5 super.show(); 6 System.out.print("T恤 "); 7 } 8 9 }
4. Client客戶端
接下來我們編寫一個客戶端測試一下裝飾模式。
首先先給adam換裝,給他穿上西裝、領帶、皮鞋,然後展示出來;然後再給bill換裝,給他穿上T恤、垮褲、球鞋,然後展示出來。我們可以看到,代碼中的服裝是一層套一層的,比如adam的,先給adam穿上Suits,再給Suits套上Tie,再給Tie套上LeatherShoes,然後對最後一層LeatherShoes展示。
1 public class Client { 2 3 public static void main(String[] args) { 4 //adam的換裝 5 Person adam = new Person("adam"); 6 7 Suits a = new Suits(); 8 Tie b = new Tie(); 9 LeatherShoes c = new LeatherShoes(); 10 11 a.Decorate(adam); 12 b.Decorate(a); 13 c.Decorate(b); 14 c.show(); 15 16 System.out.println("\n--------------"); 17 18 //bill的換裝 19 Person bill = new Person("bill"); 20 21 TShirts x = new TShirts(); 22 Trouser y = new Trouser(); 23 Sneakers z = new Sneakers(); 24 25 x.Decorate(bill); 26 y.Decorate(x); 27 z.Decorate(y); 28 z.show(); 29 } 30 31 }
運行結果如下:
平常當系統需要新功能時,是向舊的類中添加新的代碼,這些新加的代碼通常裝飾了原有類的核心職責或主要行為,這種做法的問題在於,它們再主類中加入了新的欄位、新的方法和新的邏輯,從而增加了主類的複雜度,而這些新加入的東西僅僅是為了滿足一些只在某種特定情況下才會執行的個特殊行為的需要。
而裝飾模式卻提供了一個非常好的解決方案,它把每個要裝飾的功能放在單獨的類中,並讓這個類包裝它所要裝飾的對象。因此當需要執行特殊行為時,客戶代碼就可以在運行時根據需要有選擇地、按順序的地使用裝飾功能包裝對象了。
源碼地址:https://gitee.com/adamjiangwh/GoF