引子
先列举一个生活中的场景。假如我们买了一套房(买不起。。),但是是一个毛胚房(指没有任何装修的房子),只能住。那么我们为了让房子变得温馨舒适,就要进行装修了,这样没有改变房子原本用来居住的功能,还增加了很多新的功能(比如做饭)。
那么这一期的装饰模式就是基于这样的动机,在不改变原有功能的情况下添加新功能。
那么给一个类或对象增加新的行为可以通过两种方式来完成:
- 继承:通过继承可以使子类在拥有自身方法的同时拥有父类方法,但这种增加行为的方式是静态的,用户不能控制增加行为的方式和时机,只能被动的使用新子类的行为。
- 关联:通过给装饰器嵌入一个对象,在装饰器中可以调用原有类的方法,也可以增加新的方法扩充原有类。可以动态的给对象添加新的行为。
模式简介
定义:动态的给一个对象添加新的职责,就增加对象功能来说,比生成子类的方式要更加灵活。
模式结构图:
说明:
- Component:抽象构件类,是具体构件和抽象装饰类的共同父类。其中定义了具体构件中需要实现的业务方法,它的存在使得客户端可以一致的处理未被装饰的对象以及装饰后的对象,实现客户端的透明操作(后面说明什么是透明操作)。
- ConcreteComponent:具体构件,它是抽象构件类的子类,实现了其中的业务方法。装饰器可以给它添加新的职责。
- Decorator:抽象装饰类,它是抽象构建类的子类,用于给具体构建类添加职责,具体实现在其子类中。它维护一个指向抽象构件类对象的引用,通过该引用调用未被装饰对象的方法,并通过子类扩展该方法达到装饰的目的。
- ConcreteComponent:具体装饰类,负责向构建添加新的职责。
举个例子
抽象构件:
public interface Component {
void show();
}
具体构建和抽象装饰类
public class ConcreteComponent implements Component {
@Override
public void show() {
System.out.println("我是目标对象。。。");
}
}
public class Decorator implements Component {
private Component com;
public Decorator(Component com) {
this.com = com;
}
@Override
public void show() {
com.show();
}
}
具体的装饰类:
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component com) {
super(com);
System.out.println("开始装饰。。。");
}
public void show() {
super.show();
System.out.println("增加新功能A。。。");
}
}
public class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component com) {
super(com);
}
public void show() {
super.show();
System.out.println("添加第二个新功能。。。");
}
}
测试:
public class DecoMain {
public static void main(String[] args) {
Component target = new ConcreteComponent();
System.out.println("未装饰前-----------");
target.show();
Decorator fist = new ConcreteDecorator(target);
System.out.println("第一次装饰--------");
fist.show();
Decorator sec = new ConcreteDecoratorB(target);
System.out.println("第二次装饰---------");
sec.show();
Decorator three = new ConcreteDecoratorB(fist);
System.out.println("装饰两次----------");
three.show();
}
}
//运行结果
未装饰前-----------
我是目标对象。。。
开始装饰。。。
第一次装饰--------
我是目标对象。。。
增加新功能A。。。
第二次装饰---------
我是目标对象。。。
添加第二个新功能。。。
装饰两次----------
我是目标对象。。。
增加新功能A。。。
添加第二个新功能。。。
这里举了一个简单的例子,可以看出来装饰模式的一些特点了。可以动态的增加新的功能,且容易控制添加新行为的时机。
优缺点
优点
- 扩展一个对象的行为,装饰模式比继承要更加灵活,不会导致类的个数急剧增加。
- 可以通过动态的方式来扩展一个类的功能。
- 对一个对象可以多次装饰。
- 具体构件类和具体装饰类可以独立变化,用户可以根据需求增加。
缺点
- 使用装饰模式的过程中会产生很多的小对象,比如多次装饰时,中间的类。
- 虽然装饰模式比继承要更加灵活,但也意味着更加容易出错,排错也更困难。比如多级装饰的时候,排错需要逐级排查,较为繁琐。
适用场景
- 在不影响其他对象的情况下,动态、透明的方式为单个对象添加功能。
- 不能适用继承或者适用继承不利于系统的扩展和维护时可以使用装饰模式。
常见的使用场景
- java.swing中很多场景,比如为一个Jlist添加滚动条
JList list = new JList();
JScrollPane jsp = new JScrollPane(list);
- 其实最经典的就是java的IO
其中抽象构件是InputStream,具体构件是FileInputStream、ByteArrayInputStream等。抽象装饰类是FilterInputStream,具体装饰类BufferedInputStream(提供缓冲的功能)等。
一些问题
- 在开始的时候我们说到抽象构件的存在是为了使客户端的操作透明。那么这里说的透明就是说客户端完全针对抽象编程,即所有的声明都应该是抽象构件类型。在上面的示例中应当表现为这样:
Component target = new ConcreteComponent();
Component fist = new ConcreteDecorator(target);
Component sec = new ConcreteDecoratorB(target);
Component three = new ConcreteDecoratorB(fist);
.......
这个也好理解,就是多态的体现罢了。
- 还有一种半透明的模式,这种模式下允许客户声明具体的装饰类,并且调用具体装饰类中新增的方法。(我举的例子中是直接输出的,原理相似)可以是这样的使用模式:
ConcreteDecorator obj = new ConcreteDecorator(target);
obj.newMethod();//新增的方法
注意事项
- 尽量保证装饰类的接口和被装饰类的接口相同,这样对于客户端来说装饰前后的对象都可以一致对待,也就是说尽可能使用透明模式。
- 保证具体的装饰类是一个“轻”类,也就是说不要添加太多的行为,可以通过装饰的方式添加。
- 如果只有一个具体构件类,那么抽象装饰类可以直接继承它。