一、装饰者模式的内容
装饰(Decorator)模式又名包装(Wrapper)模式[GOF95]。装饰者模式动态地将责任附加到对象上。想要扩展功能,装饰者提供有别于继承的另一种选择。该模式以对客户端透明的方式扩展对象的功能。利用组合在运行时动态的合成自己想要的对象,这比继承更具弹性,是继承关系的一个替代方案。
二、装饰者模式的结构
涉及角色
(1)抽象构件角色:定义一个抽象接口,来规范准备附加功能的类。
(2)具体构件角色(即被装饰者):将要被附加功能的类,实现抽象构件角色接口。
(3)抽象装饰者角色:持有对具体构件角色的引用并定义与抽象构件角色一致的接口。
(4)具体装饰角色:实现抽象装饰者角色,负责为具体构件添加额外功能。
其中,被装饰者与抽象装饰者都继承与抽象构件角色,具体装饰角色继承与抽象装饰角色,之所以让装饰者和被装饰者继承于同一组件是想让装饰者和被装饰者具有统一的类型而非为了继承行为。
装饰者模式的基本思想是用装饰者来包装组件使之成为一个同类型的新组件,所以在装饰者角色中,记录当前对象(一般是声明一个基类引用变量,构造器中传对象引用参数初始化此变量),利用多态技术,用基类对象引用最终被包裹后的对象(注意:每包裹一层就把之前的对象覆盖掉),就获得了组件和所有包裹过组件的行为。
三、要点
- 继承属于扩展的形式之一,但不见得是达到弹性设计的最佳方式。(子类数量爆炸、设计死板、以及基类加入的新功能并不能适用与所有子类。)
- 在我们的设计中,应该允许行为可以被扩展,而无须修改现有的代码。
- 组合和委托可以再运行时动态地加上新的行为。
- 除了继承,装饰者模式也可以让我们扩展行为。
- 装饰者模式意味着一群装饰者类,这些类用来包装具体组件。
- 装饰者类反应被装饰的组件的类型
- 装饰者可以再被装饰者的行为前面与/或后面加上自己的行为,甚至将被装饰者的行为整个去掉,而达到特性行为的目的。
- 你可以用无数个装饰者包装一个组件。
- 装饰者一般对组件的客户是透明的,除非客户依赖于组件的具体类型。
- 装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。
- 装饰者和被装饰对象有相同的超类型。
四、装饰者模式当中OO原则
- 封装变化
- 多用组合,少用继承
- 针对接口编程,不针对实现编程
- 交互对象之间的松耦合设计而努力
- 对扩展开放,对修改关闭
五、装饰者模式示例代码
- // the Window interface
- interface Window {
- public void draw(); // draws the Window
- public String getDescription(); // returns a description of the Window
- }
- // implementation of a simple Window without any scrollbars
- class SimpleWindow implements Window {
- public void draw() {
- // draw window
- }
- public String getDescription() {
- return "simple window";
- }
- }
- // abstract decorator class - note that it implements Window
- abstract class WindowDecorator implements Window {
- protected Window decoratedWindow; // the Window being decorated
- public WindowDecorator (Window decoratedWindow) {
- this.decoratedWindow = decoratedWindow;
- }
- public void draw() {
- decoratedWindow.draw();
- }
- }
- // the first concrete decorator which adds vertical scrollbar functionality
- class VerticalScrollBarDecorator extends WindowDecorator {
- public VerticalScrollBarDecorator (Window decoratedWindow) {
- super(decoratedWindow);
- }
- public void draw() {
- decoratedWindow.draw();
- drawVerticalScrollBar();
- }
- private void drawVerticalScrollBar() {
- // draw the vertical scrollbar
- }
- public String getDescription() {
- return decoratedWindow.getDescription() + ", including vertical scrollbars";
- }
- }
- // the second concrete decorator which adds horizontal scrollbar functionality
- class HorizontalScrollBarDecorator extends WindowDecorator {
- public HorizontalScrollBarDecorator (Window decoratedWindow) {
- super(decoratedWindow);
- }
- public void draw() {
- decoratedWindow.draw();
- drawHorizontalScrollBar();
- }
- private void drawHorizontalScrollBar() {
- // draw the horizontal scrollbar
- }
- public String getDescription() {
- return decoratedWindow.getDescription() + ", including horizontal scrollbars";
- }
- }
- public class DecoratedWindowTest {
- public static void main(String[] args) {
- // create a decorated Window with horizontal and vertical scrollbars
- Window decoratedWindow = new HorizontalScrollBarDecorator (
- new VerticalScrollBarDecorator(new SimpleWindow()));
- // print the Window's description
- System.out.println(decoratedWindow.getDescription());
- }
- }
- public abstract class Beverage {
- String description = "Unknown Beverage";
- public String getDescription() {
- return description;
- }
- public abstract double cost();
- }
- public abstract class CondimentDecorator extends Beverage {
- public abstract String getDescription();
- }
- public class DarkRoast extends Beverage {
- public DarkRoast() {
- description = "Dark Roast Coffee";
- }
- public double cost() {
- return .99;
- }
- }
- public class Decaf extends Beverage {
- public Decaf() {
- description = "Decaf Coffee";
- }
- public double cost() {
- return 1.05;
- }
- }
- public class Espresso extends Beverage {
- public Espresso() {
- description = "Espresso";
- }
- public double cost() {
- return 1.99;
- }
- }
- public class HouseBlend extends Beverage {
- public HouseBlend() {
- description = "House Blend Coffee";
- }
- public double cost() {
- return .89;
- }
- }
- public class Milk extends CondimentDecorator {
- Beverage beverage;
- public Milk(Beverage beverage) {
- this.beverage = beverage;
- }
- public String getDescription() {
- return beverage.getDescription() + ", Milk";
- }
- public double cost() {
- return .10 + beverage.cost();
- }
- }
- public class Mocha extends CondimentDecorator {
- Beverage beverage;
- public Mocha(Beverage beverage) {
- this.beverage = beverage;
- }
- public String getDescription() {
- return beverage.getDescription() + ", Mocha";
- }
- public double cost() {
- return .20 + beverage.cost();
- }
- }
- public class Soy extends CondimentDecorator {
- Beverage beverage;
- public Soy(Beverage beverage) {
- this.beverage = beverage;
- }
- public String getDescription() {
- return beverage.getDescription() + ", Soy";
- }
- public double cost() {
- return .15 + beverage.cost();
- }
- }
- public class Whip extends CondimentDecorator {
- Beverage beverage;
- public Whip(Beverage beverage) {
- this.beverage = beverage;
- }
- public String getDescription() {
- return beverage.getDescription() + ", Whip";
- }
- public double cost() {
- return .10 + beverage.cost();
- }
- }
- public class StarbuzzCoffee {
- public static void main(String args[]) {
- Beverage beverage = new Espresso();
- System.out.println(beverage.getDescription()
- + " $" + beverage.cost());
- Beverage beverage2 = new DarkRoast();
- beverage2 = new Mocha(beverage2);
- beverage2 = new Mocha(beverage2);
- beverage2 = new Whip(beverage2);
- System.out.println(beverage2.getDescription()
- + " $" + beverage2.cost());
- Beverage beverage3 = new HouseBlend();
- beverage3 = new Soy(beverage3);
- beverage3 = new Mocha(beverage3);
- beverage3 = new Whip(beverage3);
- System.out.println(beverage3.getDescription()
- + " $" + beverage3.cost());
- }
- }
六、 使用装饰模式的优点和缺点
使用装饰模式主要有以下的优点:
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
- 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错。
使用装饰模式主要有以下的缺点:
由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。
七、 模式实现的讨论
大多数情况下,装饰模式的实现都比上面定义中给出的示意性实现要简单。对模式进行简化时需要注意以下的情况:
(1)一个装饰类的接口必须与被装饰类的接口相容。
(2)尽量保持Component作为一个"轻"类,不要把太多的逻辑和状态放在Component类里。
(3)如果只有一个ConcreteComponent类而没有抽象的Component类(接口),那么Decorator类经常可以是ConcreteComponent的一个子类。如下图所示:
八、装饰者模式在JDK当中应用
Decorator (recognizeable by creational methods taking an instance of same abstract/interface type which adds additional behaviour)
- All subclasses of
java.io.InputStream
,OutputStream
,Reader
andWriter
have a constructor taking an instance of same type. java.util.Collections
, thecheckedXXX()
,synchronizedXXX()
andunmodifiableXXX()
methods.javax.servlet.http.HttpServletRequestWrapper
andHttpServletResponseWrapper