装饰(Decorator)模式又名包装(Wrapper)模式。Decorator以对客户端透明的方式扩展对象的功能,是继承的一种代替方案。
1.什么时候使用
- 需要动态的扩展一个类,这些扩展也可以动态的撤销,并保持原有类的静态定义的情况。
- 需要增加由一些基本功能排列组合贰产生的非常强大的功能,并使继承关系变得不实现,典型的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组件来处理。