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

news2024/11/20 1:46:14

📢 大家好,我是 【战神刘玉栋】,有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;
    }

    @Override
    public 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);
    }

    @Override
    public String getContent() {
        return "<b>" + text.getContent() + "</b>";
    }
}

public class ItalicTextDecorator extends TextDecorator {
    public ItalicTextDecorator(Text text1) {
        super(text1);
    }

    @Override
    public 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动态代理?

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


总结陈词

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

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

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

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2132462.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python 求亲和数

亲和数&#xff08;Amicable Numbers&#xff09;是指两个不同的正整数&#xff0c;它们的真因数&#xff08;即除去本身的所有因数&#xff09;之和与对方的数相等。 def sum_of_proper_divisors(n):"""计算一个数的真因子之和"""divisors_su…

SpringBoot闲一品交易平台

SpringBoot闲一品交易平台 #vue项目实战 #计算机项目 #java项目 SpringBoot闲一品交易平台通过运用软件工程原理和开发方法&#xff0c;借助Spring Boot框架&#xff0c;旨在实现零食交易信息的高效管理&#xff0c;提升用户的购物体验和满意度。 技术栈 开发语言&#xff1a;…

用于安全研究的 Elastic Container Project

作者&#xff1a;来自 Elastic Andrew Pease•Colson Wilhoit•Derek Ditch 使用 Docker 启动 Elastic Stack 序言 Elastic Stack 是一个模块化数据分析生态系统。虽然这允许工程灵活性&#xff0c;但建立开发实例进行测试可能很麻烦。建立 Elastic Stack 的最简单方法是使用…

Day09-StatefuleSet控制器

Day09-StatefuleSet控制器 0、昨日内容回顾1、StatefulSets控制器1.1 StatefulSet概述1.2 StatefulSets控制器-网络唯一标识之headless1.3 StatefulSets控制器-独享存储 2、metric-server2.1 metric-server概述2.2 部署metric-server:2.3 hpa案例 3、helm概述3.1 安装helm3.2 h…

RabbitMQ 高级特性——持久化

文章目录 前言持久化交换机持久化队列持久化消息持久化 前言 前面我们学习了 RabbitMQ 的高级特性——消息确认&#xff0c;消息确认可以保证消息传输过程的稳定性&#xff0c;但是在保证了消息传输过程的稳定性之后&#xff0c;还存在着其他的问题&#xff0c;我们都知道消息…

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

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

Spring Boot集成Akka Stream快速入门Demo

1.什么是Akka Stream&#xff1f; Akka Streams是一个用于处理和传输元素序列的库。它建立在Akka Actors之上&#xff0c;使流的摄入和处理变得简单。由于它是建立在Akka Actors之上的&#xff0c;它为Akka现有的actor模型提供了一个更高层次的抽象。Akka流由3个主要部分组成-…

从0开始学习RocketMQ:快速部署启动

快速部署 快速部署一个单节点单副本 RocketMQ 服务&#xff0c;并完成简单的消息收发。 安装Apache RocketMQ 下载地址&#xff1a;RocketMQ官网下载 这里我们下载二进制包&#xff1a;rocketmq-all-5.3.0-bin-release.zip 直接解压即可&#xff1a;tar -zxvf rocketmq-all…

光伏开发:工商业光伏的流程管理全面解析

一、项目准备阶段 1、资源寻觅与沟通 首要任务是寻找适合的工商业屋顶或空地资源&#xff0c;并与业主初步交流&#xff0c;了解其意向、屋顶条件及用电情况。这一阶段的关键在于建立信任关系&#xff0c;为后续工作奠定基础。 2、资料收集与核查 全面收集业主资料&#xff…

算法练习题26——多项式输出(模拟)

输入格式 输入共有 2 行 第一行 1 个整数&#xff0c;n&#xff0c;表示一元多项式的次数。 第二行有 n1 个整数&#xff0c;其中第 i 个整数表示第 n−i1 次项的系数&#xff0c;每两个整数之间用空格隔开。 输出格式 输出共 1 行&#xff0c;按题目所述格式输出多项式。…

Navicat BI 中创建自定义字段:计算字段

在数据库设计和开发中&#xff0c;避免存储任何可以从其他字段计算或重建的数据是一种惯例。因此&#xff0c;在 Navicat BI 中构建图表时&#xff0c;你可能会缺少一些数据。但这不是问题&#xff0c;因为 Navicat BI 提供了专门用于此目的的计算字段。在今天的博客中&#xf…

网站按钮检测系统源码分享

网站按钮检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

浅谈MVC设计模式

1 前言 1.1 内容概要 熟悉使用JSON工具&#xff0c;完成Java对象&#xff08;Map&#xff09;和Json字符串之间的相互转换&#xff08;注意提供构造器和getter/setter方法&#xff09; 注意事项&#xff1a;不管使用的是什么JSON工具&#xff0c;都要提供类的无参构造方法和…

基于SpringBoot的宠物寄领养网站管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【前后端分离】基于JavaSpringBootVueMySQL的宠物寄领养网站…

北斗卫星系统信号介绍

覆盖范围亚太区域全球范围 卫星数量35颗区域服务卫星30颗全球服务卫星 信号频段B1I, B2IB1C, B2a, B3, 兼容GPS/Galileo 定位精度区域内10米全球2.5~5米&#xff0c;中国内更高 新增功能区域短报文通信全球短报文通信、星基增强、精密定位 抗干扰能力相对有限更强 互操作…

无人机 PX4 飞控 | 如何检测状态估计EKF性能

无人机 PX4 飞控 | 如何检测状态估计EKF性能 前言检查EKF性能缺少pyulog问题解决脚本崩溃没有输出文件生成对应文件 结语 前言 ECL &#xff08;Estimation and Control Library&#xff0c;估计和控制库&#xff09;&#xff0c;其中的状态估计使用扩展卡尔曼滤波算法&#x…

图像检测【YOLOv5】——深度学习

Anaconda的安装配置&#xff1a;&#xff08;Anaconda是一个开源的Python发行版本&#xff0c;包括Conda、Python以及很多安装好的工具包&#xff0c;比如&#xff1a;numpy&#xff0c;pandas等&#xff0c;其中conda是一个开源包和环境管理器&#xff0c;可以用于在同一个电脑…

计算机网络基本概述

欢迎大家订阅【计算机网络】学习专栏&#xff0c;开启你的计算机网络学习之旅&#xff01; 文章目录 前言一、网络的基本概念二、集线器、交换机和路由器三、互连网与互联网四、网络的类型五、互连网的组成1. 边缘部分2. 核心部分 六、网络协议 前言 计算机网络是现代信息社会…

安装node 报错需要:glibc >= 2.28

--> 解决依赖关系完成 错误&#xff1a;软件包&#xff1a;2:nodejs-18.20.4-1nodesource.x86_64 (nodesource-nodejs) 需要&#xff1a;libm.so.6(GLIBC_2.27)(64bit) 错误&#xff1a;软件包&#xff1a;2:nodejs-18.20.4-1nodesource.x86_64 (nodesource-nodej…

【数据结构篇】~排序(1)之插入排序

排序~插入排序 前言插入排序1.直接插入排序&#xff08;时间复杂度&#xff1a;O(N^2)&#xff09;1.思想2.代码 2.希尔排序(时间复杂度&#xff1a;O(N∙))1.思路简易证明希尔排序的复杂度 2.代码 前言 四大排序&#xff0c;今天解决插入排序 堆排序和冒泡排序已经写过了&am…