1. 简介
装饰器模式(Decorator Pattern):动态地给一个对象添加职责,就增加功能来说,装饰器比生成子类更灵活。
2. 示例
水果店需要给网上客户发货,除了包装之外,需要对特定水果包装加额外装饰,比如加防伪标志、加固、加急等额外功能,但在外部看来还是打包组件。
类图设计
水果包装接口类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,如果按照读取的方式分的话,分为字节流与字符流,具体如下图
针对不同的读取和写入场景,Java IO 又在这四个父类基础之上,扩展出了很多子类,如下图
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 缺点
- 如果最里面的装饰器出错,需要从外面一层一层往里面去查看,多层嵌套导致增加复杂性。