三十六:装饰模式

装饰模式(Decorator)又名包装(Wrapper)模式,装饰模式以对客户端透明的方式扩展对象的功能,是继承的一个替代方案.

一:引言
孙悟空有七十二般变化,他的每一种变化都给他带来一种附加本领。他变成鱼时,就可以到水里游泳,他变成鸟时,就可以在天上飞行,而不管悟空怎么变化,在二郎神眼里,他永远是那只猢狲.
装饰模式以对客户透明的方式动态地给一人对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同,装饰模式可以在不使用创造更多子类的情况下,将对象的功能加以扩展。
装饰模式使用原来被装饰的类的一个子类的实例,把客户端的调用委派到被装饰类,装饰模式的关键在于这种扩展是完全透明的。老孙变成的鱼相当于老孙的子类,这条鱼与外界互动要通过"委派"交给老孙本尊,由老孙本尊采取行动,尽管老孙把自己"装饰"成了鱼,在二郎神眼里,他仍然是那只猢狲。

二:装饰模式的结构
在装饰模式中的角色有:
(A)抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加功能的对象
(B)具体构件(Concrete Component)角色:定义一个将要接收附加责任的类
(C)装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口
(D)具体装饰(Concrete Decorator)角色:负责给构件对象"贴上"附加功能
下面给出装饰模式的示意性源代码:
package cai.milenfan.basic.test; 
import java.util.Enumeration;
//抽象构件角色
public interface Component {
//某个商业方法
void sampleOperation();
}


package cai.milenfan.basic.test; 

/**
*这是一个装饰类,应该注意几点:
* (1)有一个私有的属性component,其数据类型是构件Component
* (2)接口的实现方法也值得注意,每一个实现的方法都委派给父类,但并不是单纯的委派,而是有功能的增强
*/
public class Decorator implements Component{
private Component cmponent;
public Decorator(){}
public Decorator(Component component){
this.cmponent = component;
}
public void sampleOperation(){
cmponent.sampleOperation();
}
}



package cai.milenfan.basic.test; 
//具体装饰角色
public class ConcreateComponent implements Component{
public ConcreateComponent(){
//write your code here
}
public void sampleOperation() {
//write your code here
}
}


package cai.milenfan.basic.test; 
//具体装饰类
public class ConcreteDecorator extends Decorator{
//商业方法
public void sampleOperation(){
super.sampleOperation();
}
}

装饰模式的对象图如下:
new Decorator1(
new Decorator2(
new Decorator3(
new ConcreteComponent();
)
)
);


这就意味着Decorator1的对象持有一个对Decorator2对象的引用,后者则持有一个对Decorator3的引用,再后者持有一个对具体构件ConcreteComponent对象的引用,这种链式的引用关系使装饰模式看上去像是一个LinkedList.
回到齐天大圣的例子:Component的角色由孙悟空扮演,ConcreteComponent的角色属于大圣本尊,就是猢狲本人,大圣的七十二变扮演的便是Decorator角色,而ConcreteDecorator的角色便是花,鸟,鱼等七十二般变化。

四:在什么情况下使用装饰模式
(1)需要扩展一个类的功能,或给一个类增加附加责任
(2)需要动态地给一个对象增加功能,这些功能可以再动态地撤消
(3)需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实
再回到齐天大圣的例子:
首先有一个大圣本尊的实例:
齐天大圣c = new大圣本尊();
然后,你把大圣"装饰"成一个雀儿:
齐天大圣bird = new雀儿(c);
由于"大圣本尊"是ConcreteComponent类,因此可以调用默认的构造子创建实例,而"雀儿"是装饰类,要装饰的是"大圣本尊",也即一个"猢狲"实例,换言之,上面的java语句把"猢狲"变成了"雀儿"(把雀儿的功能加到了"猢狲"身上)。由于多态性原则,"雀儿"构造子可以接受任何"齐天大圣"的子类的实例,这也就意味着可以把任何大圣的化身装饰成"雀儿",比如:
齐天大圣c = new大圣本尊();
齐天大圣fish = new鱼儿(c);
齐天大圣bird = new雀儿(fish);

五:使用装饰模式的优点和缺点
(1)装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性,它允许系统动态地决定"贴上"一个需要的"装饰",或者除掉一个不需要的"装饰",继承关系则不同,继承关系是静态的,它在系统运行就决定了。
。 。 。 。

六:模式实现的讨论
大多数情况下,装饰模式的实现都比本节的定义中给出的示意性实现要简单,对模式进行简化时需要注意以下情况:
(1)一个装饰类的接口必须与被装饰类的接口相容:ConcreteDecorator类必须继承自一个共同的父类Component
(2)尽量保持Component作为一个"轻"类:这个类的责任是为各个ConcreteDecorator类提供共同的接口,因此它应当重在提供接口而不是存储数据。在示例中Component是一个java接口,而在实际工作中,它可以是一个抽象类或者是一个具体类,此时,就应当注意不要把太多的逻辑和状态放在Component类里。
(3)如果只有一个ConcreteComponent类而没有抽象的Component类(接口),那么Decorator类经常可以是ConcreteComponent的一个子类,这样的话ConcreteComponent就要扮演双重角色。
(4)如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类,甚至只有两个ConcreteDecorator类的情况下都可以这样做,但是如果ConcreteDecorator类的数目大于三的话,使用一个单独的Decorator类来区分抽象和具体的责任就是必要的了。

透明性要求:
最后指出一个重要问题,通常叫做针对抽象编程,装饰模式对客户端的透明性要求程序不要声明一个ConcreteDecorator类型的变量,而应当声明一个Component类型的变量。用孙悟空的例子来说,必须永远把孙悟空的所有变化都当成孙悟空来对待,而如果把他变成的雀儿当成雀儿,那就被老孙骗了!换言之,下面的做法是对的:
齐天大圣c = new大圣本尊();
而下面的做法是不对的:
大圣本尊c = new大圣本尊();,这就意味着在ConcreteDecorator里不可以有Component里所没有的方法,为什么呢?如果在ConcreteDecorator里面有一个新的方法newMethod(),那么,客户端怎么调用这个newMethod()呢?记住客户端只有Component接口,而这个接口里并没有newMethod()方法。

半透明的装饰模式
纯粹的装饰模式很难找到,装饰模式的用意是在不改变接口的前提下,增强所考虑的类的性能,在增强性能的时候,往往要建立新的公开的方法,即便是在孙大圣的系统里,也需要新的方法。比如齐天大圣类并没有飞行能力,而雀儿有,这就意味着雀儿应当有一个fly()方法,再比如,齐天大圣并没有你的游泳能力,而鱼儿有,这就意味着在鱼儿类里应当有一个新的swim()方法。这就导致了大多数的装饰模式的实现都是"半透明"的,换言之,允许装饰模式改变接口,增加新的方法,这意味着客户端可以声明ConcreteDecorator类型的变量,从而可以调用ConcreteDeconrator类中才有的方法:
齐天大圣c = new大圣本尊();
雀儿bird = new雀儿(c);
bird.fly();
但是,只要客户端不需要调用这些属于装饰的方法,而只调用属于Component的方法,那么装饰模式就仍然等同于透明的

七:装饰模式与其他模式的关系
(1)装饰模式与适配器模式的区别和联系
它们都有一个别名,即包装(Wrapper)模式,但是这两个模式是很不一样的。适配器模式的用意是要改变所考虑的对象的接口而不一定改变对象的性能,而装饰模式的用意是要保持接口,从而增强所考虑对象的性能
(2)装饰模式将一个东西的表皮换掉,而保持它的内心,策略模式恰好相反,它在保持接口不变的情况下,使得算法可以互换,装饰模式的实现要求Component类尽量地"轻",而策略模式要求抽象策略类尽量"重"。
(3)装饰模式与合成模式的关系
装饰模式常常用在合成模式的行为扩展上,使用继承关系扩展合成模式的行为很困难,如果仅仅对抽象构件(Component)类还是合成类(Composite)类或者树叶类(Leaf)类使用继承,会导致多态性被破坏

八:一个例子GrepReader---半透明的装饰模式的应用
功能描述:Grep是Unix操作系统中的命令,可以用来处理对流的搜索,由于Grep给出的结果仍然是流,因此可以作为过滤器在任何一条管道线中使用,比如命令Grep BMW file在文件file中搜索所有含有BMW字样的行,并显示出来.本例就用java的i/o包来实现一个简单的搜索器,叫做Grep,进行类似的搜索.
(1)宏观设计
首先把程序进行功能的划分,也即宏观设计。这里采用MVC模式:
(A)模型(Model):即GrepReader类,是一个对输入的文件流进行处理的构件,真正的搜索就发生在这里,这是一个处理char流的构件.
(B)视图(View):即GrepView类,是一个简单的数据输出工具,它只是把数据打印到屏幕上
(C)控制器(Control):即Grep类,是系统的控制中心.
(2)微观设计
即考虑每个类的大概的实现是怎么样的
(3)源码
package cai.milenfan.basic.test; 

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FilterReader;
import java.io.IOException;
import java.io.Reader;

public class GrepReader extends FilterReader{
protected String substring;
protected BufferedReader in;
private int lineNumber;
protected GrepReader(FileReader in,String substring) {
super(in);
this.in = new BufferedReader(in);
this.substring = substring;
lineNumber =0;
}
public final String readLine()throws IOException{
String line;
do{
line = in.readLine();
lineNumber ;
}while(line!=null&&line.indexOf(substring)==-1);
return line;
}
public int getLineNo(){
return lineNumber;
}
}



package cai.milenfan.basic.test; 

import java.io.PrintStream;

public class GrepView {
PrintStream out;
public GrepView(){
out = System.out;
}
public void println(String line){
out.println(line);
}
}

package cai.milenfan.basic.test;
import java.io.*;
public class Grep {
static GrepReader g;
private static GrepView gv = new GrepView();
public static void main(String[] args){
String line;
if(args.length<=1){
gv.println("Usage:java Grep ");
gv.println(" no regexp");
gv.println(" files to be searched in");
System.exit(1);
}
gv.println("\nGrep:搜索" args[0] "文件" args[1]);
gv.println("文件号和行号\r\t下面的行里含有所搜索的字符串\n");

try {
g = new GrepReader(new FileReader(args[0]),args[1]);
for(;;){
line = g.readLine();
if(line==null){
break;
}
gv.println(args[1] ":" g.getLineNo() ";\t" line);
}
g.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}


//运行: java Grep C:\\AJAX.txt Ajax
//如果某行以Ajax开头,程序就会打印出这一行的文字.
//在eclipse里为main方法传参数:run-->run configulation->main选项里选择要运行的类,argument里的program argument里输入参数后apply->run

九:一个发票系统(跳过)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值