介绍
装饰器模式也称为包装模式(Wrapper Pattern) 是指在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式。
装饰器模式的核心是功能扩展,使用装饰器模式可以透明且动态地扩展类的功能。
装饰器模式代码举例
实现逻辑
- 装饰器类和原始类实现共同的父类,下方案例中的 Coffe
- 装饰器类,组合原始类的对象作为目标对象
- 装饰器类重写需要装饰的方法,并且重写的内容中可以调用原始类的对象方法。
- 通过中间层的装饰器基类,避免实现每个共同父类的方法
代码
//装饰器和目标类基础接口
public interface Coffee {
String getDescription();
double getCost();
}
//具体的咖啡,什么都不加
public class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double getCost() {
return 1.0;
}
}
//装饰器基类,为了避免每个装饰器都要手动重新实现共同父类的接口,即使该装饰器不需要装饰对应方法
public abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
@Override
public abstract String getDescription();
@Override
public double getCost() {
return decoratedCoffee.getCost();
}
}
//摩卡咖啡装饰器,增加风味描述、增加价格
public class MochaDecorator extends CoffeeDecorator {
public MochaDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription() + ", Mocha";
}
@Override
public double getCost() {
return super.getCost() + 0.5;
}
}
//奶油装饰器,增加奶油、增加价格
public class WhipDecorator extends CoffeeDecorator {
public WhipDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription() + ", Whip";
}
@Override
public double getCost() {
return super.getCost() + 0.3;
}
}
//测试类
public class Main {
public static void main(String[] args) {
// 创建一杯简单的咖啡
Coffee simpleCoffee = new SimpleCoffee();
// 添加摩卡装饰
Coffee mochaCoffee = new MochaDecorator(simpleCoffee);
// 再添加奶泡装饰
Coffee finalCoffee = new WhipDecorator(mochaCoffee);
System.out.println(finalCoffee.getDescription() + " costs $" + finalCoffee.getCost());
}
}
Simple Coffee, Mocha, Whip costs $1.8
IO 类库的装饰器模式
为什么没有 BufferdFileInputStream
在 JAVA 的 IO 类库中,例如如果让 FileInputStream 支持 BufferdInputStream,需要让 需要间接的将 FileInputStream 传递给 BufferdInputStream。为什么 Java 不直接支持BufferdFileInputStream 呢。
InputStream in = new FileInputStream("");
InputStream bin = new BufferedInputStream(in);
为什么不基于继承实现 BufferedFileInputStream
- 如果 InputStream 类只有一个子类 FileInputStream 那么再在 FileInputStream 下面实现一个BufferedInputStream 也没有什么问题
- 问题在于 InputStream 的子类太多了,如果每个子类都单独实现 Buffered 功能和 DataInputStream 的功能那么类的数量将会爆炸性增多。
IO 类库使用装饰模式实现
- 在设计原则中,组合优于继承,针对继承结构过于复杂的问题可以将继承关系转换为组合关系来解决。
- 所以 IO 类库中,对于 BufferedInputStream 没有选择对所有的 InputStream 实现一遍,而是只实现一遍通过组合目标 InputStream 来实现增强功能
- 对于需要实现 Buffered 的功能的 inputStream,只需使用BufferedInputStream 对其进行一次包装即可实现。
- 并且如果想要实现 DataInputStream 的按照数据类型读取,只需要再次添加一层包装即可。
总结&思考
总结
- 装饰器模式可以解决继承关系过于复杂的问题,通过组合关系替代继承关系。
- 装饰器模式主要的作用是给原始类添加增强功能,除此之外装饰器模式还支持嵌套使用,为了满足这个功能装饰器类和原始类都继承自相同的父类或者接口。
- 装饰器模式和静态代理模很相似,都是通过组合来对原始类进行增强,主要区别是代理模式主要对原始类不相关的功能进行增强,但是装饰器模式是对原始类相关功能的增强。
IO 类库的 FilterInputStream 的作用是什么
- 和案例中的装饰类基类作用一样,提供 InputStream 需要实现的方法的实现。方法的具体执行通过委托给组合的 InputStream 对象实现。
- 如果没有 FilterInputStream,那么每个装饰器类,都需要对 InputStream 的方法提供实现,即使是委托给组合的 InputStream 成员对象执行也会很麻烦。
装饰器类和原始类有相同的父类的作用
装饰器类和原始类有相同的父类,例如 InputStream 和 Coffee 可以实现对原始类进行“嵌套”多个装饰器类来进行增强。例如对 FileInputStream 包装 BufferedInputStream 后再包装一层 DataInputStream,这样既实现了缓存读取又实现类按照基本数据类型来读取。
和其他组合代替继承的设计模式(例如代理模式)的区别
代理模式中,代理类附加的是原始类不相关的功能,但是装饰器模式中附加的是与原始类相关的增强功能。