1. 简介

装饰器模式(Decorator Pattern):动态地给一个对象添加职责,就增加功能来说,装饰器比生成子类更灵活。

2. 示例

水果店需要给网上客户发货,除了包装之外,需要对特定水果包装加额外装饰,比如加防伪标志、加固、加急等额外功能,但在外部看来还是打包组件。
【趣味设计模式系列】之【装饰器模式】-LMLPHP

类图设计
【趣味设计模式系列】之【装饰器模式】-LMLPHP

水果包装接口类Bag,接口方法pack,完成水果打包。

package com.wzj.decorator;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:13
 * @Desc: 包装接口
 */
public interface Bag {
    void pack();
}

苹果、橘子、香蕉各自实现包装接口,用自己的特定的外包装。

package com.wzj.decorator;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:15
 * @Desc: 苹果包装类
 */
public class AppleBag implements Bag {
    @Override
    public void pack() {
        System.out.println("苹果使用纸箱包装");
    }
}

package com.wzj.decorator;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:15
 * @Desc: 橘子包装类
 */
public class OrangeBag implements Bag {
    @Override
    public void pack() {
        System.out.println("橘子使用网兜包装");
    }
}

package com.wzj.decorator;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:15
 * @Desc: 香蕉包装类
 */
public class BananaBag implements Bag {
    @Override
    public void pack() {
        System.out.println("香蕉使用竹箩包装");
    }
}

装饰器类BagDecorator,实现包装类Bag接口,同时拥有包装接口的引用,为了组装更多具体装饰器加入进来,增加包装类的装饰功能。

package com.wzj.decorator;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:23
 * @Desc: 装饰器类
 */
public class BagDecorator implements Bag {
    private Bag bag;  //维持一个对抽象构件对象的引用

    public BagDecorator(Bag bag)  //注入一个抽象构件类型的对象
    {
        this.bag = bag;
    }

    public void pack() {
        bag.pack();
    }
}

防伪装饰器CheckedBagDecorator,继承BagDecorator类,并增加自己的防伪标识方法checked。

package com.wzj.decorator;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:32
 * @Desc: 防伪装饰器
 */
public class CheckedBagDecorator extends BagDecorator {
    public CheckedBagDecorator(Bag bag) {
        super(bag);
    }

    @Override
    public void pack() {
        super.pack();
        checked();  //打印防伪标识
    }

    //增加防伪标识
    public void checked() {
        System.out.println("===============");
        System.out.println("打印上防伪标识");
    }
}

加固装饰器ReinforceBagDecorator,继承BagDecorator类,并增加自己的加固方法reinforce。

package com.wzj.decorator;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:34
 * @Desc: 加固装饰器
 */
public class ReinforceBagDecorator extends BagDecorator{
    public ReinforceBagDecorator(Bag bag) {
        super(bag);
    }

    public void pack() {
        super.pack();  //调用原有业务方法
        reinforce();
    }

    //加固包装
    public void reinforce() {
        System.out.println("===============");
        System.out.println("加固了包装");
    }
}

加急装饰器SpeedBagDecorator,继承BagDecorator类,并增加自己的加急方法speedy。

package com.wzj.decorator;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:34
 * @Desc: 加急装饰器
 */
public class SpeedBagDecorator extends BagDecorator {

    public SpeedBagDecorator(Bag bag) {
        super(bag);
    }

    public void pack() {
        super.pack();  //调用原有业务方法
        speedy();
    }

    //快件加急
    public void speedy() {
        System.out.println("===============");
        System.out.println("打上加急标识");
    }
}

客户端类

package com.wzj.decorator;

import org.aspectj.weaver.ast.Or;

/**
 * @Author: wzj
 * @Date: 2020/9/8 10:40
 * @Desc:
 */
public class Client {
    public static void main(String[] args) {
        AppleBag appleBag = new AppleBag();
        OrangeBag orangeBag = new OrangeBag();
        BananaBag bananaBag = new BananaBag();
        // 苹果纸箱包装后,外加防伪标识、加固包装
        new ReinforceBagDecorator(new CheckedBagDecorator(appleBag)).pack();
        System.out.println("*********************************");
        // 橘子网兜包装后,外加防伪标识、加固包装
        new SpeedBagDecorator(new ReinforceBagDecorator(new CheckedBagDecorator(orangeBag))).pack();

    }
}

结果

苹果使用纸箱包装
===============
打印上防伪标识
===============
加固了包装
*********************************
橘子使用网兜包装
===============
打印上防伪标识
===============
加固了包装
===============
打上加急标识

从上述例子可以看出,装饰器的好处,不仅可以对具体的水果包装类进行装饰,多个装饰器还可以嵌套装饰,非常灵活,这也是为什么,装饰器中需要引用Bag类,就是方便嵌套,因为每个具体的装饰器,本身也是Bag的子类。

3. 源码分析

Java IO类库非常庞大,从大类分,如果从按流的方向来分的话,分为输入流InputStream,输出流OutputStream,如果按照读取的方式分的话,分为字节流与字符流,具体如下图
【趣味设计模式系列】之【装饰器模式】-LMLPHP
针对不同的读取和写入场景,Java IO 又在这四个父类基础之上,扩展出了很多子类,如下图
【趣味设计模式系列】之【装饰器模式】-LMLPHP
OutputStream 是一个抽象类,FileOutputStream 是专门用来写文件流的子类,FilterOutputStream很特殊,它实现了OutputStream,同时持有OutputStream的引用,部分源码如下图:

public class FilterInputStream extends InputStream {
    /**
     * The input stream to be filtered.
     */
    protected volatile InputStream in;

    ...

所以FilterOutputStream在设计的时候本质就是一个装饰器,其子类BufferedOutputStream,DataOutputStream,PrintStream都是具体的装饰器,实现额外的功能。
同样FilterInputStream、InputStreamReader、OutputStreamWriter都是装饰器类,其子类充当具体装饰的功能。
在平时写代码的时候都有如下几行嵌套的写法:

File f = new File("c:/work/test.data");
FileOutputStream fos = new FileOutputStream(f);
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter bw = new BufferedWriter(osw);
bw.write("https://githup.com");
bw.flush();
bw.close();

本质上,OutputStream的实现类可以与具体装饰器实现相互嵌套,具体的装饰器之间也可以相互嵌套,非常灵活,避免了独立为每个类创建子类而产生累爆炸的不合理设计。

4. 总结

4.1 优点

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 处理可以撤销的职责。
  • 扩展子类灵活,避免产生累爆炸。

4.2 缺点

  • 如果最里面的装饰器出错,需要从外面一层一层往里面去查看,多层嵌套导致增加复杂性。
09-13 15:56