Decorator模式:越来越贵的咖啡

需求故事

    1. 作为一个Espresso(10块钱),什么都不加价格是10块钱
    1. 作为一个Espresso(10块钱),希望可以加sugar(1块钱),加牛奶(4块钱),加香草(3块钱),这样最终价格是18块钱
    1. 作为一个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模式使用的场景是:需要对某个对象进行多层包装,但是又不想破坏原有对象的行为代码。

你可能感兴趣的:(Decorator模式:越来越贵的咖啡)