初识设计模式 chapter 03-装饰者模式
1 引言
本章可以称为“给爱用继承的人一个全新的设计眼界”。
我们即将再度探讨典型的继承滥用问题,一旦你熟悉了装饰者模式的技巧,你将能够在不修改任何底层代码的情况下,给你的(或别人的)对象赋予新的职责。
2 正文
2.1本章需求背景
1、咖啡店,有四种原材料咖啡:综合、深焙、低咖啡因、浓缩,四种调料:牛奶、摩卡、豆浆、奶泡;
2、原材料和调料可以任意搭配形成新的饮料,且后期可以加入新的原材料和调料。
如果单纯的为每种咖啡(原材料+调料)建立一个类(接口),无疑是最笨拙的方法,且违背了设计原则:封装变化(材料价格),多用组合少用继承。
public class Beverage
{
//为每种调料设置静态变量
public void cost()
{
float condimentCost = 0.0;
if(hasMilk)
{
condimentCost += milkCost;
}
//以此类推其他三种调料的价格
}
}
再为每种原材料建立一个类,继承Beverage,覆盖cost()方法加上原材料的价格,得到最终饮料的价格。
以上设计的缺点:
1、一旦出现新的调料,将要修改Bererage代码;
2、以某种原材料为基础的饮料并不会包含所有调料,比如新增一个Tea;
3、万一顾客需要双份的调料呢?
在这里引入一条新的设计原则:
类应该对扩展开放,对修改关闭。
我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。如果能实现这样的目标,有什么好处呢?
这样的设计具有弹性可以应对change,可以接受新的功能来应对改变的需求。
在选择需要被扩展的代码部分时要小心。每个地方都采用 开放-关闭 原则,是一种浪费,也没必要,还会最终导致代码变得复杂且难以理解。
2.2 认识装饰者模式
目前所知道的关于装饰者的一切:
1、装饰者和被装饰者对象有相同的超类型;
2、你可以用一个或者多个装饰者包装一个对象;
3、既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象的场合,可以用装饰过的对象代替它;
4、装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的;
5、对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。
装饰者模式:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
还记得本章的需求背景吗?里面谈到的原材料就是装饰者对象,而调料则是装饰者。
在以上分析的基础上,我们以Beverage为装饰者对象和装饰者的超类,装饰者对象直接继承Beverage类,为所有调料类设计一个超类condimentDecorator(抽象装饰者)继承于Beverage类。
Beverage类定义
public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
CondimentDecorator类定义
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 Soy extends CondimentDecorator {
Beverage beverage;
public Soy(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Soy";
}
public double cost() {
return .15 + 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());
}
}
2.3 真实世界的装饰者
java.io包内的类太多了,简直是排山倒海。下面是一个典型的对象集合,用装饰者来将功能结合起来,以读取文件数据:
超类:InputStream
组件:FileInputStream、StringBufferInputStream、ByteArrayInputStream
抽象装饰者:FilterInputStream
具体装饰者:PushBackInputStream、BufferedInputStream、DataInputStream、LineNumberInputStream
但是Java I/O也引出装饰者模式的一个“缺点”:利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能会造成所用此API程序员的困扰。
但是,现在你已经了解了装饰者的工作原来,以后当使用别人的大量装饰的API时,就可以很容易地辨别出他们的装饰者类时如何组织的,已方便用包装方式取得想要的行为。
3 本章小结
装饰者模式是JDK中另一个使用较多的设计模式,上一个是观察者模式(在Swing中大量使用),业内好的API设计无一离不开常见的设计模式,通常我们所说要阅读源码,也是为了学习大牛们的设计思路。
与本书《Head First 设计模式》相比,一个理论,一个实践,实践是检验真理的唯一方式,理论结合实际才是一个码农进步的关键思想。