8.java設計模式之裝飾者模式

xiaokantianse發表於2020-11-18

基本需求:

  • 咖啡的種類有很多種,調料也有很多種,下單時,可以點單品咖啡也可以點單品咖啡+調料的組合,並計算下單時花費的金額

傳統方式:

  • 方式一
    • 建立一個抽象類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;
           
           ......
        }
        

注意事項:

  • 裝飾類和被裝飾類可以獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴充套件一個實現類的功能,多層裝飾比較複雜
  • 擴充套件一個類的功能,動態增加功能,動態撤銷

相關文章