百度工程师教你玩转设计模式(装饰器模式)

news2025/4/7 17:07:05

在这里插入图片描述

作者 | 北极星小组

想要写好代码,设计模式(Design Pattern)是必不可少的基本功,设计模式是对面向对象设计(Object Oriented Design)中反复出现的一类问题的一种解决方案,本篇介绍装饰器模式(Decorator Pattern)。

在我们日常的开发过程中,一个最常见的场景就是在已有的基础上新增功能,常规的做法有以下几种:

  • 修改已有的类:违背开闭原则。

  • 增加新的子类:每次都得新增大量对应的类,随着功能的增加,子类越来越膨胀。

在此场景下,装饰器模式就可以体现出它的优势了,它允许在不修改原有对象的前提下,灵活的扩展已有类的功能。下面是装饰器模式的一个通用的类图:

图片
△UML

其中的各个类的作用如下:

  • 抽象组件(Component): 可以是接口或者抽象类,它定义了具体类以及装饰器所拥有的方法。

  • 具体组件(ComponentA, ComponentB):具体的组件,实现或者继承自抽象组件。可以理解成上述场景中已存在的类。

  • 抽象装饰器(Decorator): 通常为抽象类,持有一个被装饰的对象,定义了具体装饰器的方法。此类非必须也可以没有,具体装饰器也可直接继承或者实现抽象组件。

  • 具体装饰器(DecoratorX, DecoratorY): 具体的装饰器,继承自抽象装饰器(也可直接继承自抽象组件),扩展了抽象组件的某些功能。

下面,将通过3个具体的案例的讲解装饰器的使用方式,方便大家进一步的理解。

一、装饰器在任务处理场景的应用

在实际的开发中,我们经常需要定义不同的类来处理各种不同的任务。假设一个这样的场景,我们的系统有多个具体的类,用来处理不同类型的任务。现在需要添加一个功能,就是在处理完任务后发出一条消息。针对这个场景,使用装饰器模式的实现思路如下:

  • 抽象组件(TaskProcessor):处理任务的抽象类(亦可通过接口实现),定义一个通用的任务处理方法process()。

  • 具体组件(TaskProcessorA, TaskProcessorB): 负责实现具体的任务处理逻辑

  • 抽象装饰器(TaskProcessDecorator):持有一个任务处理对象实例

  • 具体装饰器(AfterTaskProcessDecorator):实现具体的任务处理完成后的消息通知扩展能力

具体的代码如下:

package com.baidu.demo;
public class Decorator {
    // 抽象组件
    static abstract class TaskProcessor {
        abstract void process();
    }
    // 具体组件
    static class TaskProcessorA extends TaskProcessor {
        @Override
        void process() {
            System.out.println("TaskProcessorA处理完成");
        }
    }
    // 具体组件
    static class TaskProcessorB extends TaskProcessor {
        @Override
        void process() {
            System.out.println("TaskProcessorB处理完成");
        }
    }
    // 抽象装饰器
    static abstract class TaskProcessDecorator extends TaskProcessor {
        protected TaskProcessor processor;
        public TaskProcessDecorator(TaskProcessor processor) {
            this.processor = processor;
        }
        abstract void process();
    }
    // 具体装饰器
    static class AfterTaskProcessDecorator extends TaskProcessDecorator {
        public AfterTaskProcessDecorator(TaskProcessor processor) {
            super(processor);
        }

        @Override
        void process() {
            processor.process();
            afterProcess();
        }

        void afterProcess() {
            System.out.println("任务处理完毕,发送消息...");
        }
    }

    public static void main(String[] args) {
        // 扩展之前
        System.out.println("==========before==========");
        TaskProcessor processorA = new TaskProcessorA();
        processorA.process();
        TaskProcessor processorB = new TaskProcessorB();
        processorB.process();

        // 装饰器扩展之后:TaskProcessorA TaskProcessorB并未做任何修改,即可实现功能的扩展
        System.out.println("==========after==========");
        TaskProcessor decoratorA = new AfterTaskProcessDecorator(processorA);
        decoratorA.process();
        TaskProcessor decoratorB = new AfterTaskProcessDecorator(processorB);
        decoratorB.process();
    }
}

// 输出结果如下
==========before==========
TaskProcessorA处理完成
TaskProcessorB处理完成
==========after==========
TaskProcessorA处理完成
任务处理完毕,发送消息...
TaskProcessorB处理完成
任务处理完毕,发送消息...

二、装饰器在文件IO场景的应用

装饰器模式,一个典型的应用就是文件IO操作,最基础的类实现字节流读取类,使用装饰器模式可以封装文件字节流读取类,然后可以继续封装可缓存的文件字节流读取类,在项目中按需使用。具体实现如下:

  • InputStream:具体组件,实现读取字节流。

  • FileInputStream:具体装饰器,作为InputStream的子类,扩展文件操作。

  • BufferedInputStream:具体装饰器,作为FileInputStream的子类,扩展缓存操作。

具体代码如下:

//具体组件,实现读取字节流
public abstract class InputStream {
    public int read(byte b[], int off, int len) {}
}

//具体装饰器,作为InputStream的子类,扩展文件操作
public class FileInputStream extends InputStream {
    protected InputStream in;
    
    public FileInputStream(String name) {
        InputStream in = ... //此处省略,通过文件名创建对象
        this.in = in;
    }
    
    public int read(byte b[], int off, int len) {
        return this.in.read(b, off, len);
    }
}

//具体装饰器,作为FileInputStream的子类,扩展缓存操作
public class BufferedInputStream extends FileInputStream {
    protected FileInputStream in;
    protected byte[] buffer;
    
    public BufferedInputStream(FileInputStream in) {
        this.in = in;
    }
    
    public int read(byte b[], int off, int len) {
        if (this.buffer == null || this.buffer.length == 0) {
            this.in.read(this.buffer, 0, in.lenght());
        }
        
        System.arraycopy(this.buffer, off, b, 0, len);
        ...
    }
}

public static void main(String[] args) {
    FileInputStream fs = new FileInputStream('./test.log');
    BufferedInputStream bs = new BufferedInputStream(fs);
    
    byte[] b;
    bs.read(b, 0, 1);
}

三、装饰器在日志系统场景的应用

在日志系统中,一般常用日志的级别分别为 DEBUG(调试)、INFO(运行信息)、WARN(警告)、ERROR(错误),一旦发生错误级别的日志后,则需要触发报警通知相关人员及时进行跟进,报警方式一般有:邮件、短信、如流等,通常我们会根据业务场景以组合的方式进行报警通知,使用装饰器模式则能很好实现组合报警这一功能。

  • 抽象组件:Log接口抽象

  • 具体组件:Slf4j 具体日志类的实现

  • 抽象装饰器:LogDecorator 日志装饰器的基类

  • 具体装饰器:MailLogDecorator、SMSLogDecorator、InfoFlowLogDecorator具体装饰类

/**
 * 日志接口
 */
public interface Log {
    void debug(String message);
    void info(String message);
    void warn(String message);
    void error(String message);
}

/**
 * Slf4j 日志
 */
public class Slf4jLog implements Log {

    //日志记录对象
    private final Logger log = LoggerFactory.getLogger("system_log");

    @Override
    public void debug(String message) {
        if (log.isDebugEnabled()) {
             log.debug(message);
        }
    }

    @Override
    public void info(String message) {
        if (log.isInfoEnabled()) {
              log.info(message);
        }
    }

    @Override
    public void warn(String message) {
        if (log.isWarnEnabled()) {
            log.warn(message);
        }
    }

    @Override
    public void error(String message) {
        if (log.isErrorEnabled()) {
            log.error(message);
        }
    }
}

/**
 * 日志装饰器
 */
public class LogDecorator implements Log {
    protected Log log;

    public LogDecorator(Log log) {
        this.log = log;
    }

    @Override
    public void debug(String message) {
        log.debug(message);
    }

    @Override
    public void info(String message) {
        log.info(message);
    }

    @Override
    public void warn(String message) {
        log.warn(message);
    }

    @Override
    public void error(String message) {
        log.error(message);
    }
}

/**
 * 邮件日志装饰器
 */
public class MailLogDecorator extends LogDecorator {
    public MailLogDecorator(Log log) {
        super(log);
    }

    @Override
    public void warn(String message) {
        log.warn(message);
        mail(message);
    }

    @Override
    public void error(String message) {
        log.error(message);
        mail(message);
    }
    
    public void mail(String message) {
        //模拟邮件发送
        log.info("邮件已发送,信息:" + message);
    }
}

/**
 * 短信日志装饰器
 */
public class SMSLogDecorator extends LogDecorator {
    public SMSLogDecorator(Log log) {
        super(log);
    }
    
    @Override
    public void error(String message) {
        log.error(message);
        send(message);
    }

    public void send(String message) {
        //模拟短信发送
        log.info("短信已发送,信息:" + message);
    }
}

/**
 * 如流日志装饰器
 */
public class InfoflowLogDecorator extends LogDecorator {
    public InfoflowLogDecorator(Log log) {
        super(log);
    }

    @Override
    public void warn(String message) {
        log.warn(message);
        send(message);
    }
    
    @Override
    public void error(String message) {
        log.error(message);
        send(message);
    }

    
    public void send(String message) {
        //模拟如流发送
        log.info("如流消息已发送,信息:" + message);
    }
}

/**
 * 日志测试类
 */
public class LogTest {

    /**
     * 测试日志装饰器
     */
    @Test
    public void testLogDecorator() {
        Log log = new SMSLogDecorator(new InfoFlowLogDecorator(new MailLogDecorator(new Slf4jLog())));
        log.debug("系统调试开启");
        log.info("系统正常运行");
        log.warn("数据为空警告");
        log.error("db 连接错误");
    }
}
===========output=========
15:16:56.564 [main] DEBUG system_log - 系统调试开启
15:16:56.566 [main] INFO  system_log - 系统正常运行
15:16:56.566 [main] WARN  system_log - 数据为空警告
15:16:56.566 [main] INFO  system_log - 邮件已发送,信息:数据为空警告
15:16:56.566 [main] INFO  system_log - 如流消息已发送,信息:数据为空警告
15:16:56.566 [main] ERROR system_log - db 连接错误
15:16:56.566 [main] INFO  system_log - 邮件已发送,信息:db 连接错误
15:16:56.566 [main] INFO  system_log - 如流消息已发送,信息:db 连接错误
15:16:56.566 [main] INFO  system_log - 短信已发送,信息:db 连接错误

Process finished with exit code 0

四、总结

如上几个案例,装饰器的最大作用就是在不修改原有类的基础上扩展已有的功能,它符合开闭原则,而且实现也比较灵活。

---------- END ----------

推荐阅读【技术加油站】系列:

百度工程师教你玩转设计模式(工厂模式)

百度工程师教你玩转设计模式(适配器模式)

百度工程师教你玩转设计模式(单例模式)

图片

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

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

相关文章

(第一章)OpGL超级宝典学习:配置和超级宝典相同的工作环境

目录前言配套资源配置解压文件夹复制资源HOWTOBUILD什么是CMake什么是GLFW安装CMake开始构建build glfw生成debug和release的lib库build sample推送结语前言 最近发现学习好像到了一定的瓶颈,马上要到2023年了,想要在新的一年开始后对自己有一定的突破&a…

Kubernetes 实现自动扩容和自愈应用实践

Kubernetes 自动扩容和自愈 tags: 实践 文章目录Kubernetes 自动扩容和自愈1. 背景2. 准备3. kind 部署 kubernetes4.实践4.1 部署 deployment4.2 创建 Service4.3 创建 Ingress4.4 部署 Ingress-nginx4.5 K8s 实现自愈4.6 k8s 实现自动扩容5. 其他1. 背景 在生产非 kubernet…

java进阶—List

上节回顾 List 是一个有序的,允许重复的Collection,其下的子类主要有 ArrayList LinkedList,Vector(这个官方不推荐使用) 那么同为list的子类,ArrayList 跟 LinkedList 有什么区别呢? 这里就涉及到了list的底层两种实现方式&…

《计算机体系结构量化研究方法》第2章-存储器层次结构设计 2.1 引言

一、概述 1、存储器层次结构:层次由速度和容量各不相同的存储器组成。 2、存储器层次结构被分为几个级别——离处理器越近,容量越小速度越快。 3、包含性质:大多数情况下,低层级存储器中的数据是上一级存储器中数据的超集。比如…

基于RSA的数字签名设计与实现

信息安全课程的第二个实验,主要是用java、js,前端页面用的html写的。 页面成果展示: 基本公私钥生成 实验环境为win10系统,使用git命令行工具——git bash生成公私钥。生成私钥,密钥长度为1024bit并从私钥中提取公钥 …

如何在 Spring Boot 项目中开启 “热部署“

目录 1. 添加热部署框架支持 2. 设置当前项目 Settings 和新项目 Settings 开启项目自动编译 3. 开启运行中热部署 4. 使用 Debug 启动项目 (社区版 IDEA) 1. 添加热部署框架支持 在 pom.xml 中添加如下框架引用: <dependency><groupId>org.springframework.b…

vue3中常用的三种组件传值方式

比较大型的项目中经常会涉及到多个组件之间进行传值&#xff0c;所以对之前用过的一些传值方法做个笔记&#xff0c;还有就是对不同的情况下需要使用哪一种传值方法更合适的思维进行一个总结 vue3中常用的三种组件传值方式mitt依赖注入pinia总结mitt 因为vue3去掉了全局事件总…

qt实现的pdf阅读器(二)--XpdfReader在linux下的编译

目录 1.简介 2.需求说明 3.编译 3.3. 下载源码 3.2. 移植安装包和依赖库 3.2.1 准备工作 3.3.2 了解 3.3.3 编译并安装zlib 3.3.4 编译并安装libpng 3.3.5 编译并安装lcms 3.3.6 编译并安装freetype 3.3.7 编译xpdf 3.3.8 查看运行编译好的xpdf 1.简介 Xpdf 是一个免…

我以为自己MySQL够牛逼了,直到看到了Alibaba的面试题

前言 众所周知&#xff0c;简历上“了解&#xff1d;听过名字&#xff1b;熟悉&#xff1d;知道是啥&#xff1b;熟练&#xff1d;用过&#xff1b;精通&#xff1d;做过东西”。 相信大家对于MySQL的索引都不陌生&#xff0c;索引(Index)是帮助MySQL高效获取数据的数据结构。…

SpringBoot Disruptor框架遇到的问题

1.消息重复消费问题 问题描述&#xff1a; 项目中启动了多个消费者,测试中发现同一条消息被多次消费。 解决方案&#xff1a; ①幂等方案处理 ②disrutor提供了不同的处理机制&#xff1a; 自定义消费者实现EventHandler接口,他是属于重复消费&#xff0c; 自定义消费者实现W…

SVN培训笔记(下拉项目、同步修改、添加文件、修改文件、删除文件、改名文件等)

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/128417196 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…

【矩阵论】7. 范数理论——非负/正矩阵

7.5 非负/正矩阵 7.5.1 定义 a. 非负/正矩阵定义 一个实矩阵 A(aij)∈RmnA(a_{ij})\in R^{m\times n}A(aij​)∈Rmn 若对每一 iii 和 jjj &#xff0c;aij≥0a_{ij}\ge 0aij​≥0 &#xff0c;则称A是非负矩阵&#xff0c;A≥0A\ge 0A≥0 若对每一 iii 和 jjj &#xff0c;…

简单四则运算语法树可视化

简单四则运算语法树可视化 前几天有一篇博客是关于四则运算和二叉树的&#xff0c;我是把四则运算用二叉树写出来&#xff08;我是用的 JSON 的形式来存储和表达的&#xff09;&#xff0c;并计算最终的结果。最近&#xff0c;也在继续这个方面的东西&#xff0c;不过遇到一些…

uni-app 微信支付-小程序、APP、IOS

小程序 支付 先看官方文档 https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_2.shtml 知晓有那些比不可少的流程&#xff0c;之后后端确定返回的参数值&#xff0c;用于前端支付。参数值必须一一对应&#xff0c;不然支付会失败 uni.requestPayment({timeStamp…

mysql5.7主从复制配置

写在最前面&#xff1a;一入编程深似海&#xff0c;从此对象变路人&#xff08;码农没时间谈恋爱&#xff09;。很长一段时间连写个文章的时间都没有了&#xff0c;学完后端、学前端&#xff0c;前端刚入门又要搞容器化&#xff0c;这真是“一重山外一重关&#xff0c;关关难过…

如何在Angular框架中更好地使用字体?一篇文章解答!

作为前端开发人员&#xff0c;在Angular JS中构建项目时&#xff0c;使用自定义字体可能会很棘手。有时候&#xff0c;如果开发者想要把选择的字体添加到项目中&#xff0c;将不得不把它导入到现有的代码中。 PS&#xff1a;Kendo UI致力于新的开发&#xff0c;来满足不断变化…

信息安全产品认证

文章目录一、引言二、《网络关键设备和网络安全专用产品安全认证证书》2.1 背景2.2 产品目录2.3 认证依据标准2.4 认证机构三、《中国国家信息安全产品认证证书》3.1 背景3.2 产品目录3.3 行业跟进四、《IT产品信息安全认证证书》五、CCC认证5.1背景5.2 中国强制性产品认证体系…

多源传感器组合导航 GNSS 视觉SLAM LiDAR INS 开源项目总结

多源传感器组合导航 GNSS 视觉SLAM LiDAR INS 开源项目总结 本文更改自 吴桐wutong 微信公众号文章。 开源代码总览 名称传感器类型组合类型滤波方法备注RTKLIBG-KFGAMP、rtklibexplorerhttps://www.rtklib.com/GPSTKG-KFhttps://github.com/SGL-UT/GPSTkBNCG-KFppp_wizardK…

【多个IP地址用逗号分割开】vue简单实现,textarea文本域输入多个ip地址用逗号分隔开,根据空格分割

前言 这个功能也是很多地方会用到的。 一般使用的地方是比如需要设置白名单或者黑名单 然后页面上会有一个textarea文本域。 在文本域中输入多个ip地址&#xff0c;输入一个回车换一行。 然后点击保存后&#xff0c;把数据通过逗号隔开的格式传给后端 后端再去拿到每一个ip地址…

电脑重装系统win11如何更改默认下载路径

win11如何更改默认下载路径&#xff1f;当大家平日里面&#xff0c;在使用win11系统的时候&#xff0c;如果觉得某一个下载路径的内存空间已经满了的话&#xff0c;那么就必须要及时更改&#xff0c;下面是小编提 供的更改路径的方法。 工具/原料&#xff1a; 系统版本&#x…