装饰模式Decorator

 

 

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

1.什么时候使用

  1. 需要动态的扩展一个类,这些扩展也可以动态的撤销,并保持原有类的静态定义的情况。
  2. 需要增加由一些基本功能排列组合贰产生的非常强大的功能,并使继承关系变得不实现,典型的Wrapper应用。

 

模拟类图:

 

 

 

在装饰模式中的各个角色有:

  • 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
  • 具体构件(Concrete Component)角色:定义一个将要接收附加责任的类。
  • 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
  • 具体装饰(Concrete Decorator)角色:负责给构件对象"贴上"附加的责任。

 看headFirst中星巴兹咖啡的使用

 

 

写下星巴兹的代码

先从Beverage类下手,这不需要改变星巴兹原始的设计。如下

public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}

Beverage是一个抽象类,有两个方法:getDescription()及cost()。

getDescription()已经在此实现了,但是cost()必须在子类中实现。

 

Beverage很简单。让我们也来实现Condiment(调料)抽象类,也就是装饰者类吧:

public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}

 

1.首先,必须让Condiment Decorator能够取代Beverage,所以将CondimentDecorator扩展自 Beverage 类。

 

 

写饮料的代码

现在,已经有了基类,让我们开始开始实现一些饮料吧!先从浓缩咖啡(Espresso)开始。别忘了,我们需要为具体的饮料设置描述,而且还必须实现cost()方法。

//首先,让Espresso扩展自Beverage类,因为Espresso是一种饮料。
public class Espresso extends Beverage {
//为了要设置饮料的描述,我们写了一个构造器。记住,description实例变量继承自Beverage。
public Espresso() {
description = "Espresso";
}
//最后,需要计算Espresso的价钱,现在不需要管调料的价钱,直接把Espresso的价格$1.99返回即可。
public double cost() {
return 1.99;
}
}



public class HouseBlend extends Beverage {
public HouseBlend() {
description = "House Blend Coffee";
}
//这是另一种饮料,做法和Espresso一样,只是把Espresso名称改为"House Blend Coffee",并返回正确的价钱$0.89。
public double cost() {
return .89;
}
}

 

写调料代码

如果你回头去看看装饰者模式的类图,将发现我们已经完成了抽象组件(Beverage),有了具体组件(HouseBlend),也有了抽象装饰者(CondimentDecorator)。现在,我们就来实现具体装饰者。先从摩卡下手:

//摩卡是一个装饰者,所以让它扩展自CondimentDecorator。
public class Mocha extends CondimentDecorator {//别忘了,CondimentDecorator扩展自Beverage。
Beverage beverage;
public Mocha(Beverage beverage) {
//要让Mocha能够引用一个Beverage,做法如下:(1)用一个实例变量记录饮料,也就是被装饰者。
//(2)想办法让被装饰者(饮料)被记录到实例变量中。这里的做法是:把饮料当作构造器的参数,再由构造器将此饮料记录在实例变量中。
this.beverage = beverage;
}
public String getDescription() {
//我们希望叙述不只是描述饮料(例如“DarkRoast”),而是完整地连调料都描述出来(例如“DarkRoast, Mocha”)。
//所以首先利用委托的做法,得到一个叙述,然后在其后加上附加的叙述(例如“Mocha”)。
return beverage.getDescription() + ", Mocha";
}
public double cost() {
//要计算带Mocha饮料的价钱。首先把调用委托给被装饰对象,以计算价钱,然后再加上Mocha的价钱,得到最后结果。
return .20 + beverage.cost();
}
}

 

public class StarbuzzCoffee {
public static void main(String args[]) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription()+ " $" + beverage.cost());//订一杯Espresso,不需要调料,打印出它的描述与价钱
Beverage beverage2 = new DarkRoast();//制造出一个DarkRoast对象。
beverage2 = new Mocha(beverage2);//用Mocha装饰它。
beverage2 = new Mocha(beverage2);//用第二个Mocha装饰它。
beverage2 = new Whip(beverage2);//用Whip装饰它。
System.out.println(beverage2.getDescription()
+ " $" + beverage2.cost());
Beverage beverage3 = new HouseBlend();
//最后,再来一杯调料为豆浆、摩卡、奶泡的HouseBlend咖啡。
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
System.out.println(beverage3.getDescription()
+ " $" + beverage3.cost());
}
}

 

结果

 

 

 java IO包中的Decorator模式

JDK提供的java.io包中使用了Decorator模式来实现对各种输入输出流的封装。以下将以java.io.OutputStream及其子类为例,讨论一下Decorator模式在IO中的使用。

  首先来看一段用来创建IO流的代码:

 

try { 
 OutputStream out = new DataOutputStream(new FileOutputStream("test.txt")); 
} catch (FileNotFoundException e) { 
 e.printStackTrace(); 
}

 

 这段代码对于使用过JAVA输入输出流的人来说再熟悉不过了,我们使用DataOutputStream封装了一个FileOutputStream。这是一个典型的Decorator模式的使用,FileOutputStream相当于Component,DataOutputStream就是一个Decorator。将代码改成如下,将会更容易理解:

try { 
 OutputStream out = new FileOutputStream("test.txt"); 
 out = new DataOutputStream(out); 
} catch(FileNotFoundException e) { 
 e.printStatckTrace(); 
}

 由于FileOutputStream和DataOutputStream有公共的父类OutputStream,因此对对象的装饰对于用户来说几乎是透明的。下面就来看看OutputStream及其子类是如何构成Decorator模式的:

 

 OutputStream是一个抽象类,它是所有输出流的公共父类,其源代码如下:

 

public abstract class OutputStream implements Closeable, Flushable { 
 public abstract void write(int b) throws IOException; 
 ... 
}

 

 

它定义了write(int b)的抽象方法。这相当于Decorator模式中的Component类。
ByteArrayOutputStream,FileOutputStream 和 PipedOutputStream 三个类都直接从OutputStream继承,以ByteArrayOutputStream为例:

public class ByteArrayOutputStream extends OutputStream { 
 protected byte buf[]; 
 protected int count; 
 public ByteArrayOutputStream() { 
  this(32); 
 } 
 public ByteArrayOutputStream(int size) { 
  if (size 〈 0) { 
   throw new IllegalArgumentException("Negative initial size: " + size); 
  } 
  buf = new byte[size]; 
 } 
 public synchronized void write(int b) { 
  int newcount = count + 1; 
  if (newcount 〉 buf.length) { 
   byte newbuf[] = new byte[Math.max(buf.length 〈〈 1, newcount)]; 
   System.arraycopy(buf, 0, newbuf, 0, count); 
   buf = newbuf; 
  } 
  buf[count] = (byte)b; 
  count = newcount; 
 } 
 ... 
}

它实现了OutputStream中的write(int b)方法,因此我们可以用来创建输出流的对象,并完成特定格式的输出。它相当于Decorator模式中的ConcreteComponent类。

接着来看一下FilterOutputStream,代码如下:

 

public class FilterOutputStream extends OutputStream { 
 protected OutputStream out; 
 public FilterOutputStream(OutputStream out) { 
  this.out = out; 
 } 
 public void write(int b) throws IOException { 
  out.write(b); 
 } 
 ... 
}

 

 

 

同样,它也是从OutputStream继承。但是,它的构造函数很特别,需要传递一个OutputStream的引用给它,并且它将保存对此对象的引用。而如果没有具体的OutputStream对象存在,我们将无法创建FilterOutputStream。由于out既可以是指向FilterOutputStream类型的引用,也可以是指向ByteArrayOutputStream等具体输出流类的引用,因此使用多层嵌套的方式,我们可以为ByteArrayOutputStream添加多种装饰。这个FilterOutputStream类相当于Decorator模式中的Decorator类,它的write(int b)方法只是简单的调用了传入的流的write(int b)方法,而没有做更多的处理,因此它本质上没有对流进行装饰,所以继承它的子类必须覆盖此方法,以达到装饰的目的。

  BufferedOutputStream 和 DataOutputStream是FilterOutputStream的两个子类,它们相当于Decorator模式中的ConcreteDecorator,并对传入的输出流做了不同的装饰。以BufferedOutputStream类为例:

 

public class BufferedOutputStream extends FilterOutputStream { 
 ... 
 private void flushBuffer() throws IOException { 
  if (count 〉 0) { 
   out.write(buf, 0, count); 
   count = 0; 
  } 
 } 
 public synchronized void write(int b) throws IOException { 
  if (count 〉= buf.length) { 
   flushBuffer(); 
  } 
  buf[count++] = (byte)b; 
 } 
 ... 
}

 

 

并且覆盖了父类的write(int b)方法,在调用输出流写出数据前都会检查缓存是否已满,如果未满,则不写。这样就实现了对输出流对象动态的添加新功能的目的。

  下面,将使用Decorator模式,为IO写一个新的输出流。
  自己写一个新的输出流

  了解了OutputStream及其子类的结构原理后,我们可以写一个新的输出流,来添加新的功能。这部分中将给出一个新的输出流的例子,它将过滤待输出语句中的空格符号。比如需要输出"java io OutputStream",则过滤后的输出为"javaioOutputStream"。以下为SkipSpaceOutputStream类的代码:

 

 

import java.io.FilterOutputStream; 
import java.io.IOException; 
import java.io.OutputStream; 
/** 
* A new output stream, which will check the space character 
* and won’t write it to the output stream. 
* @author Magic 
* 
*/ 
public class SkipSpaceOutputStream extends FilterOutputStream { 
 public SkipSpaceOutputStream(OutputStream out) { 
  super(out); 
 } 
 /** 
 * Rewrite the method in the parent class, and 
 * skip the space character. 
 */ 
 public void write(int b) throws IOException{ 
  if(b!=’ ’){ 
   super.write(b); 
  } 
 } 
}

 

 

 

它从FilterOutputStream继承,并且重写了它的write(int b)方法。在write(int b)方法中首先对输入字符进行了检查,如果不是空格,则输出。

  以下是一个测试程序:

 

import java.io.BufferedInputStream; 
import java.io.DataInputStream; 
import java.io.DataOutputStream; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.OutputStream; 
/** 
* Test the SkipSpaceOutputStream. 
* @author Magic 
* 
*/ 
public class Test { 
 public static void main(String[] args){ 
  byte[] buffer = new byte[1024]; 

  /** 
  * Create input stream from the standard input. 
  */ 
  InputStream in = new BufferedInputStream(new DataInputStream(System.in)); 

  /** 
  * write to the standard output. 
  */ 
  OutputStream out = new SkipSpaceOutputStream(new DataOutputStream(System.out)); 

  try { 
   System.out.println("Please input your words: "); 
   int n = in.read(buffer,0,buffer.length); 
   for(int i=0;i〈n;i++){ 
    out.write(buffer[i]); 
   } 
  } catch (IOException e) { 
   e.printStackTrace(); 
  } 
 } 
}

 

 执行以上测试程序,将要求用户在console窗口中输入信息,程序将过滤掉信息中的空格,并将最后的结果输出到console窗口。比如:

 

Please input your words: 
a b c d e f 
abcdef

 

 

 

 

总 结

  在java.io包中,不仅OutputStream用到了Decorator设计模式,InputStream,Reader,Writer等都用到了此模式。而作为一个灵活的,可扩展的类库,JDK中使用了大量的设计模式,比如在Swing包中的MVC模式,RMI中的Proxy模式等等。对于JDK中模式的研究不仅能加深对于模式的理解,而且还有利于更透彻的了解类库的结构和组成。

 

 

 Activity组件通过其父类ContextThemeWrapper和ContextWrapper的成员变量mBase来引用了一个ContextImpl对象,这样,Activity组件以后就可以通过这个ContextImpl对象来执行一些具体的操作,例如,启动Service组件、注册广播接收者和启动Content Provider组件等操作。同时,ContextImpl类又通过自己的成员变量mOuterContext来引用了与它关联的一个Activity组件,这样,ContextImpl类也可以将一些操作转发给Activity组件来处理。

 

 

 

 

 

 

posted on 2013-12-09 18:00  mingfeng002  阅读(235)  评论(0编辑  收藏  举报