簡說設計模式——裝飾模式


一、什么是裝飾模式

  還記得我的一個長輩曾經買了一部手機,買的時候還好好的新新的,剛拿到家就壞了,怎么回事呢?其實就是一個假手機,把一個已經報廢的舊機子改了改,外面加了個新殼子罷了,這就是一個裝飾模式,在原有的基礎上加了些東西。

  裝飾模式(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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM