装饰模式(Decorator Pattern) 是什么
按照四人团的说法,装饰模式(Decorator Pattern) 的意图是:
动态地给一个对象添加一些额外的责任。提供一种比用生成子类来构建此行为或责任的更为灵活的方式。
在我看来,装饰模式的重点在于依赖继承和多态特性来实现核心任务和额外工作的统一处理,即被装饰的内容和装饰“一视同仁”。
下面让我们来通过一个实例场景来看看装饰模式到底是怎么一回事。假设你需要打印发票 sales ticket , 发票有抬头、正文和脚注,发票抬头可以是企事业单位,发票号等等,脚注也是一样,可能有很多不同种类的脚注需要打印。如果发票格式固定那也就没必要继续讨 论了,现在的问题是,不同的客户需要的发票或者收据的抬头或脚注,他们需要的条目是不一样的,有的需要著明单位,有的只需要发票号,但是脚注需要开票人, 等等,对你来说跟现在的 Web 系统一样,客户的要求是动态;不过发票的正文是不会变化的,是固定。要满足这个需求我们有很多种方案,比如你可以抽象一系统对象层次来分层完成这些对象责 任。不过我们这里要推荐的是装饰模式,我们来具体看一下,装饰模式是如何工作的:
先看看该场景的 UML 图,
下面看看具体实现:
- package com.zhaipuhong.designpattern.decorator;
- public abstract class Component {
- public abstract void prtTicket();
- }
package com.zhaipuhong.designpattern.decorator;
public abstract class Component {
public abstract void prtTicket();
}
- package com.zhaipuhong.designpattern.decorator;
- import java.util.Date;
- public class SalesTicket extends Component {
- @Override
- public void prtTicket() {
- // print sales ticket here
- System.out.println("Sales Ticket Body" );
- }
- }
package com.zhaipuhong.designpattern.decorator;
import java.util.Date;
public class SalesTicket extends Component {
@Override
public void prtTicket() {
// print sales ticket here
System.out.println("Sales Ticket Body");
}
}
- package com.zhaipuhong.designpattern.decorator;
- public abstract class Decorator extends Component {
- private Component myComp;
- public Decorator(Component myComp) {
- this .myComp = myComp;
- }
- @Override
- public void prtTicket() {
- if (myComp != null )
- myComp.prtTicket();
- }
- }
package com.zhaipuhong.designpattern.decorator;
public abstract class Decorator extends Component {
private Component myComp;
public Decorator(Component myComp) {
this.myComp = myComp;
}
@Override
public void prtTicket() {
if(myComp != null)
myComp.prtTicket();
}
}
- package com.zhaipuhong.designpattern.decorator;
- import java.util.Date;
- public class Header1 extends Decorator {
- public Header1(Component myComp) {
- super (myComp);
- // TODO Auto-generated constructor stub
- }
- @Override
- public void prtTicket() {
- // place the code of print the sales head
- System.out.println("Sales Ticket Header1" );
- super .prtTicket();
- }
- }
package com.zhaipuhong.designpattern.decorator;
import java.util.Date;
public class Header1 extends Decorator {
public Header1(Component myComp) {
super(myComp);
// TODO Auto-generated constructor stub
}
@Override
public void prtTicket() {
// place the code of print the sales head
System.out.println("Sales Ticket Header1");
super.prtTicket();
}
}
- package com.zhaipuhong.designpattern.decorator;
- import java.util.Date;
- public class Footer1 extends Decorator {
- public Footer1(Component myComp) {
- super (myComp);
- // TODO Auto-generated constructor stub
- }
- @Override
- public void prtTicket() {
- super .prtTicket();
- // place the code of print sales foot
- System.out.println("Sales Ticket Footer1" );
- }
- }
package com.zhaipuhong.designpattern.decorator;
import java.util.Date;
public class Footer1 extends Decorator {
public Footer1(Component myComp) {
super(myComp);
// TODO Auto-generated constructor stub
}
@Override
public void prtTicket() {
super.prtTicket();
// place the code of print sales foot
System.out.println("Sales Ticket Footer1");
}
}
- package com.zhaipuhong.designpattern.decorator;
- public class Header2 extends Decorator {
- public Header2(Component myComp) {
- super (myComp);
- // TODO Auto-generated constructor stub
- }
- @Override
- public void prtTicket() {
- // place the code of print the sales head
- System.out.println("Sales Ticket Header2" );
- super .prtTicket();
- }
- }
package com.zhaipuhong.designpattern.decorator;
public class Header2 extends Decorator {
public Header2(Component myComp) {
super(myComp);
// TODO Auto-generated constructor stub
}
@Override
public void prtTicket() {
// place the code of print the sales head
System.out.println("Sales Ticket Header2");
super.prtTicket();
}
}
- package com.zhaipuhong.designpattern.decorator;
- public class Footer2 extends Decorator {
- public Footer2(Component myComp) {
- super (myComp);
- // TODO Auto-generated constructor stub
- }
- @Override
- public void prtTicket() {
- // TODO Auto-generated method stub
- super .prtTicket();
- //place the code of print sales foot
- System.out.println("Sales Ticket Footer2" );
- }
- }
package com.zhaipuhong.designpattern.decorator;
public class Footer2 extends Decorator {
public Footer2(Component myComp) {
super(myComp);
// TODO Auto-generated constructor stub
}
@Override
public void prtTicket() {
// TODO Auto-generated method stub
super.prtTicket();
//place the code of print sales foot
System.out.println("Sales Ticket Footer2");
}
}
如果你的发票格式为:
Sales Ticket Header1
Sales Ticket Body
Sales Ticket Footer1
那么你可以这样去创建对象:
new Header1(new Footer1(new SalesTicket()));
如果你的发票格式为:
Sales Ticket Header1
Sales Ticket Header2
Sales Ticket Body
Sales Ticket Footer1
那么你可以这样去创建对象:
new Header1(new Header2(new Footer1(new SalesTicket())));
看见了吗,你可以自由组合对象的行为,确实非常的神奇。
装饰模式为对象添加额外责任的方式就像做蛋糕一样,一圈一圈的加上去,中间的面包是核心,是被装饰的对象(发票正文),是核心任务,外围的都是装饰对象, 这就是说装饰模式包含两部分内容,即装饰对象和被装饰对象。特别有趣而又要注意的是,装饰对象不仅可以装饰被装饰对象,而且可以装饰装饰对象,是不是有点 绕口,但仔细体会一下,这就是装饰模式的巧妙之处。该特性是在 Decorator 抽象类中体现的,装饰类 Decorator 是用来修饰 Component 对象的,而Component 既是 Decorator 的父类又是 Decorator 的成员变量,所以Decorator 可以装饰Component 的子类(SalesTicket),具体的装饰行为是Decorator 的实现子类来完成的,所以Header1、Footer1等装饰实现类可以用来 装饰Component 的子类(如,SalesTicket,它是蛋糕中间的面包),而Header1、Footer1等装饰实现类本身也是Component 的子类,所以……
装饰模式耍了这么绕,其实就用了两招,继承和多态。第一,所以组件都是Component 的子类,由Component 规划对象的行为 (如,public void prtTicket();),装饰类Decorator 与 被装饰类SalesTicket 都是Component 的子类,那么他们具有相同的行为;装饰类Decorator 也以Component 为成员 变量,那他就有机会操作Component 的所有子类,而Decorator 的子类都override了Decorator 定义的接口,确切的说是 Component 定义的接口,这样,各装饰类有了机会重写自己关心的装饰工作。
下面给出装饰模式的标准UML图
装饰模式其实就在我们的身边,在J2SE I/O 类库中,大量采用了装饰模式。比如流的使用让很多人比解其妙,当你熟悉了装饰模式,你在使用流就会感觉轻松多了。
我们来输入流(Inputstream)来说吧,输出流(OutputStream)跟它是平行设计,举一反三。Inputstream 就是Decorator 模式标准视图的 Component , 像AudioInputStream、ByteArrayInputStream、FileInputStream、PipedInputStream、 SequencenputStream、StringBufferInputStream等这些都是蛋糕中的面包,是被装饰的对象,这点与我们的例子有点 不同。我们的例子里的蛋糕中面包是小麦面粉做的,流的装饰模式还有大米粉、玉米粉、高粱粉等做的面包,很丰富,哈哈---^_^. FilterInputStream 就是 Decorator 模式标准视图的 Decorator 了,FilterInputStream 的子类诸如BufferedInputStream, LineNumberInputStream ……就是具体的装饰类了。其它的这里就不赘述了。
举个例子,要使用输入流,你首先得挑一款面包,是小麦面粉的,还是大米粉的。比如我们经常从文件中创建流:
- new FileReader( "test.txt" );
new FileReader("test.txt");
这里蛋糕核心已经挑好了。为了提高性能,我们考虑对文件流做缓冲处理,这是可选的动作,是为文件流额外锦上添花的:
- new BufferedReader( new FileReader( "test.txt" ));
new BufferedReader(new FileReader("test.txt"));
如果你还不满足,你可以继续,输入流中有处理行号的装饰类:
- new LineNumberReader( new BufferedReader( new FileReader( "test.txt" )));
new LineNumberReader(new BufferedReader(new FileReader("test.txt")));
其实流的构建非常的容易,我想可能是流的面包和装饰在命名上没有一个明显的区分,种类有多,让人眼花缭乱,最终使人望而生畏。这个也是本人时时困惑的地 方,每每这样,我就看看这些类的父类就知道该怎么做了,看是否是继承自FilterInputStream ,因为明白装饰模式的原理。
结尾时,我们送一款糯米面包心的蛋糕^_^:
- new LineNumberReader( new BufferedReader( new InputStreamReader(socket.getInputStream())));
new LineNumberReader(new BufferedReader(new InputStreamReader(socket.getInputStream())));
到这里我们知道,装饰模式是为现有功能添加附加功能的一种方法。他是一种动态的,自由的组合对象行为的方式。