裝飾者模式

你比從前快樂;發表於2022-12-07

晚風吹人醒,萬事藏於心。我沒說不公平,也沒有說苦,我說我知道了。

歡迎來到星巴茲咖啡

Beverage是店裡所有飲料的抽象類,下面是飲料的不同口味。

在日常生活中,你在購買時,可能還會加一些小料(湊單滿減),例如燕奶(Steamed Milk)、豆漿(Soy)、摩卡(Mocha)等,在付款時,電腦的訂單系統會根據你點的飲料和加的小料計算出總的價錢。

我們現在就是要設計一個能自動計算價格的訂單系統。

最簡單的方法,就是所有的飲料包括小料都寫一個實現類,但是這樣在後期就是一個維護噩夢。不說可能有幾百種飲料幾百種實現方式,如果後期稍微改動其中一款小料的價格,那麼你就需要到一個一個的實現類裡面去進行修改,嚴重違反了軟體的設計原則。

tips:

程式碼應該如同晚霞中的蓮花一樣地關閉(免於改變),如同晨曦中的蓮花一樣地開放(能夠擴充套件)。

設計原則:

類應該對擴充套件開放,對修改關閉。

這樣的設計具有彈性可以應對改變,可以接受新的功能來應對改變的需求。

注意:在選擇需要被擴充套件的程式碼部分時要小心。每個地方都採用開放-關閉原則,是一種浪費,也沒有必要,還會導致程式碼變得複雜且難以理解,要找到平衡點。

認識裝飾者模式

在上面星巴茲咖啡的設計中,實現類數量爆炸、設計死板、以及基類加入新功能不適用於所有的子類。

用裝飾者模式進行設計

  1. 拿一個深色烘焙咖啡(DarkRoast)物件

  2. 以摩卡(Mocha)物件裝飾它

  3. 以奶泡(Whip)物件裝飾它

  4. 呼叫cost()方法,並委託(delegate)將調料的價錢加上去

簡單來講就是將物件一層一層包起來,在呼叫的時候,先一層一層進去,之後一層一層計算結果出來。

定義裝飾者模式

說明:動態地將責任附加到物件上。若要擴充套件功能,裝飾者提供了比繼承更有彈性的替代方案

使用裝飾者模式設計星巴茲類圖

注意:這裡使用繼承是達到“型別匹配”的目的(!!!),而不是利用繼承來獲得行為。

新咖啡師傅特訓

如果有一張單子點的是:“雙倍摩卡豆漿奶泡拿鐵咖啡”,進行設計實現。

流程

核心程式碼實現

總抽象類,裝飾類與被裝飾類都實現此類,達到型別匹配

/**
* @Description 抽象類飲料
* @Author lh
* @Date 2022/12/6 19:31
*/
public abstract class Beverage {
   public String description = "Unknown Beverage";

   public String getDescription() {
       return description;
  }

   public abstract double cost();
}

被裝飾類,不同口味飲料

/**
* @Description 義大利濃縮咖啡
* @Author lh
* @Date 2022/12/6 19:37
*/
public class Espresso extends Beverage {

   public Espresso() {
       description = "Espresso";
  }

   @Override
   public double cost() {
       return 1.99;
  }
}
/**
* @Description 家庭混合咖啡
* @Author lh
* @Date 2022/12/6 19:39
*/
public class HouseBlend extends Beverage {

   public HouseBlend() {
       description = "House Blend Coffee";
  }

   @Override
   public double cost() {
       return .89;
  }
}

裝飾抽象類

/**
* @Description 裝飾類調料抽象類
* @Author lh
* @Date 2022/12/6 19:35
*/
public abstract class CondimentDecorator extends Beverage {
   public abstract String getDescription();
}

裝飾類實現

/**
* @Description 調料摩卡
* @Author lh
* @Date 2022/12/6 19:41
*/
public class Mocha extends CondimentDecorator{
   private final Beverage beverage;

   public Mocha(Beverage beverage) {
       this.beverage = beverage;
  }

   @Override
   public String getDescription() {
       return beverage.getDescription() + ", Mocha";
  }

   public double cost() {
       return .20 + beverage.cost();
  }
}
/**
* @Description 調料奶泡
* @Author lh
* @Date 2022/12/6 19:51
*/
public class Whip extends CondimentDecorator{
   private final Beverage beverage;

   public Whip(Beverage beverage) {
       this.beverage = beverage;
  }

   @Override
   public String getDescription() {
       return beverage.getDescription() + ", Whip";
  }

   public double cost() {
       return .15 + beverage.cost();
  }
}

實現 雙倍摩卡豆漿奶泡拿鐵咖啡

/**
* @Description 星巴茲計算
* @Author lh
* @Date 2022/12/6 20:10
*/
public class StarbuzzCoffee {
   public static void main(String[] args) {
       Beverage beverage = new HouseBlend();
       System.out.println(beverage.getDescription() + ":" + beverage.cost() + "元");

       Beverage beverage1 = new HouseBlend();
       beverage1 = new Mocha(beverage1);
       beverage1 = new Mocha(beverage1);
       beverage1 = new Whip(beverage1);
       System.out.println(beverage1.getDescription() + ":" + beverage1.cost() + "元");
  }
}

真實世界的裝飾者:Java I/O

下面是一個典型的物件集合,用裝飾者來將功能結合起來,以讀取檔案資料。

和星巴茲的設計相比,java.io其實並沒有多大的差距。

核心程式碼示例

/**
* @Description 獲取文字行數
* @Author lh
* @Date 2022/12/7 19:34
*/
public class LowerNumberInputStream extends FilterInputStream {

   public LowerNumberInputStream(InputStream in) {
       super(in);
  }

   public int read() throws IOException {
       int c = super.read();
       return (c == -1 ? c : Character.toLowerCase(c));
  }

   public int read(byte[] b, int offset, int len) throws IOException {
       int result = super.read(b, offset, len);
       for (int i = offset; i < offset + result; i++) {
           b[i] = (byte) Character.toLowerCase(b[i]);
      }
       return result;
  }
}
/**
* @Description IO測試
* @Author lh
* @Date 2022/12/7 19:40
*/
public class InputTest {
   public static void main(String[] args) throws FileNotFoundException {
       int c;
       try {
           InputStream in = new LowerNumberInputStream(new BufferedInputStream(new FileInputStream("text.txt")));
           while ((c = in.read()) >= 0) {
               System.out.println(c);
          }
      } catch (Exception e) {
           e.printStackTrace();
      }
  }
}

總結

  • 裝飾者和被裝飾者物件有相同的超型別(型別匹配)。

  • 你可以用一個或多個裝飾者包裝一個物件。

  • 既然裝飾者和被裝飾者有相同的超型別,所以在任何需要原始物件(被包裝的)場合,可以用裝飾過的物件代替它。

  • 裝飾者可以在所委託被裝飾者的行為之前與之後,加上自己的行為,以達到特定的目的。

  • 物件可以在任何時候被裝飾,所以可以在執行時動態地、不限量得使用你喜歡的裝飾者來裝飾物件。

程式碼地址

https://gitee.com/LHDAXIE/design-mode

相關文章