手撕設計模式——咖啡點單系統之裝飾模式
1.業務需求
? 大家好,我是菠菜啊,好久不見,今天給大家帶來的是——裝飾模式。老規矩,在介紹這期內容前,我們先來看看這樣的需求:現在有一個咖啡館,有基礎飲料:美式咖啡、紅茶、拿鐵等,配料:牛奶、奶泡、糖等,怎么樣實現任意的基礎飲料和配料的組合,并且能夠輸出組合描述以及結算金額?

2.代碼實現
Talk is cheap,show me your code.
初版實現思路:
? 我們之前學習過橋接模式,可以用該模式實現。
初版代碼如下:
//飲料抽象類
public abstract class Beverage2 {protected ToppingImplementor toppingImplementor;protected String description;protected double cost;public Beverage2(ToppingImplementor toppingImplementor){this.toppingImplementor = toppingImplementor;}public void setToppingImplementor(ToppingImplementor toppingImplementor) {this.toppingImplementor = toppingImplementor;}public String getDescription(){return this.description+toppingImplementor.addTopping();}public double cost(){return this.cost+toppingImplementor.addCost();}
}
//配料接口
public interface ToppingImplementor {String addTopping();double addCost();
}
//紅茶
public class BlackTea2 extends Beverage2{public BlackTea2(ToppingImplementor toppingImplementor) {super(toppingImplementor);description = "紅茶";cost = 12;}
}
//美式
public class AmericanoCoffee2 extends Beverage2{public AmericanoCoffee2(ToppingImplementor toppingImplementor) {super(toppingImplementor);description = "美式咖啡";cost = 18;}
}
//拿鐵
public class Latte2 extends Beverage2{public Latte2(ToppingImplementor toppingImplementor) {super(toppingImplementor);description = "拿鐵";cost = 17;}
}
//牛奶配料
public class Milk2 implements ToppingImplementor{@Overridepublic String addTopping() {return "加奶";}@Overridepublic double addCost() {return 3;}
}
//糖配料
public class Sugar2 implements ToppingImplementor{@Overridepublic String addTopping() {return "加糖";}@Overridepublic double addCost() {return 2;}
}
//奶泡配料
public class Whip2 implements ToppingImplementor{@Overridepublic String addTopping() {return "加奶泡";}@Overridepublic double addCost() {return 4;}
}
//客戶端
public class Client2 {public static void main(String[] args) {Beverage2 beverage2 = new Latte2(new Sugar2());System.out.println(beverage2.getDescription()+"花費:"+beverage2.cost());beverage2.setToppingImplementor(new Milk2());System.out.println(beverage2.getDescription()+"花費:"+beverage2.cost());}
}
執行結果:
代碼結構:
思考:
? 上述代碼用橋接模式將基礎飲料和配料的抽象和實現分離,但是我們也發現一些問題。比如,我們運行時要加雙份配料無法實現,如果再添加一種維度(如杯型)無需要修改代碼才能實現。于是,我們進一步優化代碼。
3.代碼優化
優化代碼:
//飲料基類
public abstract class Beverage {abstract String getDescription();abstract double cost();
}
//基礎飲料具體實現
public class BlackTea extends Beverage{@Overridepublic String getDescription() {return "紅茶";}@Overridepublic double cost() {return 12;}
}public class AmericanoCoffee extends Beverage{@Overridepublic String getDescription() {return "美式咖啡";}@Overridepublic double cost() {return 18;}
}public class Latte extends Beverage{@Overridepublic String getDescription() {return "拿鐵";}@Overridepublic double cost() {return 17;}
}
//配料裝飾器抽象類
public abstract class BeverageDecorator extends Beverage{protected Beverage beverage;public BeverageDecorator(Beverage beverage){this.beverage = beverage;}}
//具體配料裝飾器實現
public class Milk extends BeverageDecorator{public Milk(Beverage beverage) {super(beverage);}@OverrideString getDescription() {return beverage.getDescription() + "加奶";}@Overridedouble cost() {return beverage.cost() + 3;}
}public class Sugar extends BeverageDecorator{public Sugar(Beverage beverage) {super(beverage);}@OverrideString getDescription() {return beverage.getDescription() + "加糖";}@Overridedouble cost() {return beverage.cost() + 2;}
}public class Whip extends BeverageDecorator{public Whip(Beverage beverage) {super(beverage);}@OverrideString getDescription() {return beverage.getDescription() + "加奶泡";}@Overridedouble cost() {return beverage.cost() + 4;}
}
//客戶端
public class Client {public static void main(String[] args) {Beverage beverage = new Sugar(new Sugar(new Milk(new AmericanoCoffee())));System.out.println(beverage.getDescription()+",價格"+beverage.cost());Beverage beverage2 = new Latte();beverage2 = new Milk(beverage2);beverage2 = new Whip(beverage2);beverage2 = new Sugar(beverage2);System.out.println(beverage2.getDescription()+",價格"+beverage2.cost());}
}
執行結果:
代碼結構:
思考:
? 上述代碼將配料種類和基礎飲料解耦,N種飲料+M種配料只需(N+M)個類,支持運行時自由組合配料(如雙倍加糖),新增配料或者基礎飲料無需修改代碼,滿足開閉原則。這種縱向增強現有功能,層層疊加功能的實現方式,就是裝飾模式。
?
4.定義與組成
?
?
? 裝飾模式(Decorator Pattern)是一種結構型設計模式,它允許動態地向對象添加新功能,同時不改變其結構。該模式通過創建包裝對象(裝飾器)來擴展原始對象的功能,提供了比繼承更靈活的功能擴展方式。
核心定義:
在不改變現有對象結構的情況下,動態地給對象添加額外職責。裝飾模式比生成子類更為靈活。
- 組件接口(Component ):定義一個對象接口,給這些動態地添加職責
- 具體組件(Concrete Component):實現組件接口的基礎功能
- 抽象裝飾器(Decorator):維持對組件對象的引用并實現組件接口
- 具體裝飾器(Concrete Decorator):添加具體的附加功能
5.應用示例
5.1 Java I/O流體系
// 經典裝飾模式實現
InputStream fileStream = new FileInputStream("data.txt");
InputStream bufferedStream = new BufferedInputStream(fileStream); //添加緩沖
InputStream gzipStream = new GZIPInputStream(bufferedStream);
- 組件接口:
InputStream
- 具體組件:
FileInputStream
- 裝飾器抽象類:
FilterInputStream
- 具體裝飾器:
BufferedInputStream
,GZIPInputStream
5.2 Java GUI (Swing/AWT)
// 為組件添加滾動功能
JTextArea textArea = new JTextArea();
JScrollPane scrollPane = new JScrollPane(textArea);
5.3 服務層功能增強
日志/監控/權限校驗:通過裝飾器為業務邏輯添加非核心功能,保持業務類純凈
UserService service = new UserServiceImpl();
service = new LoggingDecorator(service); // 添加日志記錄
service = new TimingDecorator(service); // 添加性能監控
5.4 動態配置組合
電商優惠系統:基礎價格策略通過裝飾器疊加滿減、折扣券等功能
PriceStrategy base = new BasePriceStrategy();
base = new DiscountDecorator(base, 0.8); // 8折
base = new CouponDecorator(base, 100); // 滿送100
6.適用場景
6.1 核心適用場景
? 需動態擴展功能:如運行時按需添加日志、加密等。
? 避免類爆炸:功能組合多時(如咖啡配料、電商優惠)。
? 保持接口一致性:所有對象(原始/裝飾后)對外暴露相同接口。
? 基礎功能修改:如需徹底改變核心邏輯,應使用策略模式或適配器模式。
6.2 注意事項
- 避免過度裝飾
? 裝飾層數過多會降低可讀性和調試難度(如10層裝飾調用棧深度增加)
- 順序敏感:
? 裝飾順序影響最終結果
7.結構性模式對比
? 裝飾器、代理、橋接 都屬于結構型模式,都涉及對象組合,但它們解決的問題、目的和實現方式有顯著區別:
特性 | 裝飾器模式 (Decorator) | 代理模式 (Proxy) | 橋接模式 (Bridge) |
---|---|---|---|
主要目的 | 動態添加職責 (增強功能) | 控制訪問 (間接訪問、延遲加載等) | 分離抽象與實現 (解耦兩個維度) |
關注點 | 為對象添加新功能/行為 | 管理對對象的訪問方式 | 管理類結構的擴展維度 |
關系對象 | 包裝同一接口的對象 (通常層次深) | 代表一個具體對象 (通常1對1) | 連接抽象角色和實現角色 |
功能變化 | 運行時動態添加/移除功能 | 隱藏/控制原有功能 | 編譯時確定抽象和實現的組合 |
繼承替代 | 是 (避免子類爆炸) | 不一定是 (也可用于控制) | 是 (防止多維度繼承爆炸) |
UML 關鍵 | 裝飾器與被裝飾者實現同一接口 | 代理與真實對象實現同一接口 | 抽象持有實現的接口引用 |
典型應用 | Java I/O 流、GUI 組件增強 | 虛擬代理、保護代理、遠程代理 | 跨平臺UI庫、驅動接口 |
? 一句話總結:“加功能(裝飾)、控訪問(代理)、解耦合(橋接)”。
8.總結
裝飾模式是Java開發中最常用且強大的設計模式之一,特別適用于需要動態擴展功能的場景。它通過組合代替繼承,完美解決了功能擴展中的類爆炸問題,同時保持了對開閉原則的遵守。
使用裝飾模式當:
- 需要在不影響其他對象的情況下添加功能
- 需要動態、透明地添加或撤銷功能
- 繼承擴展不可行或不實際(如final類)
- 系統需要多層次的功能組合
一接口** | 代理與真實對象實現同一接口 | 抽象持有實現的接口引用 |
| 典型應用 | Java I/O 流、GUI 組件增強 | 虛擬代理、保護代理、遠程代理 | 跨平臺UI庫、驅動接口 |
? 一句話總結:“加功能(裝飾)、控訪問(代理)、解耦合(橋接)”。
8.總結
裝飾模式是Java開發中最常用且強大的設計模式之一,特別適用于需要動態擴展功能的場景。它通過組合代替繼承,完美解決了功能擴展中的類爆炸問題,同時保持了對開閉原則的遵守。
使用裝飾模式當:
- 需要在不影響其他對象的情況下添加功能
- 需要動態、透明地添加或撤銷功能
- 繼承擴展不可行或不實際(如final類)
- 系統需要多層次的功能組合
技術需要沉淀,同樣生活也是~
個人鏈接:博客,歡迎一起交流