装饰模式概念
装饰模式又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
但是纯粹的装饰模式很难找到,大多数的装饰模式的实现都是“半透明”的,而不是完全透明的。换言之,允许装饰模式改变接口,增加新的方法。半透明的装饰模式是介于装饰模式和适配器模式之间的。适配器模式的用意是改变所考虑的类的接口,也可以通过改写一个或几个方法,或增加新的方法来增强或改变所考虑的类的功能。
大多数的装饰模式实际上是半透明的装饰模式,这样的装饰模式也称做半装饰、半适配器模式。
针对的问题
动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。不改变接口的前提下,增强所考虑的类的性能。
何时使用:
1)需要扩展一个类的功能,或给一个类增加附加责任。<br /> 2)需要动态的给一个对象增加功能,这些功能可以再动态地撤销。<br /> 3)需要增加一些基本功能的排列组合而产生的非常大量的功能,从而使继承变得 不现实。
以下场景我建议你谨慎使用:
- 多层封装会提升代码的复杂度,就像剥洋葱一样;
- 继承是静态的给类增加功能,而装饰模式则是动态的增加功能。
角色组成
l 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。<br /> l 具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类<br /> l 装饰角色(Decorator):持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口<br /> l 具体装饰角色(ConcreteDecorator):负责给构件对象“贴上”附加的责任
模式的结构
装饰器模式主要包含以下角色。
- 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
装饰器模式的结构图如图 1 所示。
图1 装饰器模式的结构图
package decorator;
public class DecoratorPattern {
public static void main(String[] args) {
Component p = new ConcreteComponent();
p.operation();
System.out.println("---------------------------------");
Component d = new ConcreteDecorator(p);
d.operation();
}
}
//抽象构件角色
interface Component {
public void operation();
}
//具体构件角色
class ConcreteComponent implements Component {
public ConcreteComponent() {
System.out.println("创建具体构件角色");
}
public void operation() {
System.out.println("调用具体构件角色的方法operation()");
}
}
//抽象装饰角色
class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
public void operation() {
component.operation();
}
}
//具体装饰角色
class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
public void operation() {
super.operation();
addedFunction();
}
public void addedFunction() {
System.out.println("为具体构件角色增加额外的功能addedFunction()");
}
}
实际应用
下面我们看一个例子,我们就以上面说的换装为例。我们先分析一下,换装需要有一个人类用于指定是谁换装、一个服装类为具体服装类的父类、以及服装类下的各种具体服装的类。UML图如下:
1. 人类(Person类)
通过构造方法获取人,再通过show()方法传递出去。
public class Person {
private String name;
public Person() {}
public Person(String name) {
this.name = name;
}
public void show() {
System.out.println(name + "的装扮:");
}
}
2. 服装类(Finery类)
通过构造方法传递参数给show()方法,show()方法为重写父类Person类的方法。
public class Finery extends Person {
protected Person component;
public void Decorate(Person component) {
this.component = component;
}
@Override
public void show() {
if(component != null) {
component.show();
}
}
}
3. 具体服装类
上述UML图中我给了6种服装,这个可以自行设计,内部实现都是相同的,这里就放一个TShirt类,过多的就不赘余了。
public class TShirts extends Finery {
@Override
public void show() {
super.show();
System.out.print("T恤 ");
}
}
4. Client客户端
接下来我们编写一个客户端测试一下装饰模式。
首先先给adam换装,给他穿上西装、领带、皮鞋,然后展示出来;然后再给bill换装,给他穿上T恤、垮裤、球鞋,然后展示出来。我们可以看到,代码中的服装是一层套一层的,比如adam的,先给adam穿上Suits,再给Suits套上Tie,再给Tie套上LeatherShoes,然后对最后一层LeatherShoes展示。
平常当系统需要新功能时,是向旧的类中添加新的代码,这些新加的代码通常装饰了原有类的核心职责或主要行为,这种做法的问题在于,它们再主类中加入了新的字段、新的方法和新的逻辑,从而增加了主类的复杂度,而这些新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的个特殊行为的需要。
public class Client {
public static void main(String[] args) {
//adam的换装
Person adam = new Person("adam");
Suits a = new Suits();
Tie b = new Tie();
LeatherShoes c = new LeatherShoes();
a.Decorate(adam);
b.Decorate(a);
c.Decorate(b);
c.show();
System.out.println("\n--------------");
//bill的换装
Person bill = new Person("bill");
TShirts x = new TShirts();
Trouser y = new Trouser();
Sneakers z = new Sneakers();
x.Decorate(bill);
y.Decorate(x);
z.Decorate(y);
z.show();
}
}
运行结果如下:
而装饰模式却提供了一个非常好的解决方案,它把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象。因此当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择地、按顺序的地使用装饰功能包装对象了。
注释
装饰器模式在 Java 语言中的最著名的应用莫过于 Java I/O 标准库的设计了。例如,InputStream 的子类 FilterInputStream,OutputStream 的子类 FilterOutputStream,Reader 的子类 BufferedReader 以及 FilterReader,还有 Writer 的子类 BufferedWriter、FilterWriter 以及 PrintWriter 等,它们都是抽象装饰类。
下面代码是为 FileReader 增加缓冲区而采用的装饰类 BufferedReader 的例子:
- BufferedReader in = new BufferedReader(new FileReader(“filename.txt”));
- String s = in.readLine();