【HeadFirst 设计模式学习笔记】3 装饰模式
作者:gnuhpc
出处:http://www.cnblogs.com/gnuhpc/
1.这个模式可以称为“给爱用继承的人一个全新的设计眼界”的模式。牵扯到第五个设计原则:“类应该对扩展开放,而对修改封闭”。但是要注意,遵循这一标准会带来更多层次上的抽象,增加代码的复杂度,所以并不是所有类都要这样设计。
2.文中举了一个为辛巴克咖啡馆写一个计算咖啡价格+调料价格的类,使用了装饰模式——动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更加有弹性的替代方案。我们就拿这个计算咖啡价格的东西举例子。
3.在原来的设计中,都是继承于Beverage这个超类中,多一项咖啡+调料组合就多一个子类,最后造成类的爆炸。而使用装饰模式,我们希望用装饰器(这里的调料)一层层的包含被装饰的咖啡,最后达到通过调用最外层的装饰者的cost()方法就可以委托其内部计算计算价钱。我们针对这个目标,从代表饮品的Beverage类下手,这是一个基类,代表一个逻辑上的抽象(但不一定要是抽象类,看是否有抽象方法来定),被装饰者和装饰者都使用该基类,在这个基类中定义了装饰者和被装饰者需要的方法,其中抽象方法是装饰者和被装饰者同时都需要的方法,而普通实现的方法则是装饰者覆盖使用的方法:
public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
我们将调料视为装饰器,这个装饰器超类为了能够将要被装饰的部分包起来,所以要继承自饮品这个超类:
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
现在我们就构造一些饮品(被装饰者)
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";//这个变量是继承而来
}
public double cost() {
return 1.99;
}
}
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 beverage3 = new HouseBlend();
beverage3 = new Soy(beverage3);//一直针对抽象组建类型编程
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
System.out.println(beverage3.getDescription()
+ " $" + beverage3.cost());
}
}
4.总结一下装饰模式特点:
- 装饰者和被装饰对象有相同的基类--都是来自Beverage这个类。
- 继承关系:基类->被装饰者(就是具体的一些基类延伸),基类->装饰器->装饰者(传入基类)-对应上面的代码。
- 你可以用一个或多个装饰者包装一个对象--看看beverage3这个对象就知道了。
- 在任何需要被包装者的场合可以用装饰过的对象代替它--比如首先我们在咖啡上加豆浆,然后我们在加豆浆的咖啡上想再加摩卡的话,我们可以直接在这个加过豆浆的咖啡对象上加摩卡。
- 装饰者可以在所委托的被装饰者的行为上加上自己的行为,达到特定目的--getDescription和cost方法充分证明了这一点。
5.JDK中的装饰模式
最典型的就是IO系统了,比如BufferedInputStream及LineNumberInputStream都扩展自FilterInputStream——这个类是一个抽象的装饰类。而最高的抽象组件是InputStream类。
我们也可以以假乱真写一个输入流类
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in) {
super(in);
}
public int read() throws IOException {
int c = super.read();
return (c == -1 ? c : Character.toLowerCase((char)c));
}
public int read(byte[] b, int offset, int len) throws IOException {
int result = super.read(b, offset, len);
for (int i = offset; i < offset+result; i++) {
b[i] = (byte)Character.toLowerCase((char)b[i]);
}
return result;
}
}
public class InputTest {
public static void main(String[] args) throws IOException {
int c;
try {
InputStream in =
new LowerCaseInputStream(
new BufferedInputStream(
new FileInputStream("test.txt")));
while((c = in.read()) >= 0) {
System.out.print((char)c);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
6.装饰模式的一些缺陷:
产生各种小类,维护不便。有些代码会依赖特定的类型,而这样的代码一导入装饰者就出问题了。
总结:
装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案
FAQ:
为什么在一定要有Decorator这个类?
基于上边的这个例子我们可以看到Decorator这个类隔离了Component中可能出现的具体实现(比如上例中的getDescription方法),之所以要隔离室因为装饰者对这个方法的实现逻辑和高层的Component类实现的逻辑是不同的。在Decorator将一个方法退化为一个抽象方法,有助于督促具体的装饰器必须实现这个方法,而不会无意使用Component继承而来的方法(如果没有Decorator而直接继承自Component可能会因为实现逻辑不同而出现没有实现新逻辑而误用旧逻辑的情况,这会导致出现运行中错误,而退化为抽象方法后,具体装饰器则不得不实现这个方法,否则不可能编译通过,这就将错误压制在了编译阶段,显然,这相对于运行中出错是要好得多)。