一、装饰器模式介绍

1. 解决的问题

主要解决动态地给一个对象添加一些额外的职责。

2. 定义

装饰器模式是一种结构型设计模式,允许将对象通过放入包含行为的特殊封装对象来为原对象绑定新的行为。

3. 应用场景

  • 希望在无需修改代码的情况下即可使用对象,且希望在运行时能为对象增加额外的行为,可以使用装饰器模式。
  • 用继承来扩展对象行为的方案难以实现或者根本不可能时,可以使用装饰器模式。

二、装饰器模式优缺点

1. 优点

  • 无需创建新的子类即可扩展对象的行为。
  • 可以在运行时添加或删除对象的功能。
  • 可以使用多个装饰封装对象来组合几种行为。
  • 单一职责原则:将实现了不同行为的一个大类拆分为多个较小的类。

2. 缺点

  • 在封装器栈中删除特定封装器比较困难。
  • 实现行为不受装饰栈顺序影响的装饰比较困难。
  • 各层初始化配置代码看起来可能会比较糟糕。

三、装饰器模式应用实例:日志系统支持各类通知组合

1. 实例场景

我们通常会记录系统运行的各类情况及状态,保存到日志中,常用日志的级别分别为 DEBUG(调试)、INFO(运行信息)、WARN(警告)、ERROR(错误)。

一旦发生错误级别的日志,代表系统出现了重大问题,需要及时通知到相关人员来尽快修复。

一般我们会进行邮件通知到运维及开发,但有时邮件会比较多,容易被忽略,讨论后希望将错误级别的日志可以支持邮件通知、短信通知、钉钉通知,并支持邮件+短信、邮件+钉钉、邮件+短信+钉钉等各类组合。

此时,可以用装饰器模式来实现。

2. 装饰器模式实现

2.1 工程结构
decorator-pattern
└─ src
    ├─ main
    │    └─ java
    │    └─ org.design.pattern.decorator
    │       └─ log
    │          ├─ Log.java
    │          ├─ Slf4jLog.java
    │          └─  decorator
    │             ├─ LogDecorator.java
    │             ├─ MailLogDecorator.java
    │             ├─ DingDingLogDecorator.java
    │             └─ SMSLogDecorator.java
    └─ test
        └─ java
            └─ org.design.pattern.decorator.test
                  └─ LogTest.java
2.2 代码实现
2.2.1 日志

日志接口

/**
 * 日志接口
 */
public interface Log {

    /**
     * 记录调试日志
     *
     * @param message 信息
     */
    void debug(String message);

    /**
     * 记录普通日志
     *
     * @param message 信息
     */
    void info(String message);

    /**
     * 记录警告日志
     *
     * @param message 信息
     */
    void warn(String message);

    /**
     * 记录错误日志
     *
     * @param message 信息
     */
    void error(String message);
}

Slf4j 日志类

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

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

    /**
     * 记录调试日志
     *
     * @param message 信息
     */
    @Override
    public void debug(String message) {
        if (log.isDebugEnabled()) {
             log.debug(message);
        }
    }

    /**
     * 记录普通日志
     *
     * @param message 信息
     */
    @Override
    public void info(String message) {
        if (log.isInfoEnabled()) {
              log.info(message);
        }
    }

    /**
     * 记录警告日志
     *
     * @param message 信息
     */
    @Override
    public void warn(String message) {
        if (log.isWarnEnabled()) {
            log.warn(message);
        }
    }

    /**
     * 记录错误日志
     *
     * @param message 信息
     */
    @Override
    public void error(String message) {
        if (log.isErrorEnabled()) {
            log.error(message);
        }
    }
}
2.2.2 日志装饰器

日志装饰器基类

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

    /**
     * 日志类
     */
    protected Log log;

    /**
     * 构造方法
     *
     * @param log 日志类
     */
    public LogDecorator(Log log) {
        this.log = log;
    }

    /**
     * 记录调试日志
     *
     * @param message 信息
     */
    @Override
    public void debug(String message) {
        log.debug(message);
    }

    /**
     * 记录普通日志
     *
     * @param message 信息
     */
    @Override
    public void info(String message) {
        log.info(message);
    }

    /**
     * 记录警告日志
     *
     * @param message 信息
     */
    @Override
    public void warn(String message) {
        log.warn(message);
    }

    /**
     * 记录错误日志
     *
     * @param message 信息
     */
    @Override
    public void error(String message) {
        log.error(message);
    }
}

邮件日志装饰器

/**
 * 邮件日志装饰器
 */
public class MailLogDecorator extends LogDecorator {

    /**
     * 构造方法
     *
     * @param log 日志类
     */
    public MailLogDecorator(Log log) {
        super(log);
    }

    /**
     * 记录警告日志
     *
     * @param message 信息
     */
    @Override
    public void warn(String message) {
        log.warn(message);
        mail(message);
    }

    /**
     * 记录错误日志
     *
     * @param message 信息
     */
    @Override
    public void error(String message) {
        log.error(message);
        mail(message);
    }

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

钉钉日志装饰器

/**
 * 钉钉日志装饰器
 */
public class DingDingLogDecorator extends LogDecorator {

    /**
     * 构造方法
     *
     * @param log 日志类
     */
    public DingDingLogDecorator(Log log) {
        super(log);
    }

    /**
     * 记录警告日志
     *
     * @param message 信息
     */
    @Override
    public void warn(String message) {
        log.warn(message);
        send(message);
    }

    /**
     * 记录错误日志
     *
     * @param message 信息
     */
    @Override
    public void error(String message) {
        log.error(message);
        send(message);
    }

    /**
     * 发送顶顶消息
     *
     * @param message 信息
     */
    public void send(String message) {
        //模拟邮件发送
        log.info("钉钉消息已发送,信息:" + message);
    }
}

短信日志装饰器

/**
 * 短信日志装饰器
 */
public class SMSLogDecorator extends LogDecorator {

    /**
     * 构造方法
     *
     * @param log 日志类
     */
    public SMSLogDecorator(Log log) {
        super(log);
    }

    /**
     * 记录错误日志
     *
     * @param message 信息
     */
    @Override
    public void error(String message) {
        log.error(message);
        send(message);
    }

    /**
     * 发送短信
     *
     * @param message 信息
     */
    public void send(String message) {
        //模拟短信发送
        log.info("短信已发送,信息:" + message);
    }
}
2.3 测试验证
2.3.1 测试验证类
/**
 * 日志测试类
 */
public class LogTest {

    /**
     * 测试日志装饰器
     */
    @Test
    public void testLogDecorator() {
        Log log = new SMSLogDecorator(new DingDingLogDecorator(new MailLogDecorator(new Slf4jLog())));
        log.debug("系统调试开启");
        log.info("系统正常运行");
        log.warn("数据为空警告");
        log.error("mongo 连接错误");
    }
}
2.3.2 测试结果
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 - mongo 连接错误
15:16:56.566 [main] INFO  system_log - 邮件已发送,信息:mongo 连接错误
15:16:56.566 [main] INFO  system_log - 钉钉消息已发送,信息:mongo 连接错误
15:16:56.566 [main] INFO  system_log - 短信已发送,信息:mongo 连接错误

Process finished with exit code 0

四、装饰器模式结构

装饰器模式-模式结构图

  1. 部件(Component)声明封装器和被封装对象的公用接口。
  2. 具体部件(Concrete Component)类是被封装对象所属的类。它定义了基础行为,但装饰类可以改变这些行为。
  3. 基础装饰(Base Decorator)类拥有一个指向被封装对象的引用成员变量。该变量的类型应当被声明为通用部件接口,这样他就可以引用具体的部件和装饰。装饰基类会将所有操作委派给被封装的对象。
  4. 具体装饰类(Concrete Decorators)定义了可动态添加到部件的额外行为。具体装饰类会重写装饰基类的方法,并在调用父类方法之前或之后进行额外的行为。
  5. 客户端(Client)可以使用多层装饰来封装部件,只要它能使用通用接口与所有对象互动即可。

设计模式并不难学,其本身就是多年经验提炼出的开发指导思想,关键在于多加练习,带着使用设计模式的思想去优化代码,就能构建出更合理的代码。

源码地址:https://github.com/yiyufxst/design-pattern-java

参考资料:
小博哥重学设计模式:https://github.com/fuzhengwei/itstack-demo-design
深入设计模式:https://refactoringguru.cn/design-patterns/catalog


易羽fxst
158 声望5 粉丝