概述
装饰器模式一种动态地往一个类中添加新的行为的设计模式。就功能而言,修饰模式相比生成子类更为灵活,这样可以给某个对象而不是整个类添加一些功能。
一般来说,我们想给某个类或者对象添加行为有两种方式:继承方式,组合方式。装饰器模式使用的是组合方式。
实现
首先看下装饰器模式的UML图
可以看到,装饰器模式主要有
- 抽象被装饰组件
- 具体被装饰组件
- 抽象装饰类
- 具体装饰类
下面我将以煎饼的代码为例子,演示装饰器模式
/**
* 抽象组件:煎饼
*/
public interface JianBing {
JianBing make();
}
/**
* 具体组件:杨氏煎饼
*/
public class YangShiJianBing implements JianBing {
public JianBing make() {
System.out.println("杨式煎饼");
return this;
}
}
/**
* 抽象装饰器:调料
*/
public AbstractClass TiaoLiao implements JianBing{
protected JianBing jianbing;
public TiaoLiao(JianBing jianbing) {
this.jianbing = jianbing;
}
public JianBing make() {
jianbing.make();
}
}
/**
* 具体装饰器:加孜然
*/
public class AddZiRan extends TiaoLiao {
public AddZiRan (JianBing jianbing) {
super(jianbing);
}
public JianBing make() {
jianbing.make();
addZiRan(jianbing);
}
public void addZiRan(JianBing jianbing) {
System.out.println(" 加孜然 ");
}
}
/**
* 具体装饰器:加辣
*/
public class AddPepper extends TiaoLiao {
public AddPepper (JianBing jianbing) {
super(jianbing);
}
public JianBing make() {
jianbing.make();
addPepper(jianbing);
}
public void addPepper(JianBing jianbing) {
System.out.println(" 加辣 ");
}
}
/**
* 客户
**/
public class Client {
public static void main(String[] args) {
// 煎饼 加孜然 加辣
JianBing jianbing = new YangShiJianBing();
JianBing jianBingAddPepper = new AddPepper(jianbing);
JianBing jianBingAddZiRan = new AddZiRan(jianBingAddPepper);
jianBingAddZiRan.make();
}
}
可以看到,使用了装饰器模式之后,我们可以动态(甚至递归)地给组件(煎饼)添加需要的行为(调料),很棒。这和代理模式有本质的区别。
装饰器模式的实例
一个比较著名的例子是Java的I/O标准库的设计,其部分如下所示
根据上图可以看出:
- 抽象构建角色(Component):由InputStream扮演。这是一个抽象类,为各种子类型提供统一的接口。
- 具体构件角色(ConcreteComponent):由ByteArrayInputStream、FileInputStream、StringBufferInputStream等类扮演。它们实现了抽象构件角色所规定的接口。
- 抽象装饰角色(Decorator):由FilterInputStream、ObectInputStream等类扮演。它们实现了InputStream所规定的接口。
- 具体装饰角色(ConcreteDecorator):由几个类扮演,分别是BufferedInputStream、DataInputStream以及两个不常用到的类LineNumberInputStream、PushbackInputStream。
装饰器模式的优点
装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
装饰器模式的缺点
这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。