基本需求:
- 咖啡的種類有很多種,調料也有很多種,下單時,可以點單品咖啡也可以點單品咖啡+調料的組合,並計算下單時花費的金額
傳統方式:
- 方式一
- 建立一個抽象類Drink,讓所有的單品咖啡和組合咖啡都繼承Drink類(組合很多)
- UML類圖
- 如果新增加一個單品咖啡或者調料,類的數量就會倍增,產生類爆炸
- 方式二
- 建立一個抽象類Drink,在Drink類中將所有的調料聚合進去,再讓我們的單品咖啡繼承Drink類
- UML類圖
- 此種方式解決了類爆炸的問題,但是新增或者刪除調料時,需要修改Drink類中的內容,違反了ocp原則
基本介紹:
-
動態的將新功能加到物件上,不改變其結構,屬於結構型模式
-
UML類圖(原理圖)
-
裝飾者模式就像打包一個快遞
- 主體為打包的東西(Component)-> 被裝飾者
- 包裝的東西(Decorator)-> 裝飾者
- Drink為主體(Component)
- 單品咖啡為具體的主體(ConcreteComponent)
- 調料為裝飾者(Decorator)
- 如果ConcreteComponent類很多,還可以向上抽取一個緩衝層
- 讓裝飾者和具體的主體繼承同一類可以對同一個具體的主體,進行多次包裝,通過遞迴的方式
- 如果ConcreteComponent和Decorator不是繼承同一類,則用遞迴不容易,Decorator不僅需要維護自身還需維護ConcreteComponent的變數(不一定行),繼承同一個抽象類,只需要在Decorator中維護一個抽象父類即可
- 例如:點兩個chocolate和一個milk + Espresso咖啡,示例
-
UML類圖(案例)
-
程式碼實現
-
@Data public abstract class Drink { // Component類 ConcreteComponent類和Decorator類都繼承該類 private String description; private float price = 0f; public abstract float cost(); }
-
public abstract class Coffee extends Drink{ // 對具體的被裝飾者進行了緩衝設計 @Override public float cost() { return super.getPrice(); } } // 子類一 義大利咖啡 class Espresso extends Coffee { public Espresso() { super.setDescription("Espresso"); super.setPrice(3.0f); } } // 子類二 濃縮咖啡 class ShortBlack extends Coffee { public ShortBlack() { super.setDescription("ShortBlack"); super.setPrice(5.0f); } }
-
public abstract class Decorator extends Drink { // 對裝飾者進行了緩衝設計 // 聚合被裝飾者 protected Drink drink; public Decorator(Drink drink) { this.drink = drink; } @Override public float cost() { // 金額除自己的金額外還需要加上被裝飾者的金額 return this.drink.cost() + super.getPrice(); } @Override public String getDescription() { // 輸出被裝飾者的資訊 return this.drink.getDescription() + "&" + super.getDescription(); } } // 子類一 牛奶 class Milk extends Decorator { public Milk(Drink drink) { super(drink); super.setDescription("Milk"); super.setPrice(1f); } } // 子類二 巧克力 class Chocolate extends Decorator { public Chocolate(Drink drink) { super(drink); super.setDescription("Chocolate"); super.setPrice(2f); } }
-
public class Client { public static void main(String[] args) { // 單點義大利咖啡 + Milk Drink order = new Espresso(); System.out.println("總費用:" + order.cost()); System.out.println("描述:" + order.getDescription()); // 新增一份 Milk order = new Milk(order); System.out.println("總費用:" + order.cost()); System.out.println("描述:" + order.getDescription()); // 新增第一份 Chocolate order = new Chocolate(order); System.out.println("總費用:" + order.cost()); System.out.println("描述:" + order.getDescription()); // 新增第二份 Chocolate order = new Chocolate(order); System.out.println("總費用:" + order.cost()); System.out.println("描述:" + order.getDescription()); } }
-
如果再增加單品咖啡或者調料時,只需繼承Coffee或者Decorator類即可,系統的擴充套件性提高了,解決了類爆炸,體現了ocp原則,動態的為被裝飾的類新增功能
-
jdk原始碼:
-
在jdk的io原始碼中就使用到的裝飾者模式
-
UMl類圖
-
InputStream作為抽象父類,相當於Drink類
-
FileInputStream、StringBufferInputStream、ByteArrayInputStream繼承了InputStream相當於咖啡單品(並沒有再進行抽象),被裝飾者
-
FilterInputStream繼承了InputSteam並且內部維護了一個其父類InputStream相當於一個Decorator裝飾者
-
BufferInputStream、DataInputStream、LineNumberInputStream繼承了FilterInputStream,相當於Milk、ChocolateD等,為裝飾者的子類
-
關鍵程式碼
-
public class FilterInputStream extends InputStream { /** * The input stream to be filtered. */ protected volatile InputStream in; ...... }
-
注意事項:
- 裝飾類和被裝飾類可以獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴充套件一個實現類的功能,多層裝飾比較複雜
- 擴充套件一個類的功能,動態增加功能,動態撤銷