装饰(Decorator)模式
装饰(Decorator)模式又名包装器(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
动机(Motivation):
过多地使用继承来扩展对象的功能,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性,并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多的子类的膨胀(多继承)。如何使“对象功能的扩展”能够根据需要来动态的实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响将为最低?
装饰模式以对客户端透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不使用创造更多的饿子类的情况下,将对象的功能加以扩展。
源代码:
// 抽象构件角色的源代码
interface Component {
/**
* 商业方法
*/
void sampleOperation();
}
// 具体构件源代码
class ConcreteComponent implements Component {
public ConcreteComponent() {}
public void sampleOperation() {
}
}
// 装饰角色源代码
class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
public Decorator() {}
// 商业方法,委派给构件
public void sampleOperation() {
component.sampleOperation();
}
}
// 具体构件源代码
class ConcreteComponent implements Component {
public ConcreteComponent() {}
public void sampleOperation() {
}
}
对象图:
装饰模式的对象图呈链状结构,其共有三个具体装饰类,分别成为 Decorator1,Decorator2,
Decorator3,具体构件类是ConcreteComponent。一个典型的创建过程如代码。
new Decorator1 (
new Decorator2(
new Decorator3(
new ConcreteComponent()
)
)
)
);
代码分析:
抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类。
装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
具体装饰(Concrete Decorator)角色:负责给构件对象“贴上”附加的责任。
这就意味着Decorator1的对象持有一个对Decorator2对象的引用,后者则持有一个对Decorator3对象的引用,再后者持有一个具体构件ConcreteComponent对象的引用,这种链式的引用关系式装饰模式看上去像是一个LinkedList.装饰模式常常被称作包裹模式,就是因为每一个具体装饰类都将是下一个具体装饰类或者具体构件类包裹起来。
装饰模式应当在什么情况下使用:
在以下情况下应当使用装饰模式:
需要扩展一个类的功能,或给一个类增加附加的责任。
需要动态地给一个对象增加功能,这些功能可以再动态地撤销
需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实。
使用装饰模式的优点:
装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
装饰模式允许系统动态地决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系式静态的,它在系统运行前就决定了。
通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加容易出错。
由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较容易实现。但是,在另一方面,使用装饰模式也会产生比使用继承关系更多的对象。更多的对象会使的查错变得困难。
模式实现的讨论:
对装饰模式简化时需要注意以下的情况:
◆ 一个装饰类的接口必须与被装饰类的接口相容。ConcreteDecorator类必须与被装饰类的接口相容。
◆ 尽量保持Component作为一个“轻”类。
这个类的责任是为ConcreteDecorator类提供共同的接口,因此它应当这种看重在提供接口而不是存储数据。Component可以是一个Java接口,而在实际的工作中,它可以是一个抽象类或者是一个具体类。此时,就应当注意不要把太多的逻辑和状态放在Component类里。
◆ 如果只有一个ConcreteComponent类而没有抽象的Component类(接口),那么Decorator类经常可以是ConcreteComponent的一个子类。由此可知没有抽象接口Component也是可以的,但ConcreteComponent就要扮演双重角色。
透明性的要求:装饰模式对客户端的透明性的要求程序不要声明一个ConcreteDecorator类型的变量,而应当声明一个Component类型的变量。
Decorator模式并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点是在于解决“主体类在多个方向上的扩展功能”。