手撕设计模式——咖啡点单系统之装饰模式

手撕设计模式——咖啡点单系统之装饰模式

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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)是它的双核心,谁先谁后,关系重大,顺序写错,查询性能立马打折。…