Decorator模式:越来越贵的咖啡
2017-01-24 本文已影响58人
和坚
需求故事
- 作为一个Espresso(10块钱),什么都不加价格是10块钱
- 作为一个Espresso(10块钱),希望可以加sugar(1块钱),加牛奶(4块钱),加香草(3块钱),这样最终价格是18块钱
- 作为一个DarkRoast(15块钱),希望可以加2份sugar(1块钱),3份牛奶(4块钱),这样最终的价格是29块钱
Story1
作为一个Espresso(10块钱),什么都不加价格是10块钱
Story1 Test Case
public class CoffeePriceTest {
@Test
public void testCoffeePriceCalculation(){
Espresso espresso = new Espresso();
assertEquals(10,espresso.price());
}
}
Story1 Implementation
public class Espresso {
public int price(){
return 10;
}
}
Story2
作为一个Espresso(10块钱),希望可以加sugar(1块钱),加牛奶(4块钱),加香草(3块钱),这样最终价格是18块钱
Story2 Test Case
public class CoffeePriceTest {
@Test
public void testCoffeePriceCalculation(){
Espresso espresso = new Espresso();
//Story1
assertEquals(10,espresso.price());
//Story2
espresso = new SugarAdded(espresso);
espresso = new MilkAdded(espresso);
espresso = new VanillaAdded(espresso);
assertEquals(18,espresso.price());
}
}
在设计story2的test case时,其实可以其他的测试方法,比如说给Espresso增加一个add方法来增加各种材料。但是这样做回违反OCP原则,因为当未来增加一个新材料的时候,就需要去改动Espresso的代码,这显然是不合理的。所以在TestCase中把材料看作是对espresso的包装
Story2 Implementation
public class SugarAdded extends Espresso {
private Espresso coffee;
public SugarAdded(Espresso crude) {
coffee = crude;
}
public int price() {
return coffee.price() + 1;
}
}
public class MilkAdded extends Espresso {
private Espresso coffee;
public MilkAdded(Espresso crude) {
coffee = crude;
}
public int price() {
return coffee.price() + 4;
}
}
public class VanillaAdded extends Espresso {
private Espresso coffee;
public VanillaAdded(Espresso crude) {
coffee = crude;
}
public int price() {
return coffee.price() + 3;
}
}
Story3
作为一个DarkRoast(15块钱),希望可以加2份sugar(1块钱),3份牛奶(4块钱),这样最终的价格是29块钱
Story3 Test Case
在写Story3的Test Case时,要考虑到重用SugarAdded,MilkAdded这些类,所以对Espresso和DarkRoast进行抽象成Coffee,这样加材料就可以作用在Coffee这个抽象接口上了,如果以后再增加一种新的Coffee实现,那么也不需要去改变加材料的代码。
public class CoffeePriceTest {
@Test
public void testCoffeePriceCalculation(){
Coffee espresso = new Espresso();
//Story1
assertEquals(10,espresso.price());
//Story2
espresso = new SugarAdded(espresso);
espresso = new MilkAdded(espresso);
espresso = new VanillaAdded(espresso);
assertEquals(18,espresso.price());
//Story3
Coffee darkRoast = new DarkRoast();
darkRoast = new SugarAdded(darkRoast);
darkRoast = new SugarAdded(darkRoast);
darkRoast = new MilkAdded(darkRoast);
darkRoast = new MilkAdded(darkRoast);
darkRoast = new MilkAdded(darkRoast);
assertEquals(29,darkRoast.price());
}
}
Story3 Implementation
public class DarkRoast implements Coffee {
@Override
public int price() {
return 15;
}
}
public class Espresso implements Coffee {
public int price(){
return 10;
}
}
public class SugarAdded implements Coffee {
private Coffee coffee;
public SugarAdded(Coffee crude) {
coffee = crude;
}
public int price() {
return coffee.price() + 1;
}
}
public class MilkAdded implements Coffee {
private Coffee coffee;
public MilkAdded(Coffee crude) {
coffee = crude;
}
public int price() {
return coffee.price() + 4;
}
}
从上面三个story的实现过程我们可以看到Decorator模式逐渐浮出了水面:
- 首先考虑的是增加一个新材料不应该改动Espresso的代码,所以把增加材料的行为抽象成了类
- 然后考虑到新增加一个新类型的咖啡DarkRoast也不因该影响添加材料的代码,所以抽象出了Coffe类型
和坚思辨
其实在java中有个用decorator模式实现的模块是所有java程序员一定使用过的,这个就是InputStream/OutputStream。因此Decorator模式使用的场景是:需要对某个对象进行多层包装,但是又不想破坏原有对象的行为代码。