23

【趣味设计模式系列】之【装饰器模式】

 3 years ago
source link: http://www.cnblogs.com/father-of-little-pig/p/13622216.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

1. 简介

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

2. 示例

水果店需要给网上客户发货,除了包装之外,需要对特定水果包装加额外装饰,比如加防伪标志、加固、加急等额外功能,但在外部看来还是打包组件。

bENnayy.png!mobile

类图设计

eymyEvf.png!mobile

水果包装接口类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,如果按照读取的方式分的话,分为字节流与字符流,具体如下图

6zEZBzi.png!mobile

针对不同的读取和写入场景,Java IO 又在这四个父类基础之上,扩展出了很多子类,如下图

j6bquub.png!mobile

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 缺点

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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK