一、定义
装饰者模式是一种比较常见的模式,其定义如下:
动态地给对象添加一些额外的职责,就增加功能来说,装饰者模式相比生成子类更为灵活。如果不用装饰者模式,我们想要扩展一个对象的功能,我们可能会采用继承该对象的方式,然后重写里面的方法来实现扩展原有功能,但当对象变化频繁的时候,这种子类会有很多,装饰者模式有效避免了这种创建大量子类的现象,动态地扩展对象的功能。
装饰者模式通用类图如下:
二、角色分析
装饰者模式有四个角色需要说明:
- Component抽象构件
抽象构建类,可以是抽象类或者接口,定义了被装饰者类的一些抽象方法等,即最基本的功能。
- ConcreteComponent具体构件
具体构建类,实现或者继承了抽象构建类,实现了具体的方法,真正被装饰的其实是它。
- Decorator装饰角色
一般是一个抽象类,实现接口或者抽象方法,它里面不一定有抽象的方法,在它的属性里面必然有一个private变量指向Component抽象构件。
- ConcreteDecorator具体装饰角色
真正在这里扩展原始对象的功能,针对不同的具体构建类,可以定义多个具体装饰角色,例如ConcreteDecoratorA和ConcreteDecoratorB是两个具体的装饰类。
三、装饰者模式案例分析
下面我们以一个日常生活中常见的例子来说明装饰者模式的使用。
冬天快到了,很多小伙伴都会喜欢打火锅,打火锅肯定要有一个汤底,然后我们可能还会点各种各样的配菜,在增加配菜的同时,我们需要动态计算吃这一顿的总价钱。
接下来我们就使用装饰者模式来把上面这种场景进行抽象。
首先,UML类图如下:
我们首先定义抽象构件角色,主要提供两个方法:获取价格的方法getPrice()、获取描述信息的方法getDesc(),代码如下:
/**
* 抽象构件: 火锅类
*/
public abstract class HotPotComponent {
/**
* 获取价格
*
* @return
*/
protected abstract BigDecimal getPrice();
/**
* 获取描述信息
*
* @return
*/
protected abstract String getDesc();
}
接下来定义一个具体构件角色:本例中就是火锅底料对象,真正装饰的也就是它。
/**
* 具体构件类: 火锅底料
*/
public class HotPotSeasoningComponent extends HotPotComponent {
@Override
protected BigDecimal getPrice() {
return BigDecimal.valueOf(20);
}
@Override
protected String getDesc() {
return "火锅底料";
}
}
接着,定义一个抽象装饰者类,它继承自抽象构件角色,内存持有一个指向抽象构件角色的引用:
/**
* 抽象装饰者类
*/
public abstract class AbstractDecorator extends HotPotComponent {
private HotPotComponent hotPotComponent;
public AbstractDecorator(HotPotComponent hotPotComponent) {
this.hotPotComponent = hotPotComponent;
}
@Override
protected BigDecimal getPrice() {
return hotPotComponent.getPrice().add(getSideDishPrice());
}
@Override
protected String getDesc() {
return hotPotComponent.getDesc() + " + " + getSideDishDesc();
}
/**
* 获取配菜的描述
*
* @return
*/
protected abstract String getSideDishDesc();
/**
* 获取配菜的价格
*
* @return
*/
protected abstract BigDecimal getSideDishPrice();
}
可以看到, 除了实现抽象构件的抽象方法之外,抽象装饰者角色内存还定义了配菜的一些方法。并且重写了抽象构件的getPrice()方法和getDesc()方法,动态的计算所有配菜的总价格。
有了抽象装饰者角色,就需要定义具体的装饰者角色进行增强了:
/**
* 具体装饰者角色: 肥牛
*/
public class FatCattleDecorator extends AbstractDecorator {
public FatCattleDecorator(HotPotComponent hotPotComponent) {
super(hotPotComponent);
}
@Override
protected String getSideDishDesc() {
return "肥牛";
}
@Override
protected BigDecimal getSideDishPrice() {
return BigDecimal.valueOf(20);
}
}
/**
* 具体装饰者角色: 小白菜
*/
public class CabbageDecorator extends AbstractDecorator {
public CabbageDecorator(HotPotComponent hotPotComponent) {
super(hotPotComponent);
}
@Override
protected String getSideDishDesc() {
return "小白菜";
}
@Override
protected BigDecimal getSideDishPrice() {
return BigDecimal.valueOf(10);
}
}
最后,我们定义一个场景类进行测试:
public class Client {
public static void main(String[] args) {
//层层包装
HotPotComponent hotPotComponent = new HotPotSeasoningComponent();
//一份肥牛
hotPotComponent = new FatCattleDecorator(hotPotComponent);
//一份肥牛
hotPotComponent = new FatCattleDecorator(hotPotComponent);
//一份小白菜
hotPotComponent = new CabbageDecorator(hotPotComponent);
System.out.println(hotPotComponent.getDesc() + " = " + hotPotComponent.getPrice() + "元");
}
}
运行结果:
火锅底料 + 肥牛 + 肥牛 + 小白菜 = 70元
可见,成功扩展原有对象的功能,这样我们以后如果需要扩展火锅底料的功能,只需要增加一个具体的装饰者角色即可实现扩展,而不需要改动原有的代码,这符合开闭原则。
四、装饰者应用分析
装饰者模式在我们的IO流中体现的最为显著,下面我们看一下IO流中InputStream相关的UML类图:
对应前面的通用类图应该很容易看出各个角色分别是谁:
- OutputStream和InputStream就对应于抽象构件角色(Component);
- FileInputStream和FileOutputStream就对应具体构件角色(ConcreteComponent);
- FilterOutputStream和FilterInputStream就对应着抽象装饰角色(Decorator);
- BufferedOutputStream,DataOutputStream等等就对应着具体装饰角色;
下面是关键源码:
//抽象类,抽象构建角色
public abstract class InputStream implements Closeable {
//省略...
}
//具体构建角色,也是具体被装饰的类
public class FileInputStream extends InputStream{
//省略...
}
//装饰者基类,该类持有一个抽象构建角色InputStream的引用
public class FilterInputStream extends InputStream {
/**
* The input stream to be filtered.
*/
protected volatile InputStream in;
//省略...
}
//具体装饰者角色
public class BufferedInputStream extends FilterInputStream {
//省略...
}
五、总结
装饰者模式的优点:
- 装饰者类和被装饰者类互不影响,独立扩展,他们之间没有耦合关系;
- 装饰者模式是频繁继承的一个替换解决方案,可以重复包装,包装之后返回的还是抽象构建角色;
- 装饰者模式可以动态地扩展一个实现类的功能;
装饰者模式的缺点:
- 多层装饰之后会使系统更加复杂,后期不太方便维护和扩展,所以不建议嵌套太多层装饰,尽量减少装饰类的数量,降低系统的复杂度。
装饰者模式的使用场景:
- 需要动态扩展一个类的功能,或给一个类在增加附加功能;
- 需要频繁使用继承才能扩展功能时可以考虑使用装饰者模式,可以减少类的创建;
- 需要为一批的兄弟类进行改装或加装功能,当前是首选装饰者模式;