Java设计模式——装饰模式
目录
模式动机
模式定义
模式结构
类图
代码分析
示例:动态添加功能的流
组件接口
具体组件
装饰抽象类
具体装饰类
客户端
模式分析
核心思想
动态扩展功能
组合优于继承
优点
动态扩展功能
组合优于继承
代码复用性高
符合开闭原则
缺点
增加系统的复杂性
类的膨胀
复杂的调试
适用环境
动态扩展功能
避免继承带来的类爆炸性增长
高度可定制化的需求
模式应用
输入输出流
GUI 组件
日志记录
模式扩展
多层次装饰
结合其他设计模式
总结
模式动机
一般有两种方式可以实现给一个类或对象增加行为:
- 继承机制,使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。
- 关联机制,即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)
装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。这就是装饰模式的模式动机。
模式定义
装饰(Decorator)模式是一种结构型设计模式,它允许在运行时动态地给一个对象添加新的职责或行为,而无需修改原有的类结构。装饰模式的核心思想是在不改变原有对象的基础上,通过组合的方式将新的职责附加到对象上。
模式结构
装饰模式包含以下几个主要角色:
- Component(组件接口):定义了被装饰对象的接口。
- ConcreteComponent(具体组件):实现了组件接口,定义了具体的组件对象。
- Decorator(装饰抽象类):也实现了组件接口,持有一个对组件对象的引用,并通过组合的方式将新的职责附加到组件对象上。
- ConcreteDecorator(具体装饰类):实现了装饰抽象类,提供了具体的装饰行为。
类图
代码分析
示例:动态添加功能的流
假设我们正在开发一个文本处理系统,需要支持多种文本处理功能,如加密、压缩等。我们可以使用装饰模式来实现这一需求。
组件接口
// 组件接口
interface Stream {void write(String data);
}
具体组件
// 具体组件:文件流
class FileStream implements Stream {@Overridepublic void write(String data) {System.out.println("Writing data to file: " + data);}
}
装饰抽象类
// 装饰抽象类
class StreamDecorator implements Stream {protected Stream stream;public StreamDecorator(Stream stream) {this.stream = stream;}@Overridepublic void write(String data) {stream.write(data);}
}
具体装饰类
// 具体装饰类:加密流
class EncryptedStream extends StreamDecorator {public EncryptedStream(Stream stream) {super(stream);}@Overridepublic void write(String data) {String encryptedData = encrypt(data);super.write(encryptedData);}private String encrypt(String data) {// 加密逻辑return "Encrypted: " + data;}
}// 具体装饰类:压缩流
class CompressedStream extends StreamDecorator {public CompressedStream(Stream stream) {super(stream);}@Overridepublic void write(String data) {String compressedData = compress(data);super.write(compressedData);}private String compress(String data) {// 压缩逻辑return "Compressed: " + data;}
}
客户端
public class DecoratorPatternDemo {public static void main(String[] args) {Stream stream = new FileStream();Stream encryptedStream = new EncryptedStream(stream);Stream compressedStream = new CompressedStream(encryptedStream);compressedStream.write("Hello, World!");}
}
模式分析
核心思想
装饰模式的核心思想是在不改变对象自身的基础上,通过组合的方式将新的职责附加到对象上。装饰模式允许在运行时动态地给一个对象添加新的职责或行为,而无需修改原有的类结构。
动态扩展功能
装饰模式通过装饰抽象类和具体装饰类,可以在运行时动态地扩展对象的功能。具体装饰类通过继承装饰抽象类,并重写其中的方法来添加新的行为。通过这种方式,可以在不修改原有对象的基础上,灵活地扩展对象的功能。
组合优于继承
装饰模式通过组合的方式扩展对象的功能,而不是通过继承。这种方式避免了继承带来的类爆炸性增长问题,并且使得系统更加灵活和可扩展。
优点
动态扩展功能
装饰模式允许在运行时动态地给一个对象添加新的职责或行为,而无需修改原有的类结构。通过装饰模式,可以在不改变对象自身的基础上,灵活地扩展对象的功能。
组合优于继承
装饰模式通过组合的方式扩展对象的功能,而不是通过继承。这种方式避免了继承带来的类爆炸性增长问题,并且使得系统更加灵活和可扩展。
代码复用性高
装饰模式通过组合的方式扩展对象的功能,可以复用已有的组件类和装饰类,从而提高代码的复用性。
符合开闭原则
装饰模式符合开闭原则,即对扩展开放,对修改关闭。通过引入装饰抽象类和具体装饰类,可以在不修改现有代码的情况下添加新的装饰类,从而扩展系统的功能。
缺点
增加系统的复杂性
装饰模式会增加系统的复杂性,因为它引入了额外的抽象类和具体装饰类。对于简单的系统,使用装饰模式可能会显得过于复杂。
类的膨胀
如果需要添加的装饰类很多,可能会导致装饰类的数量增加,从而增加系统的维护成本。
复杂的调试
由于装饰模式通过组合的方式扩展对象的功能,可能会导致对象的层次结构变得复杂,从而增加调试的难度。
适用环境
动态扩展功能
当需要在运行时动态地给一个对象添加新的职责或行为时,可以使用装饰模式。通过装饰模式,可以在不修改对象自身的基础上,灵活地扩展对象的功能。
避免继承带来的类爆炸性增长
当需要扩展对象的功能,但不想使用继承机制时,可以使用装饰模式。通过装饰模式,可以避免继承带来的类爆炸性增长问题,并且使得系统更加灵活和可扩展。
高度可定制化的需求
当需要高度可定制化的需求时,可以使用装饰模式。通过装饰模式,可以在运行时根据用户的需要动态地添加或移除对象的功能。
模式应用
输入输出流
在 Java 中,输入输出流的实现就使用了装饰模式。例如,BufferedInputStream 和 DataInputStream 都是 InputStream 的装饰类,它们通过组合的方式扩展了 InputStream 的功能。
InputStream inputStream = new FileInputStream("file.txt");
InputStream bufferedInputStream = new BufferedInputStream(inputStream);
InputStream dataInputStream = new DataInputStream(bufferedInputStream);
GUI 组件
在图形用户界面(GUI)中,组件的扩展也经常使用装饰模式。例如,一个按钮组件可以通过装饰模式添加新的功能,如添加背景颜色、边框等。
Button button = new Button("Click me");
Button decoratedButton = new BackgroundColorButton(button, Color.BLUE);
Button finalButton = new BorderButton(decoratedButton, BorderStyle.SOLID);
日志记录
在日志记录系统中,可以通过装饰模式动态地添加新的日志记录方式。例如,可以将日志记录到文件、数据库或网络服务器。
Logger logger = new FileLogger();
Logger decoratedLogger = new DatabaseLogger(logger);
Logger finalLogger = new NetworkLogger(decoratedLogger);
模式扩展
多层次装饰
在某些情况下,可能需要多层次的装饰。例如,一个文本处理系统可能需要支持多种文本处理功能,如加密、压缩、编码转换等。通过多层次的装饰,可以将这些不同的功能组合在一起,从而实现高度的灵活性和可扩展性。
Stream stream = new FileStream();
Stream encryptedStream = new EncryptedStream(stream);
Stream compressedStream = new CompressedStream(encryptedStream);
Stream encodedStream = new EncodedStream(compressedStream);
结合其他设计模式
装饰模式可以与其他设计模式结合使用,以进一步提高系统的灵活性和可扩展性。例如:
- 工厂模式:可以使用工厂模式来创建组件对象和装饰对象,从而进一步提高系统的灵活性。
- 策略模式:可以在装饰模式的基础上使用策略模式,动态地选择不同的装饰策略。
- 代理模式:可以在装饰模式的基础上使用代理模式,进一步增强对象的功能。
总结
- 装饰模式用于动态地给一个对象增加一些额外的职责,就增加对象功 能来说,装饰模式比生成子类实现更为灵活。它是一种对象结构型模 式。
- 装饰模式包含四个角色:抽象构件定义了对象的接口,可以给这些对 象动态增加职责(方法);具体构件定义了具体的构件对象,实现了 在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法); 抽象装饰类是抽象构件类的子类,用于给具体构件增加职责,但是具 体职责在其子类中实现;具体装饰类是抽象装饰类的子类,负责向构 件添加新的职责。
- 使用装饰模式来实现扩展比继承更加灵活,它以对客户透明的方式动 态地给一个对象附加更多的责任。装饰模式可以在不需要创造更多子 类的情况下,将对象的功能加以扩展。
- 装饰模式的主要优点在于可以提供比继承更多的灵活性,可以通过一种动态的 方式来扩展一个对象的功能,并通过使用不同的具体装饰类以及这些装饰类的 排列组合,可以创造出很多不同行为的组合,而且具体构件类与具体装饰类可 以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类;其主要缺 点在于使用装饰模式进行系统设计时将产生很多小对象,而且装饰模式比继承 更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需 要逐级排查,较为烦琐。
- 装饰模式适用情况包括:在不影响其他对象的情况下,以动态、透明的方式给 单个对象添加职责;需要动态地给一个对象增加功能,这些功能也可以动态地 被撤销;当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展 和维护时。
- 装饰模式可分为透明装饰模式和半透明装饰模式:在透明装饰模式中,要求客 户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该声明具体构 件类型和具体装饰类型,而应该全部声明为抽象构件类型;半透明装饰模式允 许用户在客户端声明具体装饰者类型的对象,调用在具体装饰者中新增的方法。
相关文章:
Java设计模式——装饰模式
目录 模式动机 模式定义 模式结构 类图 代码分析 示例:动态添加功能的流 组件接口 具体组件 装饰抽象类 具体装饰类 客户端 模式分析 核心思想 动态扩展功能 组合优于继承 优点 动态扩展功能 组合优于继承 代码复用性高 符合开闭原则 缺点 增加…...
【TouchSocket 和 client.GetStream 区别】
TouchSocket 和 client.GetStream() 是用于网络通信的不同工具和方法,但它们的功能层面和适用范围也有明显区别。下面我来详细解释 TouchSocket 和 client.GetStream() 的差异。 1. TouchSocket TouchSocket 是一个完整的 网络通信框架,专注于为开发者…...
怎么利用商品详情API接口实现数据获取与应用?
在当今数字化的商业时代,高效获取和利用商品数据对于企业和开发者来说至关重要。商品详情 API 接口为我们提供了一种便捷的方式来获取丰富的商品信息,从而实现各种有价值的应用。本文将深入探讨如何利用商品详情 API 接口实现数据获取与应用。 一、商品…...
【AGC005D】~K Perm Counting(计数抽象成图)
容斥原理。 求出f(m) ,f(m)指代至少有m个位置不合法的方案数。 怎么求? 注意到位置为id,权值为v ,不合法的情况,当且仅当 v idk或 v id-k 因此,我们把每一个位置和权值抽象成点 ,不合法的情况之间连一…...
【React】setState (useState) 是怎么记住上一个状态值的?
在 React 中,setState 通过 React 内部的状态管理机制来记住上一个状态值。即使每次组件重新渲染时,函数组件会被重新执行,React 仍能通过其内部的状态管理系统保持和追踪组件的状态变化。下面详细解释其工作原理: 1. setState 的…...
Vue3 使用CryptoJS加密
为什么要加密? 现在的互联网世界充满了各种各样的信息,有些信息非常重要,比如密码、个人信息等。如果我们把这些信息直接发送到服务器,别人可能会截取到,然后偷走我们的信息。为了避免这种情况发生,我们需…...
Feign的使用
一、Feign 介绍 Feign 是一个声明式的 HTTP 客户端,它使得编写 HTTP 客户端变得更加简单。在微服务架构中,使用 Feign 可以轻松地调用其他服务。Feign 内置了 Ribbon 实现负载均衡。 二、Feign 的使用步骤 引入依赖: 在项目的 pom.xml 文件…...
前端反接保护:实用方案解析与探讨
前端反接保护通常采用肖特基二极管方案或PMOS/NMOS方案,本文另外介绍一种理想二极管方案。 1、肖特基二极管方案 由于肖特基二极管具有正向导通电压,只能用于小电流场合,甚至于直接使用普通的整流二极管。比如1A电流,设D1的正向…...
【C++】第五节:内存管理
1、C/C内存分布 看下面一段代码 int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] { 1, 2, 3, 4 };char char2[] "abcd";const char* pChar3 "abcd";int* ptr1 (int*)malloc(s…...
【Java SE】方法 和 递归 的应用
🔥博客主页🔥:【 坊钰_CSDN博客 】 欢迎各位点赞👍评论✍收藏⭐ 目录 1. 方法的含义 1.1 例子 1.2 方法的概念 2. 方法的定义 3. 实参和形参 3.1 实参和形参的关系 4. 方法的重载 5. 递归 5.1 递归练习 6. 小结 1. 方法的…...
JVS低代码轻应用是什么?是如何拼装的?这篇文章讲的非常详细
1.1JVS轻应用是什么? 轻应用与传统应用的开发过程区别 传统开发(原生开发)采用的方式:①需求了解 ②产品原型③UI设计④建库建表⑤前端还原⑥后端开发⑦前后端联调⑧功能测试⑨部署上线轻应用开发方式(配置化拼装&…...
K210(openMV)与STM32 通信教程
目录 前言: 一、K210 串口部分教程 二、STM32部分 前言: 很多打比赛的同学,通常只是用K210 或者openMV来进行视觉部分的信息采集,传输数据给STM32(或者其他主控那边)进行对分析,对小车或者舵…...
【HarmonyOS】HMRouter使用详解(三)生命周期
生命周期(Lifecycle) 使用HMRouter的页面跳转时,想实现和Navigation一样的生命周期时,需要通过新建生命周期类来实现对页面对某一个生命周期的监控。 新建Lifecycle类 通过继承IHMLifecycle接口实现生命周期接口的方法重写。 通过…...
Docker 教程三 (Ubuntu Docker安装)
Ubuntu Docker 安装 Docker Engine-Community 支持以下的 Ubuntu 版本: Xenial 16.04 (LTS)Bionic 18.04 (LTS)Cosmic 18.10Disco 19.04 其他更新的版本…… Docker Engine - Community 支持上 x86_64(或 amd64)armhf,arm64&am…...
Redis:持久化
Redis:持久化 持久化RDBdump.rdb优缺点 AOF文件同步重写机制 混合持久化 持久化 虽然Redis是一个内存级别的数据库,但是Redis也是有持久化的能力的。当系统崩溃时,Redis就会被强制退出,此时内存中的数据就会丢失。为了能够在下次…...
精准监控,高效运营 —— 商品信息实时分析为商家带来新机遇
在现代商业环境中,精准监控和高效运营是商家成功的关键。通过实时分析商品信息,商家可以洞察市场趋势、优化库存管理、提升销售策略,从而抓住新的商业机遇。本文将介绍如何利用Python和一些流行的数据分析工具来实现商品信息的实时分析&#…...
Nginx应用配置实战
Nginx通用部署 Nginx常见参数介绍 Nginx 配置文件中的指令和参数决定了它的行为。下面详细介绍一些常见的 Nginx 参数,以帮助你更好地理解和配置 Nginx。 1. worker_processes worker_processes auto;作用:设置 Nginx 处理请求的工作进程数量。auto …...
html实现倒计时
参考网址 <!DOCTYPE html> <html> <head><title>倒计时示例</title> </head> <body><h1 id"titleCountDown"></h1><div id"countdown"></div><script>// 目标日期var targetDat…...
HTMLCSS练习
1) 效果如下 2) 代码如下 2.1) HTML <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" conte…...
LeetCode讲解篇之377. 组合总和 Ⅳ
文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 总和为target的元素组合个数 可以由 总和为target - nums[j]的元素组合个数 转换而来,其中j为nums所有元素的下标 而总和target - nums[j]的元素组合个数 可以由 总和为target - nums[j] - nums[k]的…...
两阶段目标检测器核心原理与流程详解
两阶段目标检测器的核心思想是:第一阶段先找候选区域,第二阶段再对候选区域做分类和精修。典型代表是: R-CNN Fast R-CNN Faster R-CNN Mask R-CNN现在最典型的是 Faster R-CNN / Mask R-CNN,所以我以它为主来讲。1. 两阶段目标检…...
NTN 长距离通信领域亮相
核心蜂窝解决方案亮相并带来Nordic NTN 核心解决方案深度分享。环节将全面解析 nRF9151 模组的核心特性与技术优势,详解卫星星座生态布局及 nRFCloud 平台的应用价值,为参会者勾勒 NTN 技术的整体框架与商业落地前景,为后续内容奠定专业基础。…...
BSS138I现货供应
在当今快速发展的电子行业中,BSS138I MOSFET作为一款广受欢迎的小信号N沟道MOSFET,因其低导通电阻、高可靠性和紧凑的SOT-23封装而备受青睐。对于需要采购BSS138I的客户来说,选择一个可靠的供应商至关重要。本文将深入探讨为何深圳市粤科源兴…...
SmartDock:让Android设备拥有桌面级生产力的智能启动器
SmartDock:让Android设备拥有桌面级生产力的智能启动器 【免费下载链接】smartdock A user-friendly desktop mode launcher that offers a modern and customizable user interface 项目地址: https://gitcode.com/gh_mirrors/smar/smartdock 你是否曾经想过…...
从FFT到CZT:解锁频谱细化的精准分析新维度
1. 为什么我们需要频谱细化? 在信号处理的世界里,傅里叶变换(FFT)就像是一把瑞士军刀,几乎每个工程师都会用它来分析信号的频率成分。但当你面对两个频率非常接近的信号时,FFT就显得力不从心了。我曾在一次…...
NotebookLM期刊推荐矩阵(含影响因子、APC费用、AI政策条款、平均一审周期——仅限本周开放下载)
更多请点击: https://intelliparadigm.com 第一章:NotebookLM期刊推荐矩阵(含影响因子、APC费用、AI政策条款、平均一审周期——仅限本周开放下载) NotebookLM 作为 Google 推出的 AI 增强型研究协作者,近期正式开放其…...
告别硬编码延时!用Vector CAPL定时器实现汽车总线报文精准周期发送
告别硬编码延时!用Vector CAPL定时器实现汽车总线报文精准周期发送 在汽车电子测试领域,CAN、LIN等总线报文的周期发送是验证ECU功能的基础需求。传统脚本常依赖delay()或硬编码等待,不仅难以维护,更会因系统调度导致时序漂移。本…...
避坑指南:LVGL Bar控件在RTOS和低内存MCU上的5个常见问题与解决方案
避坑指南:LVGL Bar控件在RTOS和低内存MCU上的5个常见问题与解决方案 在嵌入式开发中,LVGL作为轻量级图形库被广泛应用,但其Bar控件(进度条)在资源受限环境(如FreeRTOS、内存<64KB的MCU)下常出…...
别再乱画了!GD32/STM32复位与唤醒按键电路设计,90%新手会踩的坑
GD32/STM32复位与唤醒按键电路设计避坑指南 1. 复位电路设计的核心误区与解决方案 许多工程师在设计GD32/STM32复位电路时,往往低估了RC时间常数的重要性。我曾亲眼见过一个团队花费两周时间排查系统随机重启问题,最终发现竟是复位电路中一个10kΩ电阻被…...
告别虚拟机卡顿:在VMware 17上为RHEL 9.2分配CPU和内存的黄金法则
告别虚拟机卡顿:在VMware 17上为RHEL 9.2分配CPU和内存的黄金法则 当你在VMware Workstation 17上运行RHEL 9.2时,是否经常遇到编译速度慢、桌面响应延迟甚至整个系统卡死的情况?这很可能是因为你没有根据宿主机的实际硬件情况科学分配虚拟资…...
