JAVA设计模式之装饰者模式

咖啡店需要做一个订单系统,以合乎饮料供应要求。

1.最初是这样设计的:

 1 /**
 2  * 饮料抽象类
 3  *
 4  */
 5 public abstract class Beverage {
 6     
 7     protected String description;
 8     
 9     public String getDescription() {
10         return this.description;
11     }
12     
13     /**
14      * 子类需自定义自己的价格
15      * @return
16      */
17     public abstract double cost();
18     
19 }

每一种饮料都需要继承该抽象类,并覆写cost()方法。

2.但是购买咖啡时需要考虑到调料的部分,每种咖啡会加不同种的调料,比如蒸奶、豆浆、摩卡或者覆盖奶泡,那么订单系统需要考虑加入不同调料后的价格。因此需要实现不同的子类来定义添加不同调料后的价格。大家知道,一种咖啡跟多种调料有多种组合方式,那么多种咖啡和多种调料的组合后,几乎是类爆炸!

维护极其困难:

如果某种饮料价格调整;或是新增了某种饮料,怎么办?

后来经过改进后,把是否存在某种调料作为饮料的属性,并在饮料抽象类中实现cost方法,子类可以覆写cost方法并设置添加的调料最终确定添加不同调料后的价格:

 1 /**
 2  * 饮料抽象类
 3  *
 4  */
 5 public abstract class Beverage {
 6     
 7     private boolean milk;//牛奶
 8     private boolean soy;//豆浆
 9     private boolean mocha;//摩卡
10     private boolean whip;//奶泡
11     
12     private double milkCost = 0.19;
13     private double soyCost = 0.26;
14     private double mochaCost = 0.29;
15     private double whipCost = 0.17;
16     
17     protected String description;
18     
19     //setter getter method
20 
21     public String getDescription() {
22         return this.description;
23     }
24     
25     public double cost() {
26         double condimentCost = 0.0;
27         if (hasMilk()) {
28             condimentCost += milkCost;
29         }
30         if (hasSoy()) {
31             condimentCost += soyCost;
32         }
33         if (hasMocha()) {
34             condimentCost += mochaCost;
35         }
36         if (hasWhip()) {
37             condimentCost += whipCost;
38         }
39         return condimentCost;
40     }
41     
42 }
43 
44 /**
45  * 低糖咖啡
46  *
47  */
48 public class Decaf extends Beverage {
49     
50     @Override
51     public String getDescription() {
52         return "It is Decaf.";
53     }
54 
55     @Override
56     public double cost() {
57         super.setMilk(true);//添加牛奶调料
58         return 1.99 + super.cost();
59     }
60     
61 }

这样一来,如果有五种咖啡,那么只需要实现五个子类即可,不同的子类可以灵活设置添加不同的调料。

但是这样的设计存在一定的问题:

1)调料价格的改变会使我们改变现有代码;

2)出现新调料,就需要加上新的方法,并改变父类中的cost方法;

3)若出现新的饮料,如红茶,那么新的饮料继承该父类,父类中的调料属性并不合适,如奶泡等;

... ...

设计原则:类应该对扩展开放,对修改关闭。

装饰者模式思想:以饮料为主体,然后在运行时以调料来“装饰”饮料。

例如客户需要摩卡和奶泡深焙咖啡,那么要做的是:

拿一个深焙咖啡对象;

以摩卡对象装饰;

以奶泡对象装饰;

摩卡和奶泡属于调料,但是也是装饰者,它的类型反映了它装饰的对象,所谓反映,指的是两者类型一致。那么所有调料需要继承Beverage。

使用装饰者模式设计的代码:

 1 /**
 2  * 饮料抽象类
 3  *
 4  */
 5 public abstract class Beverage {
 6     
 7     protected String description;
 8     
 9     public String getDescription() {
10         return this.description;
11     }
12     
13     /**
14      * 获取每种饮料的价格
15      * @return
16      */
17     public abstract double cost();
18 }
19 
20 /**
21  * 调料抽象类
22  *
23  */
24 public abstract class Condiment extends Beverage {
25     
26     public abstract String getDescription();
27     
28 }

这里调料继承饮料,仅仅是为了使两者具有相同的类型,并非为了复用父类的行为。

下面是饮料的子类:

 1 /**
 2  * 深焙咖啡
 3  *
 4  */
 5 public class DarkRoast extends Beverage {
 6     
 7     public DarkRoast() {
 8         description = "DarkRoast";
 9     }
10     @Override
11     public double cost() {
12         return 0.19;
13     }
14 
15 }
16 
17 /**
18  * 浓缩咖啡
19  *
20  */
21 public class Espresso extends Beverage {
22     
23     public Espresso() {
24         description = "Espresso";
25     }
26     
27     @Override
28     public double cost() {
29         return 1.99;
30     }
31 
32 }
33 
34 /**
35  * 黑咖啡
36  *
37  */
38 public class HoseBlend extends Beverage {
39     
40     public HoseBlend() {
41         description = "Hose Blend Coffee";
42     }
43     
44     @Override
45     public double cost() {
46         return 0.99;
47     }
48 
49 }

调料(装饰者)子类:

 1 /**
 2  * 摩卡
 3  *
 4  */
 5 public class Mocha extends Condiment {
 6 
 7     private Beverage beverage;
 8     
 9     public Mocha(Beverage beverage) {
10         this.beverage = beverage;
11     }
12 
13     @Override
14     public String getDescription() {
15         return beverage.getDescription() + ", Mocha";
16     }
17 
18     @Override
19     public double cost() {
20         return 0.20 + beverage.cost();
21     }
22 
23 }
24 
25 /**
26  * 豆浆
27  *
28  */
29 public class Soy extends Condiment {
30     
31     private Beverage beverage;
32     
33     public Soy(Beverage beverage) {
34         this.beverage = beverage;
35     }
36 
37     @Override
38     public String getDescription() {
39         return beverage.getDescription() + ", Soy";
40     }
41 
42     @Override
43     public double cost() {
44         return 0.23 + beverage.cost();
45     }
46 
47 }
48 
49 /**
50  * 奶泡
51  *
52  */
53 public class Whip extends Condiment {
54     
55     private Beverage beverage;
56     
57     public Whip(Beverage beverage) {
58         this.beverage = beverage;
59     }
60     
61     @Override
62     public String getDescription() {
63         return beverage.getDescription() + ", Whip";
64     }
65 
66     @Override
67     public double cost() {
68         return 0.69 + beverage.cost();
69     }
70 
71 }

测试代码:

 1 public class ComponentTest {
 2     @Test
 3     public void test() {
 4         Beverage beverage = new Espresso();
 5         System.out.println(beverage.getDescription() + ", $" + beverage.cost());
 6         Beverage beverage2 = new HoseBlend();
 7         beverage2 = new Mocha(beverage2);
 8         beverage2 = new Mocha(beverage2);
 9         beverage2 = new Whip(beverage2);
10         System.out.println(beverage2.getDescription() + ", $" + beverage2.cost());
11         Beverage beverage3 = new DarkRoast();
12         beverage3 = new Soy(beverage3);
13         beverage3 = new Mocha(beverage3);
14         beverage3 = new Whip(beverage3);
15         System.out.println(beverage3.getDescription() + ", $" + beverage3.cost());
16     }
17 }

运行结果:

1 Espresso, $1.99
2 Hose Blend Coffee, Mocha, Mocha, Whip, $2.08
3 DarkRoast, Soy, Mocha, Whip, $1.31

java/IO中有很多用到装饰者模式的设计,有兴趣的朋友可以了解下。