扩展系统功能——装饰模式
装饰模式概述
装饰模式可以在不改变一个对象本身功能的基础上给对象增加额外的新行为,在现实生活中,这种情况也到处存在,例如一张照片,我们可以不改变照片本身,给它增加一个相框,使得它具有防潮的功能,而且用户可以根据需要给它增加不同类型的相框,甚至可以在一个小相框的外面再套一个大相框。
装饰模式结构如图12-3所示:
图12-3 装饰模式结构图
在装饰模式结构图中包含如下几个角色:
● Component(抽象构件):它是具体构件和抽象装饰类的共同父类,实现客户端的透明操作。
● ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。
● Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。
● ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。
装饰模式的核心在于抽象装饰类的设计,其典型代码如下所示:
class Decorator implements Component { private Component component; //维持一个对抽象构件对象的引用 public Decorator(Component component) //注入一个抽象构件类型的对象 { this.component=component; } public void operation() { component.operation(); //调用原有业务方法 } }
在抽象装饰类Decorator中定义了一个Component类型的对象component,维持一个对抽象构件对象的引用,并可以通过构造方法或Setter方法将一个Component类型的对象注入进来,同时由于Decorator类实现了抽象构件Component接口,因此需要实现在其中声明的业务方法operation(),需要注意的是在Decorator中并未真正实现operation()方法,而只是调用原有component对象的operation()方法,它没有真正实施装饰,而是提供一个统一的接口,将具体装饰过程交给子类完成。
典型的具体装饰类代码如下:
完整解决方案:
在图12-4中,Component充当抽象构件类,其子类Window、TextBox、ListBox充当具体构件类,Component类的另一个子类ComponentDecorator充当抽象装饰类,ComponentDecorator的子类ScrollBarDecorator和BlackBorderDecorator充当具体装饰类。完整代码如下所示:
抽象构件类:
abstract class Component { public abstract void display(); }
窗体类:具体构件类
class Window extends Component { public void display() { System.out.println("显示窗体!"); } }
抽象装饰类:
class ComponentDecorator extends Component { private Component component; //维持对抽象构件类型对象的引用 public ComponentDecorator(Component component) //注入抽象构件类型的对象 { this.component = component; } public void display() { component.display();//调用原有component对象的operation()方法 } }
具体装饰类:
class ScrollBarDecorator extends ComponentDecorator { public ScrollBarDecorator(Component component) { super(component); } public void display() { this.setScrollBar(); super.display(); } public void setScrollBar() { System.out.println("为构件增加滚动条!"); } }
编写如下客户端测试代码:
class Client { public static void main(String args[]) { Component component,componentSB; //使用抽象构件定义 component = new Window(); //定义具体构件 componentSB = new ScrollBarDecorator(component); //定义装饰后的构件 componentSB.display(); } }
输出结果:
为构件增加滚动条!
显示窗体!
二次装饰:
class Client { public static void main(String args[]) { Component component,componentSB,componentBB; //全部使用抽象构件定义 component = new Window(); componentSB = new ScrollBarDecorator(component); componentBB = new BlackBorderDecorator(componentSB); //将装饰了一次之后的对象继续注入到另一个装饰类中,进行第二次装饰 componentBB.display(); } }
我们可以将装饰了一次之后的componentSB对象注入另一个装饰类BlackBorderDecorator中实现第二次装饰,得到一个经过两次装饰的对象componentBB,再调用componentBB的display()方法即可得到一个既有滚动条又有黑色边框的窗体。
12.4 透明装饰模式与半透明装饰模式
软件公司开发的Sunny OA系统中,采购单(PurchaseRequest)和请假条(LeaveRequest)等文件(Document)对象都具有显示功能,现在要为其增加审批、删除等功能,使用装饰模式进行设计。
我们使用装饰模式可以得到如图12-5所示结构图:
图12-5文件对象功能增加实例结构图
在图12-5中,Document充当抽象构件类,PurchaseRequest和LeaveRequest充当具体构件类,Decorator充当抽象装饰类,Approver和Deleter充当具体装饰类。其中Decorator类和Approver类的示例代码如下所示:
Decorator:
//抽象装饰类 class Decorator implements Document { private Document document; public Decorator(Document document) { this. document = document; } public void display() { document.display(); } }
Approver:
//具体装饰类 class Approver extends Decorator { public Approver(Document document) { super(document); System.out.println("增加审批功能!"); } public void approve() { System.out.println("审批文件!"); } }
Approver类继承了抽象装饰类Decorator的display()方法,同时新增了业务方法approve(),但这两个方法是独立的,没有任何调用关系。如果客户端需要分别调用这两个方法,代码片段如下所示:
Document doc; //使用抽象构件类型定义 doc = new PurchaseRequest(); Approver newDoc; //使用具体装饰类型定义 newDoc = new Approver(doc); newDoc.display();//调用原有业务方法 newDoc.approve();//调用新增业务方法
如果newDoc也使用Document类型来定义,将导致客户端无法调用新增业务方法approve(),因为在抽象构件类Document中没有对approve()方法的声明。也就是说,在客户端无法统一对待装饰之前的具体构件对象和装饰之后的构件对象。
(1)透明装饰模式
在透明装饰模式中,要求客户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该将对象声明为具体构件类型或具体装饰类型,而应该全部声明为抽象构件类型。也就是应该使用如下代码:
Component c, c1; //使用抽象构件类型定义对象 c = new ConcreteComponent(); c1 = new ConcreteDecorator (c);
在实现透明装饰模式时,要求具体装饰类的operation()方法覆盖抽象装饰类的operation()方法,除了调用原有对象的operation()外还需要调用新增的addedBehavior()方法来增加新行为,
(2)半透明装饰模式
在具体装饰类中可以调用到抽象装饰类的operation()方法,同时可以定义新的业务方法,如addedBehavior()。
思考:
能否在装饰模式中找出两个独立变化的维度?试比较装饰模式和桥接模式的相同之处和不同之处?
个人答:
装饰模式中装饰器的功能是对具体容器类中原功能的扩展,是原功能维度上的延伸,并不是像桥接模式中两个独立的维度的组合。所以区别就很明显了。相同点是对于客户端来说,都是透明的。