当你想为现有的类增加功能或者属性时,可能第一个会想到使用继承。继承可以在编译阶段为类增加额外的功能,但是使用继承会产生一些关于新建过多的类、维护、更新相关的问题:
一个很经典的例子,关于咖啡的,想象这样一种情况:一个咖啡店的程序,有一个抽象的咖啡类,“摩卡咖啡”,“黑咖啡”,“拿铁咖啡”继承自抽象咖啡类,似乎这样设计没什么问题。但是想想星巴克咖啡,在点咖啡的时候可以选择加糖,加奶,加摩卡等一系列咖啡调味品,而且额外的调味料是需要另付费的,所以像“加双倍摩卡加糖的摩卡咖啡”可能会需要单独继承出一个类来实现,而“加普通量摩卡加糖的摩卡咖啡”也需要单独继承出一个类来实现,这样会出现什么情况?一旦调味品种类很多,而“摩卡咖啡”,“黑咖啡”,“拿铁咖啡”这样的基础咖啡种类也很多的话,因为他们组合出来的各种各样的咖啡都需要一个单独的类来表示,这样维护起来会相当困难。
另外一个例子,当你使用一些工具接口的时候,这里比如是一个window类,使用这个类的draw()方法可以画出一个基本样式的窗口,如果想给窗口加上垂直滚动条或者水平滚动条或者边框的话可能需要继承window类并作一些功能添加,假如我们需要一个带垂直滚动条的窗口、一个带水平滚动条的窗口、一个带边框的窗口、一个既带垂直滚动条也带水平滚动条的窗口、一个带垂直滚动条水平滚动条和边框的窗口,我们需要通过继承派生出这么多的类来,而且如果边框的样式需要变动我们很可能需要修改原先的代码或者通过继承创建更多的类。
遇到以上情况下如果我们使用“装饰模式”效果就不一样了XD,当你想在程序运行时动态的为某个对象增加特定的行为或属性时可以考虑使用“装饰模式”来替代使用继承(在编译阶段为类增加行为或属性)。关于“装饰模式”的类关系图如下:
图片来自:Search
观察上边的类图, 可以看到位于最顶层的是一个抽象的组件类,第二层左侧为实体组件类(有特定意义,可以用其创建对象,即为被装饰的基础对象),右侧为抽象的装饰类,第三层为实体装饰类ConcreteDecoratorA为Operation方法增加了额外的功能,ConcreteDecoratorB则增加了一个新的属性。这里需要思考一些问题:
Q:
1. Decorator类为什么设计为抽象的?
2. 为什么实体组件类与抽象的装饰类都继承了Component抽象类?
3. 怎么用Decorator来动态扩充ConcreteComponent类对象的功能?(这正是装饰模式的目的)
A:在下面。
装饰模式理解起来很抽象,用语言不容易表达,在网上看了很多关于装饰模式的文章,如果不是实现理解了代码光看文字着实费力,看看实现的代码,以Window那个为例:
这个例子中需要5个类与上面类图对应分别是:
Window类->>Component, // 抽象的window类
NormalWindow->>ConcreteComponent, // 继承自Window类的实体类
WindowDecorator->>Decorator, // 抽象的装饰类
ScrollDecorator->> ConcreteDecoratorA, // 继承自WindowDecorator的实体装饰类,为窗口加上滚动条功能
BorderDecorator->> ConcreteDecoratorB // 继承自WindowDecorator的实体装饰类,为窗口加上边框
Window类代码:
public abstract class Window { // 窗口标题 String description = "UnknowWindow"; // 绘制窗口的抽象方法,每个子类负责实现此方法 public abstract void draw(); // 得到窗口标题的方法 public String getDescription() { return description; } }
NormalWindow类的代码:
public class NormalWindow extends Window { public NormalWindow() { this.description = "NormalWindow"; } @Override public void draw() { System.out.print("Draw a normal window"); } }
WindowDecorator类的代码:
public abstract class WindowDecorator extends Window { // 这里将getDescriptiong 设置为了抽象方法,所有的装饰类子类都需要重新实现此方法 public abstract String getDescription(); }
ScrollDecorator类代码:
public class ScrollDecorator extends WindowDecorator { Window componentWindow; // 构造方法,传入Window类型的对象来初始化componentWindow实例变量, // 即要装饰的对象的引用。 public ScrollDecorator(Window componentWindow) { this.componentWindow = componentWindow; } // 为draw增加了滚动条功能 public void draw() { componentWindow.draw(); System.out.print(" add a scrollbar "); } @Override public String getDescription() { return componentWindow.getDescription()+" with scrollBar"; } }
BorderDecorator类代码:
public class BorderDecorator extends WindowDecorator { Window componentWindow; // 构造方法,传入Window类型的对象来初始化componentWindow实例变量, // 即要装饰的对象的引用。 public BorderDecorator(Window componentWindow) { this.componentWindow = componentWindow; } // 为draw增加了边框功能 public void draw() { componentWindow.draw(); System.out.print(" add border "); } @Override public String getDescription() { return componentWindow.getDescription()+" has border"; } }
测试类代码:
public class Test{ public static void main(String[] args) { Window normalWindow = new NormalWindow(); System.out.println(normalWindow.getDescription()); // 用滚动条装饰 Window windowWithScrollbar = new ScrollDecorator(normalWindow); System.out.println(windowWithScrollbar.getDescription()); // 用边框装饰 Window windowWithScrollbarAndBorder = new BorderDecorator(windowWithScrollbar); System.out.println(windowWithScrollbarAndBorder.getDescription()); windowWithScrollbarAndBorder.draw(); } }
输出:
NormalWindow
NormalWindow with scrollBar
NormalWindow with scrollBar has border
Draw a normal window add a scrollbar add border
通过以上代码及类图应该能更形象的了解了装饰模式一下是Q&A的回答部分:
A:
1. 简单的说,因为Decorator类中的某些继承自Component类的非抽象的方法需要被重新设置成abstract(抽象)的,比如getDescription()方法,因为每个子类都需要重新实现这个方法。当然这只是代码中可以看到的一个方面。
2. 因为装饰模式的实现原理,Decorator类中都会有一个Component类型的引用实例变量,存放的是它要装饰的对象的引用,是通过Decorator的构造函数传入Component类型的引用实例变量作为参数来初始化的。试想如果Decorator没有继承自Component,拿Window类举例的话,NormalWindow需要加上纵向滚动条,那么NormalWindow对象则需要作为参数传入那个带有纵向滚动条功能的Decorator类,可是如果在加上纵向滚动条后即:new ScrollDecorator(normalWindow) 执行后,normalWindow对象还需要加上一个边框那该如何操作呢?显然当Decorator也继承Component类后, new ScrollDecorator(normalWindow)自身也是Window类型的也可以作为被装饰的对象传入另外的Decorator类中,例如测试代码中的new BorderDecorator( new ScrollDecorator(normalWindow) );
3. 像上边示例, 被装饰的对象作为参数传入Decorator对象的构造函数中,用来初始化Decorator对象中的Component类型的引用变量。NormalWindow类继承自抽象的Window类,Window类中有一个抽象的draw()方法,NormalWindow实现为可以生成一个没有滚动条,没有边框的窗口,而ScrollDecorator作为装饰类对象,为NormalWindow类对象增加了滚动条功能,具体实现为ScrollDecorator对象重写了draw()方法是这样的:
NormalWindow继承自Window的draw方法:
public void draw() { System.out.print("Draw a normal window"); }
ScrollDecorator类重写继承自Window的draw方法:
Window component; // 被装饰的对象的引用,这里即为NormalWindow对象 public void draw() { // 即先执行了上一个代码段中的System.out.print("Draw a normal window"); component.draw(); // 这是额外增加的功能 System.out.print("add a scrollbar on this window"); }
装饰模式似乎很难用这么少的语言表达的很清晰。。代码倒是很好理解。
相关文章:
Design Patterns Uncovered: The Decorator Pattern : http://java.dzone.com/articles/design-patterns-decorator
关于window和coffee的例子:http://en.wikipedia.org/wiki/Decorator_pattern#Java
关于装饰模式的更多实际应用:http://www.javaworld.com/javaworld/jw-12-2001/jw-1214-designpatterns.html