论坛首页 Java版 设计模式

设计模式感悟(三)--- 装饰模式(Decorator Pattern)

浏览 4346 次
精华帖 (0) :: 良好帖 (8) :: 新手帖 (1) :: 隐藏帖 (0)
作者 正文
时间:2008-07-27 关键字: 设计模式, 装饰模式

装饰模式(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;

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;

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;

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");
	}

}

 

 

        如果你的发票格式为:

 

        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 BufferedReader(new FileReader("test.txt"));

 

        如果你还不满足,你可以继续,输入流中有处理行号的装饰类:

new LineNumberReader(new BufferedReader(new FileReader("test.txt")));

 

       其实流的构建非常的容易,我想可能是流的面包和装饰在命名上没有一个明显的区分,种类有多,让人眼花缭乱,最终使人望而生畏。这个也是本人时时困惑的地方,每每这样,我就看看这些类的父类就知道该怎么做了,看是否是继承自FilterInputStream ,因为明白装饰模式的原理。

 

       结尾时,我们送一款糯米面包心的蛋糕^_^:

new LineNumberReader(new BufferedReader(new InputStreamReader(socket.getInputStream())));

        

       到这里我们知道,装饰模式是为现有功能添加附加功能的一种方法。他是一种动态的,自由的组合对象行为的方式。

  • decorator.rar (2.2 KB)
  • 描述: 装饰模式的发票实例的源码
  • 下载次数: 123
   
时间:2008-07-28
记得以前看Think in Java中译本时,说java的io类使用了装修的设计图案,想了半天才明白是decorator design pattern
   
0 请登录后投票
时间:2008-07-28
翻译工作者有时候对计算机语言不甚熟悉
   
0 请登录后投票
时间:2008-07-28
其实也就是递归在应用中的一种表现形式
   
0 请登录后投票
时间:2008-07-28
楼主的例子举的不错,比设计模式那本书上的例子好
   
0 请登录后投票
时间:2008-07-29
例子是一个假设的场景,他不仅仅从编码实现方式上告诉你去怎么实现,更重要的是告诉你什么情况下,你可以这么做。
这个发票的例子网上很多地方都有贴图,只不过很多人跟我一样,没有看见实现,总觉得是在说空话而已^_^
   
0 请登录后投票
时间:2008-07-29
“结尾时,我们送一款糯米面包心的蛋糕^_^:”
装饰者模式的一个重要要素
“糯米”、“面包”、“蛋糕”都有一个共同的超类型——“粮食”
随时都可以通用——“吃”
不管包了几层
   
0 请登录后投票
时间:2008-07-29
总的来说这个模式不是很实用,一般来说,给类拓展方法在设计前期是很难决定的,有可能你设计了一个类,然后用着用着“哎呦,少个方法”,此时类没有实现任何接口,难道还要从新定义接口,让类实现接口而扩展其他的接口实现么?

装饰模式属于前期设计模式,很难用于面向代码的过程维护,也就是像java io一样,要前期设计好那些类,那些职责,哪些嵌套关系等等,用于方法的职责分离类划分,适用范围小。
   
0 请登录后投票
时间:2008-07-29
bloodrate 写道
总的来说这个模式不是很实用,一般来说,给类拓展方法在设计前期是很难决定的,有可能你设计了一个类,然后用着用着“哎呦,少个方法”,此时类没有实现任何接口,难道还要从新定义接口,让类实现接口而扩展其他的接口实现么?

装饰模式属于前期设计模式,很难用于面向代码的过程维护,也就是像java io一样,要前期设计好那些类,那些职责,哪些嵌套关系等等,用于方法的职责分离类划分,适用范围小。



这就要求你更加认真仔细的确定你的接口中所应提供的方法。

定义规则比实现他们更加重要吧。

而decorator模式本身,也是可以增加方法的。。。它本身就是用来增加方法的。比如io中的BufferedWriter().println()等等。

如果觉得麻烦,可以考虑使用abstractClass来实现一些公共接口的方法。这样就可以减少重复代码了吧。
   
0 请登录后投票
时间:2008-07-30
设计模式存在的目的就是给你一种针对特定场景下解决特定问题的参考。本身软件工程和设计模式是借鉴了建筑工程的思想演变而来。相对与设计模式,他好比是一本建筑设计图纸,其中告诉你在什么地方做什么门合适,怎么做门;怎么去架桥,做拱桥还是做什么桥,该如何构建;做抽屉是用斜面还是用燕尾面……

设计模式运用的法门就是“场景”和“方案”。

在设计前期确实很难确定应该有哪些类和哪些职责,是什么原因呢?我自己也是如此,常常感慨,这么好的武器都陈列在兵器柜,无用武之地,多可惜啊。不知道大家看过天龙八部没有,虚竹当了灵柩宫掌门带着几个丫鬟进了只有掌门才可以进去修炼的练功房,结果如何,他自己越练越感觉精进,而几个丫鬟练几下就走火入魔了^_^,自己功底不够啊。在设计前期或许是经验不够等因素你没有想到你所处的“场景”适合哪中设计模式,那就更不用提方案了。

我想唯一解决的办法就是多学习设计模式,多实践,熟能生巧。我在这写这些设计模式的文字一来是给自己留个笔记,二来是想跟有兴趣的同志一起讨论共同进步。量变引起质变,终有一天大家可以去灵柩宫学逍遥派的天山折梅手。
   
1 请登录后投票
论坛首页 Java版 设计模式

跳转论坛:
JavaEye推荐