1. 一个咖啡店的需求 一个咖啡店需要升级他们的系统,以满足饮料订单的需求。我们首先设计了一个类结构:
Beverage类是一个抽象类,其中cost是一个抽象方法,每个子类需要实现这个方法,以完成自己的定价。description是饮料的描述信息。
这样的设计初看上去是没有问题的,但是,顾客的需求是多变的,顾客可能会要求向饮料中加入一些其他的调味品,例如牛奶、可可等。咖啡店需要对这些添加的调味品收取一定的费用。于是,类图变成的下面这样。
无疑,这是一个糟糕的设计,为每一种添加不同调味品的饮料单独生成一个类,造成了类数量随调味品数量增加而指数扩展的现象。可以想象,如果某一个调味品的价格发生变化,那么对类进行修改将是一个灾难事件。
1.1. 让超类管理调味品价格怎么样? 首先我们来尝试让超类负责管理调味品价格,看结果是怎么样呢?
我们把Beverage类设计成这样,那么一个饮料如果需要添加milk,在下单时调用hasMilk()函数就可以加上milk的价格,milk的价格发生变化时,调用setMilk() 函数即可。
这样的设计比一开始的设计要好,但是仍然存在问题:
如果购买了新的调味品,就需要修改Beverage类
有些饮料可能并不需要这些调味品,例如茶(这里的情况和第一章的情况类似,并不是所有的鸭子都会飞)
如果顾客加了双份的milk怎么办呢?
2. 开闭原则(The Open-Closed Principle) Classes should be open for extension, but closed for modification.
类应该对扩展开放,对修改关闭。
我们的目标是在不改变原有代码的前提下,让类可以方便的扩展以拥有新的行为。
需要注意的是,在任何地方都使用开闭原则是浪费的和不必要的,它会导致复杂和那一理解的代码。
3. 装饰模式 3.1. 如何操作 我们会使用Beverage并且在运行时使用调味品对其进行装饰。例如,顾客想要一杯加Mocha和Whip的DarkRoast,我们会这样做:
调用DarkRoast类,产生一个DarkRoast对象
用Mocha对象装饰它
用Whip对象装饰它
调用cost()方法并依赖委托(delegation)来添加调味品的价格
可以把装饰对象想成是一个包装,我们用Mocha对象包装了DarkRoast对象,随后又用Whip对象包装。
接下来该计算价格了,在最外层的装饰上调用cost()方法,Whip将要去被委托计算它所装饰的对象的价格,它获得一个价格就会添加到Whip的总价上
通过上面的分析我们知道:
装饰器和他们所装饰的对象有相同的超类
可以使用一个或多个装饰器对对象进行装饰
由于decorator与它所装饰的对象具有相同的超类,我们可以传递一个装饰对象来代替原始(包装)对象。
The decorator adds its own behavior either before and/or after delegating to the object it decorates to do the rest of the job (装饰器在委派给其装饰的对象之前和/或之后添加自己的行为,以完成其余工作。)
对象可以在任何时间被装饰,所以我们可以在运行时为对象添加任意多的装饰器
3.2. 定义 1 2 The Decorator Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
3.3. 使用装饰模式重新设计
3.3.1. 代码 完整代码
3.3.1.1 Beverage类 1 2 3 4 5 6 7 8 9 public abstract class Beverage { String description = "Unkown Bervage" ; public String getDescription () { return description; } public abstract double cost () ; }
3.3.1.2. HouseBlend类 1 2 3 4 5 6 7 8 9 10 public class HoseBlend extends Beverage{ public HoseBlend() { description = "HoseBlend"; } @Override public double cost() { return 0.89; } }
3.3.1.3. CondimentDecorator类 1 2 3 public abstract class CondimentDecorator extends Beverage { public abstract String getDescription () ; }
3.3.1.4. Milk类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Milk extends CondimentDecorator { private Beverage beverage; public Milk (Beverage beverage) { this .beverage = beverage; } @Override public String getDescription () { return beverage.getDescription() + ", Milk" ; } @Override public double cost () { return beverage.cost() + 0.5 ; } }
3.3.1.5. 测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class CoffeShop { public static void main (String[] args) { Beverage beverage = new Espresso(); System.out.println(beverage.getDescription() + " $" + beverage.cost()); Beverage beverage1 = new DarkRoast(); beverage1 = new Mocha(beverage1); beverage1 = new Mocha(beverage1); beverage1 = new Whip(beverage1); System.out.println(beverage1.getDescription() + " $" + beverage1.cost()); Beverage beverage2 = new HoseBlend(); beverage2 = new Soy(beverage2); beverage2 = new Mocha(beverage2); beverage2 = new Whip(beverage2); System.out.println(beverage2.getDescription() + " $" + beverage2.cost()); } }
执行结果
1 2 3 4 5 Espresso $1.99 DarkRoast, Mocha, Mocha, Whip $1.8 HoseBlend, Soy, Mocha, Whip $1.79 Process finished with exit code 0
4. 添加需求 咖啡店设置了3中类型的杯子(小,中,大),并且调味品根据杯子的大小收费不同,例如Soy对应(小,中,大)杯的价格分别为0.1,0.2,0.3,代码应该怎么实现呢?
4.1. 代码 完整代码
我只改动了以下3个类,测试时需要打开注释的部分。
4.1.1. Beverage类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public abstract class Beverage { String description = "Unkown Bervage" ; public enum Size {SMALL, MEDIUM, LARGE}; Size size = Size.SMALL; public Size getSize () { return size; } public void setSize (Size size) { this .size = size; } public String getDescription () { return description; } public abstract double cost () ; }
在原有的代码基础上添加了
1 2 3 4 5 6 7 8 9 10 public enum Size {SMALL, MEDIUM, LARGE}; Size size = Size.SMALL; public Size getSize() { return size; } public void setSize(Size size) { this.size = size; }
4.1.2. HoseBlend类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class HoseBlend extends Beverage { private Map<Size, Double> price = new HashMap<Size, Double>(); public HoseBlend () { description = "HoseBlend" ; price.put(Size.SMALL, 0.1 ); price.put(Size.MEDIUM, 0.2 ); price.put(Size.LARGE, 0.3 ); } @Override public double cost () { return price.get(getSize()); } }
4.1.3. Milk类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Milk extends CondimentDecorator { private Beverage beverage; private Map<Size, Double> price = new HashMap<Size, Double>(); public Milk (Beverage beverage) { this .beverage = beverage; setSize(beverage.getSize()); price.put(Size.SMALL, 0.11 ); price.put(Size.MEDIUM, 0.15 ); price.put(Size.LARGE, 0.31 ); } @Override public String getDescription () { return beverage.getDescription() + ", Milk" ; } @Override public double cost () { return beverage.cost() + price.get(getSize()); } }
4.1.4. 测试 1 2 3 4 5 6 7 8 9 10 11 12 13 public class CoffeShop { public static void main (String[] args) { everage beverage3 = new HoseBlend(); beverage3.setSize(Beverage.Size.MEDIUM); beverage3 = new Milk(beverage3); System.out.println("Cup Size " + beverage3.getSize() + " " + beverage3.getDescription() + " $" + beverage3.cost()); Beverage beverage4 = new HoseBlend(); beverage4.setSize(Beverage.Size.LARGE); beverage4 = new Milk(beverage4); System.out.println("Cup Size " + beverage4.getSize() + " " + beverage4.getDescription() + " $" + beverage4.cost()); } }
执行结果:
1 2 Cup Size MEDIUM HoseBlend, Milk $0.35 Cup Size LARGE HoseBlend, Milk $0.61
5. 真实工程中的装饰模式的应用 java.io 中就使用了装饰模式
InputStream是抽象类
FilterInputStream是一个抽象装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Test1 { public static void main (String[] args) { int c; try { File file = new File("test" ); InputStream stream = new FileInputStream(file); stream = new BufferedInputStream(stream); stream = new LineInputStream(stream); while ((c = stream.read()) >= 0 ) { System.out.print((char ) c); } stream.close(); } catch (IOException e) { e.printStackTrace(); } } }
5.2. 自定义java I/O装饰器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class LowerCaseInputStream extends FilterInputStream { protected 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; } }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Test2 { public static void main (String[] args) { int c; try { InputStream in = new LowerCaseInputStream( new BufferedInputStream( new FileInputStream("test" ))); while ((c = in.read()) >= 0 ) { System.out.print((char )c); } } catch (IOException e) { e.printStackTrace(); } } }