装饰模式是一种结构型设计模式。装饰模式的思想是:以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
装饰模式涉及4个角色:
- 抽象构件(Component):要被装饰的类的抽象。
- 具体构件(Concrete Component):要被装饰的具体类型。
- 抽象装饰者(Decorator):内部包装了一个构件的实例,并实现与构件接口相同的接口。
- 具体装饰者(Concrete Decorator):为被装饰者增加加或减少行为。
一般的装饰模式
结构图:
代码实现:
抽象构件、具体构件:
public interface Component {
void method1();
}
public class ConcreteComponent implements Component {
@Override
public void method1() {
System.out.println("method 1");
}
}
抽象装饰者、具体装饰者:
public class Decorator implements Component {
// 包装一个构件的对象
private Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void method1() {
component.method1();
}
}
public class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void method1() {
super.method1();
System.out.println("hey geek!"); // 增加行为
}
}
public class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
@Override
public void method1() {
super.method1();
System.out.println("fxcku!"); // 增加行为
}
}
// 测试
class DecoratorTest {
public static void main(String[] args) {
// 未被装饰的对象
Component comp = new ConcreteComponent();
comp.method1();
// 装饰后的对象
Component component = new ConcreteDecoratorA(new ConcreteComponent());
component.method1();
}
}
运行结果:
method 1
method 1
hey geek!
装饰模式的简化
如果只有一个
ConcreteComponent
而没有抽象的Component
接口,可以让Decorator
直接继承ConcreteComponent
。
如果只有一个
ConcreteDecorator
,可以将Decorator
和ConcreteDecorator
合并。
装饰模式的透明性
装饰模式对客户端的透明性要求程序不要声明一个ConcreteComponent
类型的变量,而应当声明一个Component
类型的变量。
例如上面的测试代码,我们应该声明一个Component
类型的变量,而非声明一个ConcreteDecoratorA
类型的变量。
// 装饰后的对象
Component component = new ConcreteDecoratorA(new ConcreteComponent());
// 下面这样不符合透明性要求
ConcreteDecoratorA component = new ConcreteDecoratorA(new ConcreteComponent());
然而,纯粹的装饰模式很难找到。装饰模式的用意是在不改变接口的前提下,增强所考虑的类的性能。在增强性能的时候,往往需要建立新的公开的方法。这就导致了大多数的装饰模式的实现都是“半透明”的,而不是完全透明的。这意味着客户必须声明ConcreteDecorator
类型的变量,才可以调用ConcreteDecorator
类中新增的方法。
public class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
@Override
public void method1() {
super.method1();
System.out.println("fxcku!");
}
public void method2() {
// 装饰者新增的方法
}
}
// 测试
class DecoratorTest {
public static void main(String[] args) {
// 声明为Component类型,符合透明性要求,但接口中没有method2(),所以没法调用
Component component = new ConcreteDecoratorB(new ConcreteComponent());
component.method1();
// 想要调用method2就必须声明为具体装饰者的类型,这无疑破坏了透明性
ConcreteDecoratorB decoratorB = new ConcreteDecoratorB(new ConcreteComponent());
decoratorB.method1();
decoratorB.method2();
}
}
半透明的装饰模式是介于装饰模式和适配器模式之间的。适配器模式的用意是改变所考虑的类的接口,也可以通过改写一个或几个方法,或增加新的方法来增强或改变所考虑的类的功能。大多数的装饰模式实际上是半透明的装饰模式,这样的装饰模式也称做半装饰半适配器模式。
(图片来源于网络)
装饰模式的总结
- 优点:
- 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。
- 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。
- 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合“开闭原则”。
- 缺点:
- 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,大量小对象的产生势必会占用更多的系统资源,在一定程序上影响程序的性能。
- 装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。
- 适用场景:
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。
- 例子:
Java.io
中的输入流和输出流的设计、javax.swing
包中一些图形界面构件功能的增强。