【Java设计模式】2.装饰者模式

【Java设计模式】2.装饰者模式

问题的引入

在本市最繁华的一条街上,开了一家星巴菲(没错,不是星巴克为了避嫌 )。

这家星巴菲咖啡由于初来乍到,只能付费算账系统还未完善。不过,由于星巴菲的咖啡非常香醇、提神,并且价格优惠(简直就是教科书般的物美价廉),收到了广大上班族的推崇,甚至几个街区外的白领们宁愿上班多绕路来这里买咖啡,也不愿意去公司附近的开封菜去买 “咖啡” 。

不久之后,每天早上店里都人满为患,也正是因为来的顾客太多,原来的人为计算最终咖啡加配料价格的方式显然不能满足快速的结账。

所以,星巴菲的经理找到了你,也就是这个街区最优秀的程序员来帮他们设计这样一个结账结算系统。

首先进行系统框架的构思

最初的简单构思

可以看到,我们设计了一个Beverage类,有description变量,为字符串,用来记录这杯咖啡的类型,例如拿铁之类的。接着milk、soy、mocha以及whip为布尔值类型变量,其值代表了是否加入这些调料。下面的方法有得到这杯饮料的信息、计算整杯饮料的价钱以及给这杯饮料增加或删减调料。

从目前来看,Beverage类里的成员变量以及所包含的方法可以满足最开始的要求,即根据一杯饮料以及其加的配料,可以得到最终这杯饮料的名称以及总价钱。

但是!如果一位顾客想要一杯加了双倍摩卡的咖啡,该怎么办呢?并且,如果店里突然加了一种新的咖啡调料,那我们就要修改代码。

可以看到,如果要增加一种咖啡调料,那必须要增加一个对应布朗类型的成员变量,并且还要有对应加调料的方法,以及在cost()方法中写入对应新加调料的价格。

这么一看,那不就是把这个类进行大修改嘛!我们知道,编程序中,最忌讳的就是代码量太大,并且做出调整时,要把总代码进行修改,这样可能新代码还没实现,结果老代码也出现了问题。

所以有没有一种方法,让我们的程序代码 “对扩展开放,对修改关闭” 呢?

装饰者模式

对于本篇文章的星巴菲咖啡问题,我用装饰者模式来为其构思整个系统:

  1. 拿一个深焙咖啡(也就是DarkRoast咖啡)对象
    ps:此处的对象是指在堆上new出来的一个对象

  2. 用摩卡(Mocha)对象来装饰它

  3. 用奶泡(Whip)对象来装饰它

  4. 最终得到了摩卡奶泡深焙咖啡,并且用cost()方法计算了其最终价格 下面用一组图来表达这种关系:

    在这幅关系图中,看得出来,DarkRoast是咖啡的品种,首先Mocha调料作为装饰者类将其包含在里面,同样Whip调料类再将Mocha类包含在里面。以此类推,想要添加什么,就添加什么调料。并且其解决了刚刚提出的问题,问题以及解决方法都如下:

  5. 一位顾客想要一杯加了双倍摩卡的咖啡
    解决方法:由于不用类里面的成员变量来表达是否添加调料,但是在这种方法下,顾客想加多少摩卡就加多少,甚至加一杯都可以。

  6. 添加一种新的调料并添加与调料相关的部分数据
    解决方法:既然我们添加调料都是将原来调好的咖啡类都包含在其中,那么也就是说,调料的添加无需在咖啡类中做出修改。我们只需要另外写一个调料类,然后将原咖啡或已经加了一些调料的咖啡类包含进来即可。

具体框架设计

框架

代码实现

Beverage类

1
2
3
4
5
6
7
8
public abstract class Beverage {
String description = "Unknown Beverage";

public String getDescription(){
return description;
}
public abstract double cost();
}

4种咖啡类

HouseBlend

1
2
3
4
5
6
7
8
9
10
public class HouseBlend extends Beverage {
public HouseBlend(){
description = "HouseBlend";
}

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

DarkRoast

1
2
3
4
5
6
7
8
9
10
11
public class DarkRoast extends Beverage {
public DarkRoast(){
description = "DarkRoast";
}

@Override
public double cost() {
// TODO Auto-generated method stub
return 1.29;
}
}

Decaf

1
2
3
4
5
6
7
8
9
10
11
public class Decaf extends Beverage {
public Decaf(){
description = "Decaf";
}

@Override
public double cost() {
// TODO Auto-generated method stub
return 1.36;
}
}

Espresso

1
2
3
4
5
6
7
8
9
10
11
public class Espresso extends Beverage {
public Espresso(){
description = "Espresso";
}

@Override
public double cost() {
// TODO Auto-generated method stub
return 1.59;
}
}

调料抽象类

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

四种调料类

Milk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Milk extends Condiment {
Beverage beverage;

public Milk(Beverage b){
this.beverage = b;
}

public String getDescription(){
return beverage.getDescription()+",Milk";
}

public double cost(){
return 0.85+beverage.cost();
}
}

Mocha

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Mocha extends Condiment {
Beverage beverage;

public Mocha(Beverage b){
this.beverage = b;
}

public String getDescription(){
return beverage.getDescription()+",Mocha";
}

public double cost(){
return 0.75+beverage.cost();
}
}

Whip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Whip extends Condiment {
Beverage beverage;

public Whip(Beverage b){
this.beverage = b;
}

public String getDescription(){
return beverage.getDescription()+",Whip";
}

public double cost(){
return 0.95+beverage.cost();
}
}

Soy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Soy extends Condiment {
Beverage beverage;

public Soy(Beverage b){
this.beverage = b;
}

public String getDescription(){
return beverage.getDescription()+",Soy";
}

public double cost(){
return 1.05+beverage.cost();
}
}

测试类

1
2
3
4
5
6
7
8
9
10
public class BeverageTest {
public static void main(String[] args){
Beverage beverage1 = new Decaf();
beverage1 = new Milk(beverage1);
beverage1 = new Milk(beverage1);
beverage1 = new Whip(beverage1);
beverage1 = new Mocha(beverage1);
System.out.println(beverage1.getDescription()+" $ "+beverage1.cost());
}
}

最终运行结果

运行结果

总结

装饰者模式是一个较为典型的 “对扩展开放,对修改关闭” 模式。
装饰者可以在被装饰者的行为前面与后面加上自己的行为,甚至将被装饰者的行为整个替代掉,而达到特定的目的。
但是装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂,所以设计时也要注意使用的次数。