手撕設計模式——咖啡點單系統之裝飾模式

手撕設計模式——咖啡點單系統之裝飾模式

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());}
}

執行結果:

裝飾模式之初版代碼執行結果

代碼結構:

裝飾模式之初版代碼UML.drawio

思考:

? 上述代碼用橋接模式將基礎飲料和配料的抽象和實現分離,但是我們也發現一些問題。比如,我們運行時要加雙份配料無法實現,如果再添加一種維度(如杯型)無需要修改代碼才能實現。于是,我們進一步優化代碼。

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());}
}

執行結果:

裝飾模式之優化代碼執行結果

代碼結構:

裝飾模式之優化代碼UML.drawio

思考:

? 上述代碼將配料種類和基礎飲料解耦,N種飲料+M種配料只需(N+M)個類,支持運行時自由組合配料(如雙倍加糖),新增配料或者基礎飲料無需修改代碼,滿足開閉原則。這種縱向增強現有功能,層層疊加功能的實現方式,就是裝飾模式

裝飾模式之層層嵌套示意圖

?

4.定義與組成

?

? 裝飾模式UML.drawio

? 裝飾模式(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類)
  • 系統需要多層次的功能組合

技術需要沉淀,同樣生活也是~
個人鏈接:博客,歡迎一起交流

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/92372.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/92372.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/92372.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

LRU Cache緩存替換算法

目錄 一、LRU 是什么?Cache是什么? 二、LRU Cache的實現 三、源碼 一、LRU 是什么?Cache是什么? LRU 是 "Least Recently Used" 的縮寫,意思是“最近最少使用”。它是一種常用的 緩存(Cache&…

自定義視圖:圖形與圖像的處理(二):繪圖

除了使用已有的圖片之外,Android應用還常常需要在運行時動態地生成圖片,比如一個手機游戲,游戲界面看上去豐富多彩,而且可以隨著用戶動作而動態改變,這就需要借助于Android的繪圖支持了。1. Android繪圖基礎:Canvas、P…

微服務、服務網格、Nacos架構與原理

Nacos架構與原理 -服務網格生態-阿里云開發者社區 ------ 該文章用于學習參考,如有侵權,請直接聯系下架 服務網格的核心職責:治理“服務通信” 包括但不限于: 功能 舉例說明 負載均衡 動態選擇服務實例 熔斷、重試 某個服務失敗時自動切換、重試 流量路由 灰度發布、藍綠…

STM32——啟動過程淺析

總:STM32——學習總綱 參考文件: STM32 MAP文件淺析-V1.1 STM32 啟動文件淺析_V1.2 Cortex-M3權威指南(中文)、ARM Cotrex-M3權威指南(英文).zip 一、Map文件解析 1.1 MDK編譯過程文件 在編譯中,會生成11種編譯過程文件,可…

區塊鏈簡介

一、區塊鏈簡介 狹義上的定義: 區塊鏈是一種鏈式數據結構,通過按時間順序將數據塊逐一連接形成。這種結構通過密碼學確保了數據的不可篡改性和不可偽造性,形成了一種分布式賬本技術。 廣義上的定義: 區塊鏈技術不僅僅是一種數據…

NestJS中@Injectable裝飾器

一、基礎定義與核心作用 1.1 什么是Injectable? Injectable() 是 NestJS 依賴注入(Dependency Injection, DI)系統的核心裝飾器,用于將類標記為可注入的提供者(Provider)。它告知 NestJS 的 IoC&#xff08…

【機器學習深度學習】大模型應用落地:微調與RAG的角色與實踐

目錄 前言 一、微調與RAG:大模型應用落地的兩大支柱 1. 微調(Fine-tuning) 2. RAG(Retrieval-Augmented Generation) 二、微調可以做什么? 1. 模型自我認知調整 2. 對話風格優化 3. 提升問題理解能…

List、ArrayList 與順序表

目錄 一、List 介紹 二、線性表 三、自己實現 ArrayList 3.1 顯示元素 3.2 增 3.2.1 默認在數組后面新增元素 3.2.2 在指定位置中新增元素 3.3 查 3.4 取值 3.5 改 3.5.1 把 pos 位置的元素修改成 value 3.5.2 刪除某個元素 3.5.3 清空 四、認識 ArrayList 4.0 說…

Baumer工業相機堡盟工業相機如何通過YoloV8深度學習模型實現各類垃圾的分類檢測識別(C#代碼UI界面版)

Baumer工業相機堡盟工業相機如何通過YoloV8深度學習模型實現各類垃圾的分類檢測識別(C#代碼UI界面版)工業相機使用YoloV8模型實現各類垃圾的分類檢測識別工業相機通過YoloV8模型實現各類垃圾的分類檢測識別的技術背景在相機SDK中獲取圖像轉換圖像的代碼分…

EasyExcel高效工具類:簡化Excel導入導出,支持多Sheet與枚舉轉換

文章目錄前言一、依賴坐標二、工具類:ExcelUtil三、測試1.實體類2.前置操作3.單Sheet導出4.單Sheet導入5.多Sheet導出6.多Sheet導入7.完整代碼四、擴展:自定義注解實現枚舉類型轉換1.枚舉接口2.枚舉類3.注解4.轉換類5.使用示例6.測試總結前言 在現代應用…

技術速遞|GitHub Copilot for Eclipse 邁出重要一步

我們非常高興地宣布:2025 年 7 月 22 日,GitHub Copilot for Eclipse 又邁出了重要一步,Eclipse 變得更智能、更快捷,而且與 Eclipse 的集成也更無縫了!這是繼新功能上線以來,又一次質的提升。 &#x1f…

Coze Loop:開源智能體自動化流程編排平臺原理與實踐

項目簡介 Coze Loop 是 Coze 團隊開源的智能體自動化流程編排平臺。它以“Loop”為核心概念,支持開發者通過低代碼/可視化方式,將多種 AI Agent、插件、API、數據流等靈活編排為自動化工作流,實現復雜的智能體協作、任務自動化和多模態數據處理。Coze Loop 適用于企業自動化…

[GESP202309 四級] 2023年9月GESP C++四級上機題題解,附帶講解視頻!

本文為2023年9月GESP C四級的上機題目的詳細題解!覺得寫的不錯或者有幫助可以點個贊啦。 目錄 題目一講解視頻: 題目二講解視頻: 題目一:進制轉換 解題思路: 代碼(C): 題目二:變長編碼 解題思路: 代碼(C): 題目一講解視頻: 2023年9月GESP C四級上機題一題目…

【AI編程工具IDE/CLI/插件專欄】-國外IDE與Cursor能力對比

AI編程專欄(二) - Cursor 深度使用指南 Cursor 深度使用指南(二) - 新能力使用教程 從Trae 2.0與CodeBuddy IDE發布,談大廠布局IDE 如何選擇AI IDE?對比Cursor分析功能差異 AI編程工具IDE/CLI/插件專欄-熱門AI編程CLI初識與IDE對 前面文章介紹過了國…

word2vector細致分解(CBOW, SKIP_GRAM, 層次soft Max, 負采樣)

1 前世今生:NGRAM NGRAM:將詞當成一個離散的單元(因此存在一定的局限性,沒有考慮到詞與詞之間的關系) neural network language model:只能處理定長序列,訓練慢。使用RNN之后有所改善 2 兩種訓…

Elasticsearch向量庫

在Elasticsearch(ES)最新版本(目前8.x系列)中,無需額外的“embedding插件”,因為ES從7.14版本開始就原生支持向量數據類型(dense_vector) 和向量搜索能力,可直接作為向量…

嵌入式學習的第四十四天-ARM

一、ARM內核基礎知識1.ALU算術邏輯單元;完成運算的電路2.通用寄存器:R0~R15R13(SP):棧指針寄存器:指向棧的指針(指向正確的位置),為了保護現場 R14(LR…

QML開發:QML中的基本元素

文章目錄一、概述二、常用基本元素2.1 基礎視覺元素(常用于布局和顯示)2.1.1 元素 Item 的介紹和使用2.1.2 元素 Rectangle 的介紹和使用2.1.3 元素 Image 的介紹和使用2.1.4 元素 Text 的介紹和使用2.2 交互元素(用于接收用戶操作&#xff0…

Spring AI 項目實戰(二十二):Spring Boot + AI +DeepSeek實現智能合同數據問答助手?(附完整源碼)

系列文章 序號 文章名稱 1 Spring AI 項目實戰(一):Spring AI 核心模塊入門 2 Spring AI 項目實戰(二):Spring Boot + AI + DeepSeek 深度實戰(附完整源碼) 3 Spring AI 項目實戰(三):Spring Boot + AI + DeepSeek 打造智能客服系統(附完整源碼) 4

從 0 到 1 創建 InfluxDB 3 表:標簽、字段、命名規范一篇講透

前言 在使用 InfluxDB 3 存儲時序數據時,表的設計堪比蓋房子打地基,地基打歪,數據“塌方”指日可待。InfluxDB 雖然不是傳統意義上的關系型數據庫,但它有自己的一套“審美”:標簽(Tags)和字段(Fields)是它的雙核心,誰先誰后,關系重大,順序寫錯,查詢性能立馬打折。…