Base Station

装饰者模式(Decorator)

"Head First学习笔记(三)"

字数统计: 1.3k阅读时长: 5 min
2017/02/08

your internal mediocrity is the moment when you lost the faith of being excellent

前言

又到了学习 Head First 的时间了,今天大名鼎鼎的星巴兹咖啡找到我们,让我们帮忙更新他们的订单系统,他们现在的订单系统是这样的:system

看起来还好不是吗,简单清晰。所有的饮料都继承Beverage,具体的饮料用cost()来计算价格description来描述。但是他们遇到了一个问题,购买咖啡时候,也可以要求在其中加入各种调料,例如 : milk(牛奶)Soy(豆浆)Mocha(摩卡)等等。最后会更具加入的不同调料收取费用。所以订单系统必须考虑的到这些调料。所以他们尝试这样搭建一个系统 :

system1

是不是看起来很酷,但收银员可不这么想,这对他来说如一个噩梦。而且当修改或增加饮料时候,那么维护起来也非常的恐怖。于是我们的小明又要接手新的活了。

正文

作为一个出色的 OO 程序员,小明也很快完成了他的第一版设计 :

system2

具体的实现 :

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class Beverage {
private static final double milkSort = 0.1;
private static final double soySort = 0.3;
private static final double mochaSort = 0.4;
private static final double whipSort = 0.5;
private String description;
private boolean milk;
private boolean soy;
private boolean mocha;
private boolean whip;

public String getDescription() { return description;}

public void setDescription(String description) {
this.description = description;
}

public boolean isMilk() { return milk;}

public void setMilk(boolean milk) {this.milk = milk;}

public boolean isSoy() {return soy; }

public void setSoy(boolean soy) {this.soy = soy;}

public boolean isMocha() {return mocha;}

public void setMocha(boolean mocha) {this.mocha = mocha;}

public boolean isWhip() {return whip; }

public void setWhip(boolean whip) {this.whip = whip;}

public double cost() {
double cost = 0;
if (isMilk()) {
cost += milkSort;
}
if (isSoy()) {
cost += soySort;
}
if (isMocha()) {
cost += mochaSort;
}
if (isWhip()) {
cost += whipSort;
}
return cost;
}
}

当我们要创建饮料时 :

1
2
3
4
5
6
7
8
public class DarkRoast extends Beverage{
public DarkRoast(){
description = "Most Excellent Dark Roast";
}
public double cost{
return 1.99+super.cost();
}
}

目前看来没有什么问题,但哪些因素会影响这个设计?

  • 调整价钱的改变会使得我们更改现有代码。
  • 一旦出现新的饮料,我们就需要加上新的方法,并且要修改超类中cost()方法。
  • 以后可能会开发出新饮料。对这些饮料而言,某些调料可能并不合适,但是在这个设计方式中,子类仍将继承那些不合适的方法。
  • 万一顾客想要双倍摩卡的咖啡怎么办?
  • 当我们要设计新的功能,例如不同价格的同一调料,那将是巨大的改动。
  • 轮到你了

什么是装饰者模式

类应该对扩展开放,对修改关闭

小明去求教了禅雅塔大师,大师告诉他可以用装饰者模式来解决这个问题并给了他一张图 :

decorator

装饰者模式就是 : 装饰者模式动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更具有弹性的替代方案。

于是小明奖星巴兹的系统也设计成了装饰者模式 :

decorator_xbz

程序实现 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//基类
public abstract class Beverage {
public String description;

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public abstract double cost();
}

装饰者类,将getDescription()抽象化,子类必须实现它 :

1
2
3
public abstract class CondimentDecorator extends Beverage{
public abstract String getDescription();
}

接下来实现具体的饮料和调料类,只各列举个一个列子 :

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
public class HouseBlend extends Beverage {
public HouseBlend() {
setDescription("HouseBlend");
}

@Override
public double cost() {
return 1.88;
}
}

public class Mocha extends CondimentDecorator{
Beverage beverage;
public Mocha(Beverage beverage)
{
this.beverage=beverage;
}
public String getDescription()
{
return beverage.getDescription()+", Mocha";
}
public double cost()
{
return 0.20+beverage.cost();
}
}

测试 :

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] arg) {
Beverage espresso=new HouseBlend();
espresso = new Mocha(espresso);
espresso = new Soy(espresso);
espresso = new Whip(espresso);
System.err.println(espresso.getDescription()+": "+espresso.cost());

Beverage houseBlend = new Espresso();
houseBlend = new Mocha(new Mocha(new Whip(houseBlend)));
System.err.println(houseBlend.getDescription()+": "+houseBlend.cost());
}

输出为 :

1
2
HouseBlend, Mocha,Soy,Whip: 2.88
Espresso,Whip, Mocha, Mocha: 1.69

同样完美的实现了星巴兹的功能,虽然看起来比刚才还稍微复杂一些,但是我们可以很轻松组合这些调料,增加和修改也不用促及底层的任何代码,收银员也不会向老板抱怨要炒我们鱿鱼——这就是装饰者模式。

要点

  • 继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式。
  • 在我们的设计中,应该允许在运行时动态地加上新的行为。
  • 除了继承,装饰者模式也可以让我们扩展行为。
  • 装饰者类反应出被装饰的组件类型(事实上,他们具有相同的类型,都经过接口或继承实现)。
  • 装饰者可以在被装饰者的行为前面/或后面加上自己的行为,甚至将装饰者的整个行为取代掉,从而达到特定的目的。
  • 你可以用无数个装饰者包装一个组件。
  • 装饰者一般对组件的客户是透明的,除非客户程序依赖与组件的具体类型。
  • 装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得复杂。
CATALOG
  1. 1. 前言
  2. 2. 正文
    1. 2.1. 什么是装饰者模式
  3. 3. 要点