【一起学系列】之装饰器模式:不改代码增强功能?

3,344 阅读3分钟

意图

动态地给一个对象添加一些额外的职责,就增加功能来说,Decorator模式相比生成子类更为灵活

别名:包装器Wrapper

装饰器模式的诞生

有时我们希望给某个对象而不是整个类添加一些功能,例如:一个图形用户界面工具箱允许你对任意一个用户界面组件添加一些特性,例如边框。

使用继承机制是添加功能的一种有效途径,但这种方法不够灵活,因为边框的选择是静态的,用户不能控制对组件加边框的方式和时机。一种较为灵活的方式是将组件嵌入另一个对象中,由这个对象添加边框,我们称这个嵌入的对象为装饰。

说人话就是:

【产品】:开发小哥,我这里有一份代码,你需要调用它的功能并补充公司的额外功能但是不能动它本身的内容,能做到吗?

【开发】:哈?怎么可能!怎么可能不改代码还要他功能,还得有额外的功能?难道让我写成这样的?

void demo () {
    // 调用原有代码功能
    // do();
    
    // 调用公司功能
    // doMine();
}

【BOSS】:磕大头!你不觉得这样很low吗?

【开发】:可是不这么写我咋改写?

【BOSS】:你仔细看看原有的代码,它们都实现了同样的接口,这就是一个突破点

【开发】:我去研究研究....

HeadFirst 核心代码

于是乎,我们开启了关于设计模式的经典书籍阅读之旅

/**
 * 装饰器模式共同的接口
 */
public interface Component {
    String getName();
    double getSpend();
}


/**
 * 原本的类
 */
public class ConcreteComponent implements Component {

    private String name;

    public ConcreteComponent(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public double getSpend() {
        return 10;
    }
}


/**
 * 装饰器类 注意它利用了组合的方式,同时注意函数实现的部分
 */
public class MilkDecorator implements Component {

    Component coffe;

    MilkDecorator(Component coffe) {
        this.coffe = coffe;
    }

    @Override
    public String getName() {
       return coffe.getName() + ", 牛奶";
    }

    @Override
    public double getSpend() {
        return coffe.getSpend() + 2D;
    }
}

装饰器模式的设计思路:

  • Component:定义一个对象接口,统一行为及父级
  • ConcreteComponent:定义一个对象,可以给这个对象添加一些职责(方法)
  • Decorator(不必要):定义一个跟Component一致的接口,并约束需要实现的职责(方法)
  • ConcreteDecorator:维持一个指向Component对象的指针,并实现Component接口,以此增强对象的行为

简单来说,

  1. 我们需要一个接口来统一父级,并制定一些必要的行为
  2. 默认的实现类实现相关功能
  3. 如果需要额外的增加(装饰),则实现接口并持有接口对象的指针,在实现接口方法时调用指针对象的方法并增强

如果看着有点模棱两可,就看完本文后,访问专题设计模式开源项目,里面有具体的代码示例,链接在最下面

遵循的设计原则

  1. 对扩展开放,对修改关闭:完美实现了不修改其代码达到增强方法的目的
  2. 针对接口编程:装饰器与其本身都有同样的父级

什么场景适合使用

在不影响其他对象的情况下,以动态,透明的方式给单个对象添加职责

Code/生活中的实际应用

Java 中最著名的装饰者模式的应用,就是 java.io 包中的各种InputStream 、Reader和 Writer 的实现类了。如果我们要从文件中读取文本内容,我们可以使用 FileReader(装饰者) 和 FileInputStream(装饰者);

但是如果我们想让读取的速度更快,那么我们就需要 BufferedReader(装饰者) ,这只需要改变一行代码就能实现

最后

附上GOF一书中对于装饰器模式的UML图:

装饰器模式UML图
装饰器模式UML图

相关代码链接

GitHub地址

  • 兼顾了《HeadFirst》以及《GOF》两本经典数据中的案例
  • 提供了友好的阅读指导