### 一、咖啡店的故事
这次我们借用HeadFirst中的咖啡店的故事来讨论一下装饰模式。咖啡店中有各种种类的咖啡和咖啡需要加的配料。有一家咖啡店为了提高效率打算开发一套咖啡订购系统,用户可以根据清单选择咖啡和咖啡所加的配料,系统可以自动的计算总价格。
第一种方案是这个样子的:
![这里写图片描述](https://box.kancloud.cn/2016-04-20_571758542aea6.jpg "")
Beverge是一个抽象类,店内所有的饮料都必须继承自这个类。description用来描述这个是什么类型的饮料例如:Dark Roast。getDescription()方法返回这种饮料的描述,cost()返回这种饮料的描述,但是cost()方法是抽象的,具体的实现由子类决定,因为每中不同饮料价格都不相同。下面的四个类代表四种不同的饮料,他们都实现了父类的cost方法。
因为用户购买咖啡时还需要搭配一些调料,所以每种咖啡又分为各种不同的种类,于是系统就变成了这个样子。
![](https://box.kancloud.cn/2016-04-21_571890eb7043f.jpg)
这种方案造成的最大的问题就是类的数量众多,维护成本非常大。试想如果牛奶的价格上涨,那么每种添加牛奶的咖啡就都必须修改自己的价格,所以这种方案不符合“开-闭”原则。
第二种方案:
![](https://box.kancloud.cn/2016-04-21_571890eba4d39.jpg)
milk,soy,mocha,whip都是代表有没有这种调料的布尔值,下面的hasMilk()方法等是返回这个变量的值,setMilk()方法是设置这个变量的值。并且Beverage中的cost()方法也不再是抽象方法而是返回所加的各种调料的价格,子类会覆盖这个cost方法,子类的cost方法会返回总的价格,首先调用父类的cost方法得到调料的价格,然后再加上咖啡的价格,返回总的价格。
采用这种方法只需要5各类就能表示出这家咖啡店中所有的咖啡和配料组合。但是仍然存在问题,试想,如果我们如果增加了一种新的调料,那么Beverage类不就需要更改吗,如果客户想买双倍摩卡咖啡怎么办?如果以后开发出了一种新的饮料,比如说“茶”,对于这种饮料而言某些调料是不合适的,比如说(奶泡),但是它还是继承了所有的方法,包括不合适的。
第三种方案:装饰模式
我们把咖啡本身当做是主要的本体,而把奶泡等调料当做是咖啡的装饰,我们通过给本体添加不同的装饰来获得不同的结果。
这是咖啡的本体,我们以DarkRoast为例,这种咖啡是继承自Beverage的,它的cost()是用来返回咖啡的价格。
![](https://box.kancloud.cn/2016-04-21_571890ebbeec1.jpg)
如果客户想要摩卡咖啡,就建立一个Mocha对象,并用他将DarkRoast对象包起来(装饰)。Mocha就是一个装饰者,他的类型和他所装饰的类型是一样的,都是Beverage。Mocha也有一个cost方法,它的cost()方法会首先调用所装饰的对象DarkRoast类的cost()方法获得咖啡的价格,然后再加上自己本身的价格形成最后总的价格。
![](https://box.kancloud.cn/2016-04-21_571890ebcebcb.jpg)
如果顾客也想要加入奶泡,那就在建立一个Whip的装饰者,并用他将上边的Mocha对象包起来,Whip也是继承自Beverage。
![](https://box.kancloud.cn/2016-04-21_571890ebe4271.jpg)
现在,当为顾客计算价格的时候,通过最外圈装饰者(Whip)的cost就可以办到。Whip的cost()会委托它所装饰的对象计算出价钱,然后加上奶泡的价钱。
![](https://box.kancloud.cn/2016-04-21_571890ec0dc8c.jpg)
### 二、装饰模式的定义
装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。
### 三、装饰模式的结构
![](https://box.kancloud.cn/2016-04-21_571890ec37ad3.jpg)
从上图中可以看出,装饰模式一共有四部分组成:
1、抽象组件角色(Component):定义一个对象接口,以规范准备接受附加责任的对象,即可以给这些对象动态地添加职责。
2、具体组件角色(ConcreteComponent) :被装饰者,定义一个将要被装饰增加功能的类。可以给这个类的对象添加一些职责。
3、抽象装饰器(Decorator):维持一个指向构件Component对象的实例,并定义一个与抽象组件角色Component接口一致的接口。
4、具体装饰器角色(ConcreteDecorator):向组件添加职责。
### 四、装饰模式的实例
下面是上边所说的咖啡馆的代码。
~~~
package com.designpattern.decorator;
/**
* 抽象组件,装饰者和被装饰者都继承自它
* @author 98583
*
*/
public abstract class Beverage {
/**
* 饮料的名称,用来代表是哪种饮料
*/
protected String description = "Unknown Beverage";
public String getDescription() {
return description;
}
/**
* 每个子类都有自己的实现方法
* @return
*/
public abstract double cost();
}
~~~
~~~
package com.designpattern.decorator;
/**
* 抽象装饰者,定义具体装饰者要实现的代码
* @author 98583
*
*/
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
~~~
~~~
package com.designpattern.decorator;
/**
* 具体的装饰者
* @author 98583
*
*/
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 .20 + beverage.cost();
}
}
~~~
~~~
package com.designpattern.decorator;
/**
* 被装饰者
* @author 98583
*
*/
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "DarkRoast";
}
public double cost() {
return .99;
}
}
~~~
~~~
package com.designpattern.decorator;
public class StarbuzzCoffee {
public static void main(String[] args) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " $" + beverage.cost());
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out
.println(beverage2.getDescription() + " $" + beverage2.cost());
Beverage beverage3 = new HouseBlend();
beverage3 = new Soy(beverage2);
beverage3 = new Mocha(beverage2);
beverage3 = new Whip(beverage2);
System.out
.println(beverage3.getDescription() + " $" + beverage3.cost());
}
}
~~~
其他类的代码都是相似的,这里不再贴出,最后会附上源码。
### 五、装饰模式的优缺点
**装饰模式的优点:**
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
- 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
- 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”
**装饰模式的缺点:**
- 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
- 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
### 六、装饰模式的适用环境
1、在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
2、需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
3、当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承(如final类).
源码下载:[http://download.csdn.net/detail/xingjiarong/9317741](http://download.csdn.net/detail/xingjiarong/9317741)
- 前言
- 设计原则(一)"开-闭"原则(OCP)
- 设计原则(二)里氏替换原则(LSP)
- 设计原则(三)组合复用原则
- 设计原则(四)依赖倒置原则(DIP)
- 设计模式(一)简单工厂模式
- 设计模式(二)工厂方法模式
- 设计模式(三)抽象工厂模式
- 设计模式(四)单例模式
- 设计模式(五)创建者模式(Builder)
- 设计模式(六)原型模式
- 设计模式(七)门面模式(Facade Pattern 外观模式)
- 设计模式(八)桥梁模式(Bridge)
- 设计模式(九)装饰模式(Decorator)
- 设计模式(十)适配器模式
- 设计模式(十一)策略模式
- 设计模式(十二)责任链模式
- 设计模式之UML(一)类图以及类间关系(泛化 、实现、依赖、关联、聚合、组合)
- 设计模式之桥梁模式和策略模式的区别