当前位置: 首页 > news >正文

《程序猿之设计模式实战 · 装饰者模式》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗

🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

      • 写在前面的话
      • 基础介绍
      • 代码实现
      • Spring 使用装饰者模式
      • 动态代理VS装饰者模式
      • 装饰者模式的简化
      • 总结陈词

写在前面的话

上一篇文章《程序猿之设计模式实战 · 策略模式》介绍的了策略模式的实际运用,这篇紧随其后,补充上装饰者模式。

装饰者模式也是相当实用的,适合很多场景,且听慢慢道来。


基础介绍

基础概念:

装饰者模式是一种结构型设计模式,它允许在不改变对象自身的情况下,动态地给对象添加新的功能。通过将功能封装在装饰类中,装饰者模式提供了一种灵活的方式来扩展对象的行为。

装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不使用创造更多子类的情况下,将对象的功能加以扩展。

类图:

组成部分:

装饰者模式通常由以下几个部分组成:

1、组件接口(Component):定义一个接口,声明具体组件和装饰者都需要实现的方法。

2、具体组件(ConcreteComponent):定义一个将要接收附加责任的类,实现组件接口,表示被装饰的对象。

3、装饰者(Decorator):也是实现组件接口的类,持有一个组件对象的引用,并在其方法中调用该组件的方法。装饰者可以在调用前后添加额外的功能。

4、具体装饰者(ConcreteDecorator):继承自装饰者类,具体实现添加的功能。

常用场景:

装饰者模式适用于以下场景:

1、需要动态添加功能:当你希望在运行时为对象添加功能,而不影响其他对象时。

2、避免子类爆炸:当功能组合的数量较多时,使用装饰者模式可以避免创建大量的子类。

3、增强类的功能:当你希望在不修改现有类的情况下,增强类的功能。


代码实现

示例:以文本输出,添加粗体、斜体的职责为例。

Step1、定义组件接口

public interface Text {String getContent();
}

Step2、定义具体组件

public class BaseText implements Text {private final String content;public BaseText(String content) {this.content = content;}@Overridepublic String getContent() {return content;}
}

Step3、定义装饰者

public abstract class TextDecorator implements Text {protected Text text;public TextDecorator(Text text) {this.text = text;}
}

Step4、定义具体装饰者

public class BoldTextDecorator extends TextDecorator {public BoldTextDecorator(Text text) {super(text);}@Overridepublic String getContent() {return "<b>" + text.getContent() + "</b>";}
}public class ItalicTextDecorator extends TextDecorator {public ItalicTextDecorator(Text text1) {super(text1);}@Overridepublic String getContent() {return "<i>" + text.getContent() + "</i>";}
}

Step5、客户端测试

public class DecoratorClient {public static void main(String[] args) {Text plainText = new BaseText("Hello, 战神!");// 添加粗体装饰Text boldTextDecorator = new BoldTextDecorator(plainText);System.out.println(boldTextDecorator.getContent());// 输出内容:// <b>Hello, 战神!</b>// 添加斜体装饰Text italicTextDecorator = new ItalicTextDecorator(boldTextDecorator);System.out.println(italicTextDecorator.getContent());// 输出内容:// <i><b>Hello, 战神!</b></i>}
}

点评一下:

从代码看,还是挺清晰的,各司其职。

可以理解为不影响原有功能,动态进行功能的增强就是装饰者模式。

特别适合解决例如购物、点餐、SKU等组合的情况,即容易出现“类爆炸”的场合。


Spring 使用装饰者模式

在 Spring 框架中,装饰者模式被广泛应用于以下几个方面:

1、AOP(面向切面编程):

Spring AOP 使用代理模式来实现切面功能,实际上可以看作是对目标对象的装饰。通过在目标对象周围添加切面逻辑(如事务管理、日志记录等),而不改变目标对象的代码。

2、Spring MVC 的 HandlerInterceptor:

在 Spring MVC 中,HandlerInterceptor 可以被视为一种装饰者模式的实现。它允许在请求处理的不同阶段(如请求前、请求后)添加额外的处理逻辑,而不需要修改控制器本身的代码。

3、BeanPostProcessor:

Spring 的 BeanPostProcessor 接口允许在 Spring 容器创建和初始化 bean 之后,添加额外的功能或修改 bean 的属性。这种机制也可以看作是对 bean 的装饰。

总结:装饰者模式是一种灵活的设计模式,允许在运行时动态地为对象添加功能。在 Spring 框架中,装饰者模式的思想被广泛应用于 AOP、拦截器和 bean 处理等多个方面,使得功能的扩展变得更加灵活和可维护。


动态代理VS装饰者模式

相似之处:

细心的伙伴可能观察到,日常接触的动态代理,是否装饰者模式虽然有相似之处。

1、从增强功能上看,两者都可以在不修改原有类的情况下,为对象添加额外的功能或行为。

2、从代码结构上看:都涉及到对原有对象的封装,通常通过组合或代理的方式来实现。

但其实两者还是存在区别:

1、实现方式:

装饰者模式:通常通过创建一个装饰者类来包装原有对象,装饰者类实现与原有对象相同的接口,并在其方法中调用原有对象的方法,同时添加新的行为。

动态代理:使用反射机制在运行时创建一个代理对象,该代理对象可以在方法调用时添加额外的逻辑。动态代理不需要创建具体的装饰类,而是通过代理类来增强功能。

2、使用场景:

装饰者模式:适用于需要在多个地方以不同方式增强对象的场景,通常是为了增加对象的功能。

动态代理:适用于需要在运行时决定增强逻辑的场景,比如 AOP(面向切面编程)中的横切关注点。

3、灵活性:

装饰者模式:需要在编译时确定装饰的组合,通常是静态的。

动态代理:可以在运行时动态决定增强的逻辑,具有更高的灵活性。

总结一下:

动态代理可以被视为一种特殊的装饰者模式实现,但它更侧重于运行时的动态性和灵活性,而装饰者模式则更关注于通过组合来增强对象的功能。因此,虽然它们有相似的目的,但在实现和使用上有明显的区别。


装饰者模式的简化

实际开发中,往往场景不会都是这么复杂(没这么多元素),此时可以考虑简化。

Tips:实战要灵活,学会变通,而不要一味追求标准、套用标准。

如果只有一个待装饰的类ConcreteComponent,那么可以考虑去掉抽象的 Component 类(接口),把Decorator作为一个ConcreteComponent子类。

如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。

是不是很简洁,不过怎么越看越像JDK动态代理?

万变不如其宗,根源应该靠面向接口编程以及持有被代理/装饰的对象吧。


总结陈词

还是那句话,不用过多的纠结在用的是哪个设计模式,实现的是什么标准。

遇到实际问题能使用合适的方式解决,同时代码经得起推敲和扩展,才是最主要的。

回到装饰者模式,常用于解决“类爆炸”的问题,动态附加功能,同时基本满足面向对象开发原则的全部原则。

不过,装饰模式也有相应的缺点,由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。

当然,装饰者还是很强的,遇到适合的场景果断用上,缺点可以忽略不计。

还是那句话,你可以不用,但不能不会。

💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

相关文章:

《程序猿之设计模式实战 · 装饰者模式》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…...

[K8S]Forbidden: pod updates may not change fields other than

背景 在执行kubectl apply -f pod-nginx.yaml的时候报错 The Pod "nginx-test" is invalid: spec: Forbidden: pod updates may not change fields other than `spec.containers[*].image`, `spec.initContainers[*].image`, `spec.activeDeadlineSeconds`, `spec.to…...

C/C++漏洞检测数据集汇总

漏洞检测这个方向最近几年尤为热门&#xff0c;尤其是与深度学习技术相结合的研究&#xff0c;同时一些公开可用的数据集的出现也进一步推动了这些技术的发展。本篇文章总结归纳了目前在 C/C 源代码漏洞检测方向的一些公开数据集以及相关文献。 1 Devign (FFmpegQemu) 简介&am…...

springboot后端开发-常见注解及其用途

文章目录 1. 组件注解2. 依赖注入注解3. 配置类注解4. 测试注解5. 控制器注解6. 安全和认证注解7. 切面相关注解8. API文档相关注解(需引入swagger)9. 其他注解 在Spring Boot框架中&#xff0c;有许多常用的注解用来简化开发过程中的依赖注入、组件扫描、配置、安全控制等方面…...

TypeScript 扩展

扩展 ?:可选参数 可选链事实上并不是TypeScript独有的特性&#xff0c;它是ES11&#xff08;ES2020&#xff09;中增加的特性 可选链使用可选链操作符 ? 作用是当对象的属性不存在时&#xff0c;会短路&#xff0c;直接返回undefined&#xff0c;如果存在&#xff0c;那么…...

按键学院往期视频

按键学院第五期 网游实战系列课程 按键学院第四期 网游实战系列课程01-回合制网游的特点:测试游戏后台按键图色 网游实战系列课程02-神武新手任务的识别与处理:字库识别任务 网游实战系列课程03-神武自动组队与攻击 网游实战系列课程04-神武自动逛地图与攻击 网游实战系列课程0…...

通信工程学习:什么是MRF多媒体资源功能、MRFC多媒体资源功能控制、MRFP多媒体资源功能处理

一、MRF多媒体资源功能 MRF&#xff08;Multimedia Resource Function&#xff0c;多媒体资源功能&#xff09;是3G/IMS网络中定义的提供多媒体资源功能的网络实体&#xff0c;它为3G/IMS网络的业务和承载提供媒体能力支持。MRF通过提供丰富的媒体处理功能&#xff0c;如播放声…...

【Windows】获取进程缓解策略设置情况

目录 一、前言 二、主要概念 三、实现步骤 四、总结 原文出处链接&#xff1a;[https://blog.csdn.net/qq_59075481/article/details/142234952] 一、前言 在现代操作系统中&#xff0c;进程缓解策略&#xff08;Process Mitigation Policy&#xff09;提供了一种防御机制…...

语音识别相关概念

声音如何保存成数字信号&#xff1f; 声音是听觉对声波产生的感知&#xff0c;而声波是一种在时间和振幅上连续的模拟量&#xff0c;本质是介质的振动&#xff0c;&#xff0c;比如空气的振动。那么只需要把这个振动信号记录下来&#xff0c;并用一串数字来表达振动信号振动的…...

Iceberg与SparkSQL查询操作整合

前言 spark操作iceberg之前先要配置spark catalogs,详情参考Iceberg与Spark整合环境配置。 Iceberg使用Apache Spark的DataSourceV2 API来实现数据源和catalog。 使用SQL查询 查询的时候表要按照:catalog.数据库.表名的格式 SELECT * FROM prod.db.table; -- catalog: p…...

Linux 上安装 PostgreSQL

Linux 上安装 PostgreSQL PostgreSQL 是一款功能强大的开源关系数据库管理系统,因其稳定性、可扩展性和先进的功能而广受欢迎。在 Linux 系统上安装 PostgreSQL 是一个相对直接的过程,但具体步骤可能会因您使用的 Linux 发行版而异。本文将介绍在几种流行的 Linux 发行版上安…...

WRF-LES与PALM微尺度气象大涡模拟、PALM静态数据预备、PALM驱动数据预报、PALM模拟

查看原文>>>WRF-LES与PALM微尺度气象大涡模拟及ChatGPT在大气科学领域应用 针对微尺度气象的复杂性&#xff0c;大涡模拟&#xff08;LES&#xff09;提供了一种无可比拟的解决方案。微尺度气象学涉及对小范围内的大气过程进行精确模拟&#xff0c;这些过程往往与天气…...

需求分析概述

为什么要进行需求分析呢&#xff1f; 笑话&#xff1a;富翁娶妻 某富翁想要娶老婆&#xff0c;有三个人选&#xff0c;富翁给了三个女孩各一千元&#xff0c;请 她们把房间装满。第一个女孩买了很多棉花&#xff0c;装满房间的1/2。第 二个女孩买了很多气球&#xff0c;装满…...

Java | Leetcode Java题解之第391题完美矩形

题目&#xff1a; 题解&#xff1a; class Solution {public boolean isRectangleCover(int[][] rectangles) {long area 0;int minX rectangles[0][0], minY rectangles[0][1], maxX rectangles[0][2], maxY rectangles[0][3];Map<Point, Integer> cnt new HashM…...

java项目之基于web的人力资源管理系统的设计与实现(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的基于web的人力资源管理系统的设计与实现。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; …...

Linux 防火墙:iptables (二)

文章目录 SNAT 原理与应用SNAT 应用环境SNAT 原理SNAT 转换前提条件SNAT 格式SNAT 转换规则配置 DNAT 原理与应用DNAT 应用环境DNAT 原理DNAT 转换前提条件DNAT 格式DNAT 转换规则配置 iptables 规则的备份和还原导出&#xff08;备份&#xff09;所有表的规则导入&#xff08;…...

小目标检测顶会新思路!最新成果刷爆遥感SOTA,参数小了18倍

遥感领域的小目标检测一直是个具有挑战性和趣味性的研究方向&#xff0c;同时也是顶会顶刊的常客。但不得不说&#xff0c;今年关于遥感小目标检测的研究热情尤其高涨&#xff0c;已经出现了很多非常优秀的成果。 比如SuperYOLO方法&#xff0c;通过融合多模态数据并执行高分辨…...

【Ubuntu】虚拟机安装USB摄像头ROS驱动 usb_cam(最新方法)

写在前面&#xff1a; &#x1f31f; 欢迎光临 清流君 的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落。&#x1f4dd; 个人主页&#xff1a;清流君_CSDN博客&#xff0c;期待与您一同探索 移动机器人 领域的无限可能。 &#x1f50d; 本文系 清流君 原创之作&…...

免费的成绩查询微信小程序,让家长轻松掌握学生表现

传统的教学方式在不断地被革新。在成绩查询这一环节&#xff0c;老师们曾经面临着繁琐的手工操作和信息安全的风险。可现如今有一个让成绩查询变得轻松、高效且安全的新工具——易查分。 过去需要花费大量时间来整理成绩&#xff0c;然后通过短信或者打电话的方式告知给家长。以…...

[含视频和源码]CRUD的最佳实践,联动前后端,包含微信小程序,API,HTML等(三)

关说不练假把式&#xff0c;在上一&#xff0c;二篇中介绍了我心目中的CRUD的样子 基于之前的理念&#xff0c;我开发了一个命名为PasteTemplate的项目&#xff0c;这个项目呢后续会转化成项目模板&#xff0c;转化成项目模板后&#xff0c;后续需要开发新的项目就可以基于这…...

如何把我另一个分支上的commit拿过来

在开源过程中&#xff0c;每一次PR都是要经过严格的review的&#xff0c;这期间可能会进行多次修改&#xff0c;补充提交&#xff0c;而且这一般来说不是一个很迅速的过程&#xff0c;此时我们可能会先往后进行开发。我一般会把项目分模块逐步建立分支&#xff0c;当前一个pr合…...

【rpg像素角色】俯视角-行走动画

制作像素角色的俯视角行走动画并不像看上去那么复杂&#xff0c;尤其是在你已经完成了角色的4个方向站立姿势之后&#xff08;其中左右方向可以通过水平翻转实现&#xff09;。接下来&#xff0c;我会一步步为你讲解如何制作行走动画。 1. 理解行走规律 在制作行走动画之前&am…...

Python时间序列分析新技能,轻松掌握时间索引

大家好&#xff0c;在数据分析领域&#xff0c;时间序列数据分析是一项非常重要的技能。Pandas作为Python中强大的数据处理库&#xff0c;在处理时间序列数据时提供了丰富的功能&#xff0c;其中时间索引的应用是时间序列分析中的关键。本文将介绍如何在Pandas中使用时间索引进…...

sklearn-逻辑回归-特征工程示例

sklearn-逻辑回归-特征工程示例 在实际应用场景中&#xff0c;有时候特征的数量会很多&#xff0c;我们出于业务考虑&#xff0c;也出于计算量的考虑&#xff0c;希望对逻辑回归进行特征选择来降维。比如在判断一个人是否会患乳腺癌的时候&#xff0c;医生如果看58个指标来确诊…...

RTMP播放器延迟最低可以做到多少?

技术背景 RTMP播放器的延迟可以受到多种因素的影响&#xff0c;包括网络状况、推流设置、播放器配置以及CDN分发等。因此&#xff0c;RTMP播放器的延迟并不是一个固定的数值&#xff0c;而是可以在一定范围内变化的。 正常情况下&#xff0c;网上大多看到的&#xff0c;针对R…...

细致刨析JDBC ① 基础篇

目录 一、JDBC概述 1.JDBC的概念 ​编辑2.JDBC的核心组成 ① 接口规范: ② 实现规范: 二、JDBC快速入门 1.JDBC搭建步骤 三、核心API理解 1.注册驱动 2.Connection 3.Statement 4.PreparedStatement 5.ResultSet 四、基于Preparedment实现CRUD 1.查询单行单列 2.查询单行…...

Reactive 编程-Loom 项目(虚拟线程)

Reactive 编程与 Loom 项目&#xff08;虚拟线程&#xff09; Java 项目 Loom 是 Oracle 在 JVM 上的一项重大变革&#xff0c;旨在引入 虚拟线程&#xff08;Virtual Threads&#xff09;&#xff0c;以简化并发编程。传统的 Java 线程是重量级的&#xff0c;由操作系统管理&…...

Windows下使用MinGW编译安装zmq的步骤

背景&#xff1a; 在开发过程中&#xff0c;需要使用zmq库进行数据交互&#xff0c;因此需要编译zmq库。 安装步骤 软件下载 https://github.com/zeromq/libzmq.git 下载&#xff0c;将代码切换到git checkout 4c6cff6391分支 软件编译 cd .\libzmq\ mkdir build cd .\bu…...

电商云账户分账系统:打造高效资金流转体系

在当今的电子商务时代&#xff0c;随着消费者购物习惯的转变和在线交易量的激增&#xff0c;电商平台的运营模式也日趋复杂。为了满足多商家共存、利益共享的需求&#xff0c;电商分账成为了一个至关重要的环节。 电商分账是指电商平台在销售商品或服务后&#xff0c;根据事先…...

设计模式 -- 单例设计模式

1.1 单例 创建一个单例对象 SingleModel , SingleModel 类有它的私有构造函数和本身的一个静态实例。 SingleModel 类提供了一个静态方法&#xff0c;供外界获取它的静态实例。 DesignTest 我们的演示类使用 SingleModel 类来获取 SingleModel 对象。 创建 Single…...