Java二十三种设计模式-装饰器模式(7/23)
装饰器模式:动态扩展功能的灵活之选
引言
装饰器模式(Decorator Pattern)是一种结构型设计模式,用于在不修改对象自身的基础上,通过添加额外的职责来扩展对象的功能。

基础知识,java设计模式总体来说设计模式分为三大类:
(1)创建型模式,共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
(2)结构型模式,共7种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
(3)行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

第一部分:装饰器模式概述
1.1 定义与用途
装饰器模式的基本定义
装饰器模式是一种设计模式,允许用户在不修改对象自身的情况下,向一个对象添加新的功能。这种模式通过创建一个包装对象,也就是装饰器,来包裹实际对象,从而在不修改实际对象的基础上扩展其功能。

解释为何需要装饰器模式
- 动态添加职责:装饰器模式可以在运行时动态地给对象添加额外的职责。
 - 保持对象的透明性:使用装饰器模式不改变对象的接口,因此可以在任何使用原始对象的地方使用装饰过的对象。
 - 避免继承的复杂性:相比通过继承来扩展功能,装饰器模式提供了一种更灵活的替代方案,避免了多重继承的问题。
 
1.2 装饰器模式的组成
抽象组件(Component)
- 定义:定义了一个对象接口,可以给这些对象动态地添加职责。
 - 角色:充当具体组件和所有装饰器的公共接口。
 
具体组件(Concrete Component)
- 定义:定义了抽象组件的具体实现,也就是被装饰的具体对象。
 - 角色:提供了具体的行为和数据。
 
抽象装饰器(Decorator)
- 定义:抽象组件的子类,实现了与抽象组件相同的接口,并持有一个抽象组件的实例。
 - 角色:为组件添加职责提供了方法,同时保持接口不变。
 
具体装饰器(Concrete Decorator)
- 定义:抽象装饰器的子类,实现具体装饰功能。
 - 角色:通过实现抽象装饰器的接口,给具体组件添加额外的职责。
 
客户端(Client)
- 角色:使用抽象组件接口与具体组件和装饰器交互。
 - 职责:客户端并不关心对象是具体组件还是装饰器,它通过相同的接口与所有对象交互。
 
装饰器模式通过将一个对象包装在装饰器对象中来扩展功能,而不需要修改对象的类代码。这种模式提供了一种灵活的方式来扩展或增强对象的行为。在下一部分中,我们将通过Java代码示例来展示装饰器模式的具体实现。
第二部分:装饰器模式的实现
2.1 Java实现示例
以下是使用Java语言实现装饰器模式的一个示例。假设我们有一个简单的咖啡类,我们想要通过装饰器模式来添加不同的调料和装饰。
// 抽象组件
interface Coffee {double cost();String getIngredients();
}// 具体组件
class SimpleCoffee implements Coffee {@Overridepublic double cost() {return 10.0;}@Overridepublic String getIngredients() {return "Simple Coffee";}
}// 抽象装饰器
abstract class CoffeeDecorator implements Coffee {protected Coffee decoratedCoffee;public CoffeeDecorator(Coffee coffee) {this.decoratedCoffee = coffee;}@Overridepublic double cost() {return decoratedCoffee.cost();}@Overridepublic String getIngredients() {return decoratedCoffee.getIngredients();}
}// 具体装饰器
class Milk extends CoffeeDecorator {public Milk(Coffee coffee) {super(coffee);}@Overridepublic double cost() {return super.cost() + 2.0;}@Overridepublic String getIngredients() {return super.getIngredients() + ", Milk";}
}class Whip extends CoffeeDecorator {public Whip(Coffee coffee) {super(coffee);}@Overridepublic double cost() {return super.cost() + 1.5;}@Overridepublic String getIngredients() {return super.getIngredients() + ", Whip";}
}// 客户端代码
public class Client {public static void main(String[] args) {Coffee coffee = new SimpleCoffee();System.out.println("Cost: " + coffee.cost());System.out.println("Ingredients: " + coffee.getIngredients());Coffee milkCoffee = new Milk(coffee);System.out.println("Cost: " + milkCoffee.cost());System.out.println("Ingredients: " + milkCoffee.getIngredients());Coffee whipCoffee = new Whip(milkCoffee);System.out.println("Cost: " + whipCoffee.cost());System.out.println("Ingredients: " + whipCoffee.getIngredients());}
} 
在这个示例中,Coffee是抽象组件,SimpleCoffee是具体组件。CoffeeDecorator是抽象装饰器,Milk和Whip是具体装饰器。客户端可以通过装饰器来动态地添加调料和装饰。
2.2 装饰器模式中的角色和职责
抽象组件(Component)
- 职责:定义装饰对象和具体对象的公共接口,允许通过装饰器来扩展功能。
 
具体组件(Concrete Component)
- 职责:定义一个具体的类,实现抽象组件接口,是装饰器装饰的对象。
 
抽象装饰器(Decorator)
- 职责:持有一个组件(Component)对象的实例,定义装饰器的接口,继承自抽象组件。
 - 实现:通常是一个抽象类,实现接口中的方法,并将调用委托给被装饰的组件。
 
具体装饰器(Concrete Decorator)
- 职责:实现抽象装饰器的接口,添加装饰的功能。
 - 实现:具体装饰器类会持有一个组件对象,并在调用组件方法前后添加额外的行为。
 
客户端(Client)
- 职责:通过抽象组件接口与具体组件和装饰器交互,客户端不关心对象是具体组件还是装饰器。
 
装饰器模式通过在运行时动态地添加装饰器来扩展对象的功能,提供了一种灵活且强大的方式来增强对象的行为。在下一部分中,我们将探讨装饰器模式的使用场景。

第三部分:装饰器模式的使用场景
3.1 动态添加职责
在软件开发中,经常会遇到需要在运行时动态地给对象添加额外职责的情况。装饰器模式提供了一种灵活的方式来实现这一点,而无需改变对象本身的结构。
何时需要动态地给对象添加职责:
- 功能扩展:当需要为对象添加新功能,但又不想通过继承来扩展现有类时。
 - 条件性行为:某些行为只在特定条件下需要,装饰器模式可以根据运行时的条件来决定是否添加这些行为。
 - 多样化组合:需要多种行为组合时,装饰器模式允许通过组合不同的装饰器来实现。
 
应用实例:
- GUI组件:在图形用户界面中,可能需要为组件动态添加如边框、颜色、阴影等视觉效果。
 - 日志记录:在不修改现有方法的情况下,为方法动态添加日志记录功能。
 
3.2 避免使用多重继承
Java等许多编程语言不支持多重继承,这限制了通过继承来扩展对象功能的方式。装饰器模式提供了一种替代方案,允许一个对象在运行时拥有多个“继承”自不同类的行为。
在需要避免多重继承的情况下,装饰器模式的优势:
- 解决多重继承问题:装饰器模式允许对象在运行时拥有多个职责,而不需要通过多重继承实现。
 - 灵活性:装饰器模式提供了更高的灵活性,可以在运行时动态地添加或移除职责。
 - 保持类的独立性:避免了因为多重继承导致的类之间的强耦合。
 
应用实例:
- 支付系统:在一个支付系统中,可能需要为支付方法添加日志记录、事务管理、安全性检查等多种功能。使用装饰器模式可以避免创建过多的继承层级。
 - 网络通信:在网络通信中,可能需要为通信协议添加加密、压缩、认证等多种功能。装饰器模式允许在不修改协议本身的情况下,动态地添加这些功能。
 
装饰器模式是一种非常有用的设计模式,它允许开发者在不修改对象结构的前提下,动态地扩展对象的功能。在实际应用中,根据具体需求和场景选择是否使用装饰器模式是非常重要的。在下一部分中,我们将讨论装饰器模式的优点与缺点。

第四部分:装饰器模式的优点与缺点
4.1 优点
提高灵活性
- 动态添加行为:装饰器模式可以在运行时动态地给对象添加额外的行为或职责。
 
增强可扩展性
- 无需修改原有代码:通过装饰器可以扩展功能,而无需修改原有的类代码,遵循开闭原则。
 
简化对象复用
- 减少类的数量:避免创建多个继承层次,减少系统中类的数量,简化复用。
 
提供更好的封装性
- 隐藏实现细节:装饰器模式将对象的具体实现细节封装在装饰器内部,对外提供统一的接口。
 
4.2 缺点
过度使用导致复杂性
- 多层装饰器嵌套:如果过度使用装饰器模式,可能会导致多层嵌套,难以理解和维护。
 
增加系统的复杂度
- 类结构复杂:装饰器模式可能会使系统的类结构变得更加复杂,尤其是当有多个装饰器时。
 
性能问题
- 可能影响性能:在某些情况下,装饰器模式可能会引入额外的性能开销,尤其是在装饰器较多的情况下。
 

第五部分:装饰器模式与其他模式的比较
5.1 与适配器模式的比较
适配器模式
- 目的:使不兼容的接口能够一起工作。
 - 实现:通常通过继承或组合来转换一个类的接口。
 
装饰器模式
- 目的:动态地给对象添加额外的职责。
 - 实现:通过组合来包装对象,增加新的行为。
 
对比
- 目的不同:适配器模式主要用于接口转换,而装饰器模式用于功能扩展。
 - 使用场景:适配器模式适用于解决接口不兼容的问题,装饰器模式适用于动态添加职责。
 
5.2 与外观模式的对比
外观模式
- 目的:提供一个统一的接口来访问子系统中的一组接口。
 - 实现:通过一个外观类来简化客户端与复杂系统的交互。
 
装饰器模式
- 目的:动态地给对象添加额外的职责。
 
对比
- 简化接口:外观模式用于简化客户端与复杂系统之间的接口,而装饰器模式用于在不修改对象的情况下扩展功能。
 - 使用场景:外观模式适用于简化系统访问,装饰器模式适用于功能增强。
 
装饰器模式提供了一种灵活的方式来动态地扩展对象的功能,但也需要谨慎使用,以避免增加系统的复杂性和维护难度。在实际应用中,根据具体需求和场景选择合适的设计模式是非常重要的。在下一部分中,我们将提供装饰器模式的最佳实践和建议。

第六部分:装饰器模式的最佳实践和建议
6.1 最佳实践
保持接口的一致性
- 统一接口:确保所有装饰器和被装饰的组件都实现同一个接口,这样客户端就可以以统一的方式处理它们。
 
装饰者应该是非侵入性的
- 不修改原始类:装饰者不应该修改原始类代码,以保证原始类的独立性和可重用性。
 
单一职责原则
- 单一职责:每个装饰者应该只关注添加一种职责,遵循单一职责原则。
 
使用装饰者工厂
- 工厂模式:使用工厂模式来创建装饰者,这样可以更好地控制对象的创建过程。
 
避免过度装饰
- 适度使用:避免创建过多的装饰者,以免系统变得复杂难以管理。
 
6.2 避免滥用
避免多层装饰器导致的复杂性
- 简化设计:避免不必要的多层嵌套装饰,保持设计的简洁性。
 
避免过度复杂的继承结构
- 扁平化设计:尽量保持扁平化的设计,避免创建过多的装饰者类。
 
考虑使用组合而非继承
- 组合优于继承:在某些情况下,使用对象组合代替继承可能更灵活。
 
6.3 替代方案
#.# 使用组合模式
- 定义:组合模式允许将对象组合成树形结构,以表示“部分-整体”的层次结构。
 - 适用场景:当需要表示对象之间的层次关系时。
 
使用策略模式
- 定义:策略模式定义了一系列算法,并将每一个算法封装起来,使它们可以互换。
 - 适用场景:当需要根据不同的策略动态改变对象行为时。
 
使用外观模式
- 适用场景:当需要简化客户端对复杂系统的访问时。
 
使用享元模式
- 定义:享元模式通过共享来高效地支持大量细粒度对象的复用。
 - 适用场景:当系统中存在大量相似对象时,可以通过享元模式来减少内存消耗。
 
装饰器模式是一种强大的设计模式,可以提供灵活性和可扩展性,但也需要谨慎使用以避免复杂性和性能问题。了解其替代方案可以帮助开发者根据具体需求和场景选择最合适的设计模式。在实际开发中,应根据具体情况灵活运用装饰器模式,以达到最佳的设计效果。

结语
装饰器模式提供了一种灵活的方式来扩展对象的功能,而无需改变对象的结构。通过本文的深入分析,希望读者能够对装饰器模式有更全面的理解,并在实际开发中做出合理的设计选择。
相关Java设计模式文章:
Java二十三种设计模式-单例模式(1/23)
Java二十三种设计模式-工厂方法模式(2/23)
Java二十三种设计模式-抽象工厂模式(3/23)
Java二十三种设计模式-建造者模式(4/23)
Java二十三种设计模式-原型模式(5/23)
Java二十三种设计模式-适配器模式(6/23)
Java二十三种设计模式-装饰器模式(7/23)
相关文章:
Java二十三种设计模式-装饰器模式(7/23)
装饰器模式:动态扩展功能的灵活之选 引言 装饰器模式(Decorator Pattern)是一种结构型设计模式,用于在不修改对象自身的基础上,通过添加额外的职责来扩展对象的功能。 基础知识,java设计模式总体来说设计…...
正则表达式与文本处理
目录 一、正则表达式 1、正则表达式定义 1.1正则表达式的概念及作用 1.2、正则表达式的工具 1.3、正则表达式的组成 2、基础正则表达式 3、扩展正则表达式 4、元字符操作 4.1、查找特定字符 4.2、利用中括号“[]”来查找集合字符 4.3、查找行首“^”与行尾字符“$”…...
Python | Leetcode Python题解之第283题移动零
题目: 题解: class Solution:def moveZeroes(self, nums: List[int]) -> None:n len(nums)left right 0while right < n:if nums[right] ! 0:nums[left], nums[right] nums[right], nums[left]left 1right 1...
微信小程序面试题汇总
面试题 1. 请简述微信小程序主要目录和文件的作用? 参考回答: 微信小程序主要目录和文件的作用:(1)project.config.json:项目配置文件,用的最多的就是配置是否开启https校验 (2&am…...
学习日志:JVM垃圾回收
文章目录 前言一、堆空间的基本结构二、内存分配和回收原则对象优先在 Eden 区分配大对象直接进入老年代长期存活的对象将进入老年代主要进行 gc 的区域空间分配担保 三、死亡对象判断方法引用计数法可达性分析算法引用类型总结1.强引用(StrongReference…...
Vue前端页面嵌入mermaid图表--流程图
一、安装Mermaid 首先,你需要在你的项目中安装Mermaid。可以通过npm或yarn来安装: npm install mermaid --save # 或者 yarn add mermaid结果如图: 二、Vue 方法一:使用pre标签 使用ref属性可以帮助你在Vue组件中访问DOM元素 …...
【web]-反序列化-easy ? not easy
打开后看到源码 <?php error_reporting(0); highlight_file(__FILE__);class A{public $class;public $para;public $check;public function __construct(){$this->class "B";$this->para "ctfer";echo new $this->class ($this->para…...
python 内置函数、math模块
一、内置函数 内置函数是 Python 解释器内置的一组函数,它们可以直接在 Python 程序中使用,无需额外导入模块。这些内置函数提供了基本的操作和功能,涵盖了广泛的用途,从数学运算到数据结构操作等等。 import mathprint(type(10)…...
Ubuntu Docker 安装
Ubuntu Docker 安装 1. 引言 Docker 是一个开源的应用容器引擎,它允许开发者打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。 2. 系统要求 在安装 Docker 之前,…...
vue接入google map自定义marker教程
需求背景 由于客户需求,原来系统接入的高德地图,他们不接受,需要换成google地图。然后就各种百度,各种Google,却不能实现。----无语,就连google地图官方的api也是一坨S-H-I。所以才出现这篇文章。 google地…...
Spring Boot集成Redis与Lua脚本:构建高效的分布式多规则限流系统
文章目录 Redis多规则限流和防重复提交记录访问次数解决临界值访问问题实现多规则限流先确定最终需要的效果编写注解(RateLimiter,RateRule)拦截注解 RateLimiter 编写lua脚本UUID时间戳编写 AOP 拦截 总结 Redis多规则限流和防重复提交 市面…...
四、单线程多路IO复用+多线程业务工作池
文章目录 一、前言1 编译方法 二、单线程多路IO复用多线程业务工作池结构三、重写Client_Context类四、编写Server类 一、前言 我们以及讲完单线程多路IO复用 以及任务调度与执行的C线程池,接下来我们就给他结合起来。 由于项目变大,尝试解耦项目&#…...
单元测试--Junit
Junit是Java的单元测试框架提供了一些注解方便我们进行单元测试 1. 常用注解 常用注解: TestBeforeAll,AfterAllBeforeEach,AfterEach 使用这些注解需要先引入依赖: <dependency><groupId>org.junit.jupiter<…...
达梦数据库系列—30. DTS迁移Mysql到DM
目录 1.MySQL 源端信息 2.DM 目的端信息 3.迁移评估 4.数据库迁移 4.1源端 MySQL 准备 4.2目的端达梦准备 初始化参数设置 兼容性参数设置 创建迁移用户和表空间 4.3迁移步骤 创建迁移 配置迁移对象及策略 开始迁移 对象补迁 5.数据校验 统计 MySQL 端对象及数…...
随记0000——从0、1 到 C语言
C语言的发展历程是计算机科学史上的一个重要里程碑。 下面是从最早的机器语言到汇编语言,再到高级语言如 C 语言的简化演进过程: 1. 机器语言 定义与特点 机器语言是最底层的编程语言,由一系列二进制代码组成。直接被CPU执行,…...
C++ | Leetcode C++题解之第264题丑数II
题目: 题解: class Solution { public:int nthUglyNumber(int n) {vector<int> dp(n 1);dp[1] 1;int p2 1, p3 1, p5 1;for (int i 2; i < n; i) {int num2 dp[p2] * 2, num3 dp[p3] * 3, num5 dp[p5] * 5;dp[i] min(min(num2, num3…...
前端系列-8 集中式状态管理工具pinia
集中式状态管理工具—pinia vue3中使用pinia作为集中式状态管理工具,替代vue2中的vuex。 pinia文档可参考: https://pinia.web3doc.top/introduction.html 1.项目集成pinia 安装pinia依赖: npm install pinia在main.ts中引入pinia import { createApp } from vu…...
pytest使用
主要技术内容 1.pytest设计 接口测试 框架设想 common—公共的东西封装 1.request请求 2.Session 3.断言 4.Log 5.全局变量 6.shell命令 ❖ config---配置文件及读取 ❖ Log— ❖ payload—请求参数—*.yaml及读取 ❖ testcases—conftest.py; testcase1.py…….可…...
单表查询总结与多表查询概述
1. 单表查询总结 执行顺序: 从一张表,过滤数据,进行分组,对分组后的数据再过滤,查询出来所需数据,排序之后输出; from > where > group by > having > select > order by 2. …...
redis的使用场景和持久化方式
redis的使用场景 热点数据的缓存。热点:频繁读取的数据。限时任务的操作:短信验证码。完成session共享的问题完成分布式锁。 redis的持久化方式 什么是持久化:把内存中的数据存储到磁盘的过程,同时也可以把磁盘中的数据加载到内存…...
解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...
论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving
地址:LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂,正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...
【堆垛策略】设计方法
堆垛策略的设计是积木堆叠系统的核心,直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法,涵盖基础规则、优化算法和容错机制: 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则: 大尺寸/重量积木在下…...
Sklearn 机器学习 缺失值处理 获取填充失值的统计值
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 使用 Scikit-learn 处理缺失值并提取填充统计信息的完整指南 在机器学习项目中,数据清…...
