装饰者模式是一种对象结构性模式。它的意图是动态的给一个对象添加一些额外的功能。相比子类继承方式,装饰者模式更加灵活。
考虑这样的场景,有时候我们需要给某个对象添加一些功能,而不是对整个类添加功能。使用继承机制是添加功能的一种有效途径,但是这种方式不够灵活,用户不能控制功能的添加方式与时机。而在装饰者模式中,我们将组件嵌入另一个对象中,由这个对象来完成新功能的添加。我们称这个嵌入的对象为装饰。这个装饰与被装饰的组件实现了相同的接口,它对客户透明。实际上,它将用户的请求转发给组件,并且可以在转发前后执行一些额外的动作,也就是额外的功能。这使得我们可以嵌套多个装饰,可以任意的添加功能,非常自由灵活。
例如,有一个对象 TextView,其功能是可以在窗口中显示正文。当我们需要窗口滚动条时,可以使用 ScrollDecorator 来包装 TextView,从而添加滚动条功能。类似的,当我们还想再窗口周围添加黑色边框时,我们可以使用 BorderDecorator 来包装 TextView 来添加。而且我们可以使用 BorderDecorator 来包装 ScrollDecorator,从而实现一个既有黑色边框、又带有滚动条的窗口。像这样,只要将这些装饰类与 TextView 进行组合,就可以灵活的实现预期的各种效果。
结构
我们先来介绍装饰者模式的结构,然后再使用一个例子来说明。装饰者模式中包含如下角色:
Component:这是一个抽象类或者接口,它定义了需要实现的基本功能。
ConcreteComponent:Component 的子类或者实例,实现了 Component 中定义的方法。它充当了“被装饰”的角色。
Decorator:它也是 Component 的子类,也是装饰者类需要实现的抽象类。它包含了一个指向 Component 对象的指针。
ConcreteDecorator:Decorator 的子类,也就是具体的装饰者类。它将请求转发给它的 Component 对象,并且在转发前后添加附加的动作。
以下是装饰者模式的类图:
装饰者模式类图
这其中,我们可以看到装饰者模式的一些特点:
装饰对象的接口,与被装饰对象的接口是一致的。这样,客户感受不到装饰对象的存在,它对客户是透明的。
装饰对象保存了一个被装饰者的引用。
装饰对象接收用户请求,并将请求转发给真实对象。
在转发请求的过程中,装饰对象可以增加一些附加功能。
例子
了解了装饰者模式的结构与各种角色后,我们还是用一个例子来说明吧。这里使用的是《Head First 设计模式》中,咖啡的例子。首先,我们给所有咖啡定义统一的一个抽象类,也就是 Component。
public abstract class Beverage {
String description = "no description";
public String getDescription() {
return description;
}
public abstract double cost();
}
然后创建实际的咖啡子类,也就是 ConcreteComponent:
public class DarkRoast extends Beverage {
public DarkRoast() {
super();
description = "DarkRoast";
}
@Override
public double cost() {
return 6.5;
}
}
public class HouseHand extends Beverage {
public HouseHand() {
super();
description = "HouseHand";
}
@Override
public double cost() {
return 8.5;
}
}
接下来创建创使其的抽象类,也就是 Decorator。它继承了 Beverage:
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
最后创建装饰类,也就是 ConcreteDecorator。它包含了一个 Beverage 对象引用,将用户请求最终传递给 Beverage,并且加上自己的方法:
public class MochaDecorator extends CondimentDecorator {
Beverage beverage;
public MochaDecorator(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
@Override
public double cost() {
return beverage.cost() + 3.5;
}
}
public class SoyDecorator extends CondimentDecorator {
Beverage beverage;
public SoyDecorator(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Soy";
}
@Override
public double cost() {
return beverage.cost() + 1.5;
}
}
最后是客户端测试类:
public class Client {
public static void main(String[] args) {
Beverage darkRoast = new DarkRoast();
Beverage houseHand = new HouseHand();
// Dark Roast with double Mocha
Beverage darkRoastWithMocha = new MochaDecorator(darkRoast);
Beverage darkRoastWithMocha2 = new MochaDecorator(darkRoastWithMocha);
// houseHand + Soy
Beverage houseHandWithSoy = new SoyDecorator(houseHand);
Beverage houseHandWithSoyAndMocha = new MochaDecorator(houseHandWithSoy);
System.out.println(darkRoastWithMocha2.getDescription()
+ ", price : " + darkRoastWithMocha2.cost() + "$");
System.out.println(houseHandWithSoyAndMocha.getDescription()
+ ", price : " + houseHandWithSoyAndMocha.cost() + "$");
}
}
这里我们创建了两杯咖啡,其中一杯,DarkRoast,加入了两份 Mocha;另一杯,HouseHand,加入了一份 Soy,一份 Mocha。最后程序按照预期,输出如下结果:
DarkRoast, Mocha, Mocha, price : 13.5$
HouseHand, Soy, Mocha, price : 13.5$
Java 中的装饰者模式
Java 中的装饰者模式,最典型的例子是 java.io 包下 InputStream 与 OutputStream 相关的类了。理解了装饰者模式的原理与结构后,回过头来再看看 Java 中的 IO 相关类结构,就非常清晰了。下面我们看一下 InputStream 的一个简单的类图:
InputStream类图
可以看出,InputStream 就是装饰者模式中的 Component,ByteArrayInputStream、FileInputStream 等是被装饰者,这些类都提供了最基本的字节读取功能。 而 FilterInputStream 是 Decorator,BufferedInputStream、DataInputStream 是实际的装饰者。理清了这层关系,我们就可以总结出 Java IO 包的使用套路了:
File file = new File("hello.txt");
FileInputStream in = new FileInputStream(file);
BufferedInputStream inBuffered = new BufferedInputStream(in);
装饰者模式的优缺点
最后,我们总结一下装饰者模式的优点与缺点。
装饰者模式主要有两个优点:
比静态继承更加灵活。与静态继承相比,装饰者模式提供了更加灵活的面向对象添加职责的方式。相比之下,继承机制需要为每个添加的职责创建一个新的子类,这样会产生大量的子类,增加系统复杂度。除此之外,可以为一个特定的 Component 提供不同的 Decorator,使得可以对一些职责进行混合与匹配。使用装饰者模式可以容易的添加重复特性,而重复继承是很容易出错的。
避免在层次结构高层的类拥有太多的特性。装饰者模式并不试图在一个复杂的可定制的类中支持所有可预见的特性,相反,我们可以定义一个简单的类,并且使用 Decorator 类给它逐渐的添加功能,从简单的部件组合出复杂功能。这样,应用程序不必为不需要的特性付出代价。
同时,装饰者模式也主要有两个缺点:
Decorator 与它的 Component 不一样。Decorator 是一个透明的包装,一个被装饰了的组件和这个组件本身,还是有差别的。
会产生许多小对象。这些小对象看上去都非常类似,可能仅仅在相互连接的方式上有所不同,这使得虽然容易对他们进行定制,但是也增加了排错难度。