标签:
设计模式系列都是学习HeadFirst设计模式得出的学习心得,中间的例子也会采用书中的例子。这里有必要解释一下,在下面星巴克咖啡的例子中,有几种基本的咖啡,还有牛奶、豆浆等等可以向咖啡中添加,这里说明防止下面不懂。
今天我们来了解一下装饰者模式。
回想一下java的io包,各种stream排上倒海,初学者根本分不清楚到底怎么用,眼花缭乱。其实,它的实验遵循了装饰者设计模式。顾名思义,装饰者就可以简单的理解成用一个东西来装饰另一个东西。比如,你要做鱼吃,在你做好出国之后需要加入香菜。我们就可以简单的看成雨是一个东西,我们在用香菜装饰它。表面上来看,装饰的作用就是使更加好吃(我们的类更加好用)。其实更重要的是可以随意的扩展你的程序,还是鱼的例子,可能你家有不吃香菜的人,那么你就不能加入香菜,可能是加入葱花等等,或者两者都加入,这样我们就做成了香菜鱼、葱花鱼(根据不同的材料形成不同的产品)。这样,软件的灵活性大大提高了。
先看一下java.io包里面的类:
现在想想java.io包里的类,是不是经常遇到这样的情况:把一个流作为另一个流构造的参数,相当于用一个类把另一个类包装起来了。比如
new BufferedInputStream(new InputStream())
这是得到的对象就有了更加强大的功能,因为增加了一个缓冲区,可以一行一行的读入数据。这就是用java中对于装饰者模式的典型应用。
我们还是从一个简单的小例子开始。星巴克咖啡可以提供多种咖啡,如:HouseBlend、DarkRoast、Decaf、Espresso。在这些咖啡里还可以添加:牛奶、豆浆、摩卡等等。想象我们怎样设计这样一个系统(当然要算出最后的每一杯的价钱)?他们最开始设计的框架是这样的:
难道是把每一个咖啡都设计成一个类?想象一下随机组合的情况,那组合的情况实在太多了,明显不是一个好的方法。还有一个方法,就是在材料类(接口)中加入所有材料的Blooean字段,以true代表有这个材料,false代表没有这个材料(牛奶等)。这样类的数目就变少了是需要初始化的时候初始化那些字段就可以了。但是,这样同时也会带来一些问题,比如茶不要牛奶等等的添加成分,但是茶的具体实例中却有这些成员,还有就是如果新增加添加材料呢,所有的类都要添加这个字段的初始化。到这里我们都是假定的所有的东西价格不变。如果价格改变了呢?所以这里问题还是很多的,这不是一个好的解决办法。
我们现在有什么好的办法呢?想象一下我们曾经有一个设计模式的规则,多用组合少用继承。在结合java.io包装的形式,我们是不是可以把基本的咖啡以及各种添加的材料看成不同的类,然后我们用组合。但是又不是我们一般的直接组合,设想一下如果按照一般组合,是不是跟第一方法一样,会造成类爆炸,材料多了以后需要大量的组合?那么现在我们就直接说装饰者模式了。如果顾客想要DarkRoast,想加入Mocha,还想要Whip,是不是这样的过程:
需要添加什么就用什么包装已存在的组件(我们的背包装的对象成为组件)。我们的框架是这样的:
这里的实现代码为:
Beverage:
public abstract class Beverage { String description = "Unknown Beverage"; public String getDescription() { return description; } public abstract double cost(); }
Condiment:
public abstract class CondimentDecorator extends Beverage { public abstract String getDescription(); }
HouseLend:
public class HouseBlend extends Beverage { public HouseBlend() { description = "House Blend Coffee"; } public double cost() { return .89; } }
Mocha:
public class Mocha extends CondimentDecorator { Beverage beverage; public Mocha(Beverage beverage) { this.beverage = beverage; } public String getDescription() { return beverage.getDescription() + ", Mocha"; } public double cost() { return .20 + beverage.cost(); } }
测试代码:
public class StarbuzzCoffee { public static void main(String args[]) { Beverage beverage = new Espresso(); System.out.println(beverage.getDescription() + " $" + beverage.cost()); Beverage beverage2 = new DarkRoast(); beverage2 = new Mocha(beverage2); beverage2 = new Mocha(beverage2); beverage2 = new Whip(beverage2); System.out.println(beverage2.getDescription() + " $" + beverage2.cost()); Beverage beverage3 = new HouseBlend(); beverage3 = new Soy(beverage3); beverage3 = new Mocha(beverage3); beverage3 = new Whip(beverage3); System.out.println(beverage3.getDescription() + " $" + beverage3.cost()); } }
其余的类参考上面的,没有任何变化。
装饰者模式的重点在于:装饰者和被装饰者必须是一样的类型,也就是有共同的超类,这是相当关键的地方。在这里,我们利用继承达到“类型匹配”,而不是利用继承得到行为。有没有发现,这里是“运行时”的,而不是编译时静态决定。我们可以在运行的时候随意的组合成任意的类型的咖啡。
下面我们有一个自己的java I/O装饰者(只有两个类):
package com.worsun.javaIO装饰者; import java.io.*; public class InputTest { public static void main(String[] args) throws IOException { int c; try { InputStream in = new LowerCaseInputStream( new BufferedInputStream( new FileInputStream("resource/test1.txt"))); while((c = in.read()) >= 0) { System.out.print((char)c); } in.close(); } catch (IOException e) { e.printStackTrace(); } } }
package com.worsun.javaIO装饰者; import java.io.*; public class LowerCaseInputStream extends FilterInputStream { public LowerCaseInputStream(InputStream in) { super(in); } public int read() throws IOException { int c = in.read(); return (c == -1 ? c : Character.toLowerCase((char)c)); } public int read(byte[] b, int offset, int len) throws IOException { int result = in.read(b, offset, len); for (int i = offset; i < offset+result; i++) { b[i] = (byte)Character.toLowerCase((char)b[i]); } return result; } }
看看吧,这不就是java I/O的实现方式?我想现在再看java.io包应该会感到简单多了,相信不再为java.io头疼了。最后还有一个装饰者模式的问题:会造成大量的小类的存在,会对被人的理解有比较大的影响。其实还有一些其他问题,我们在以后的其他设计模式中再详述。
标签:
原文地址:http://www.cnblogs.com/worsun/p/5143546.html