一、模式动机
使用继承可以给现有类添加行为,但是这种方法是静态的,用户不能控制增加行为的方式和时机。因此可以使用关联机制,即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)
装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。这就是装饰模式的模式动机。
二、模式定义
装饰者模式(Decorator Pattern):动态地将责任附加到对象上。如要扩展功能,装饰者提供了比继承更有弹性的替代方案。包括抽象组件、具体组件、抽象装饰者和具体装饰者。
三、模式示例
星巴兹咖啡连锁店由于扩张速度太快了,准备更新订单系统,以满足他们的饮料供应要求。他们的类设计如下:
购买咖啡,可以要求在其中加入配料,例如:牛奶,摩卡等。星巴兹会根据所加入的配料收取不同的费用。价格表如下:
如果用继承的话,设计出很多类。采用装饰者模式来解决问题,UML类图如下:
C++代码实现
#include <string>
#include <iostream>
#include "windows.h"
using namespace std;
class Beverage
{
public:
virtual double cost() = 0;
virtual string getDescription() const { return description_; }
Beverage();
~Beverage();
protected:
string description_;
};
Beverage::Beverage()
:description_("Unknown Beverage")
{
}
Beverage::~Beverage()
{
}
//综合
class HouseBlend : public Beverage
{
public:
double cost() { return 0.89; }
string getDescription() const { return description_; }
HouseBlend();
~HouseBlend();
};
HouseBlend::HouseBlend()
{
description_ = "HouseBlend";
}
HouseBlend::~HouseBlend()
{
}
//浓缩咖啡
class Espresso : public Beverage
{
public:
double cost() { return 1.99; }
string getDescription() const { return description_; }
Espresso();
~Espresso();
};
Espresso::Espresso()
{
description_ = "Espresso";
}
Espresso::~Espresso()
{
}
//深焙咖啡
class DarkRoast : public Beverage
{
public:
double cost() { return 0.99; }
string getDescription() const { return description_; }
DarkRoast();
~DarkRoast();
private:
};
DarkRoast::DarkRoast()
{
description_ = "DarkRoast";
}
DarkRoast::~DarkRoast()
{
}
//低咖啡因
class Decaf : public Beverage
{
public:
double cost() { return 1.05; }
string getDescription() const { return description_; }
Decaf();
~Decaf();
};
Decaf::Decaf()
{
description_ = "Decaf";
}
Decaf::~Decaf()
{
}
//抽象装饰者:调料Condiment
class Condiment : public Beverage
{
public:
Condiment(Beverage* beverage);
~Condiment();
protected:
Beverage* beverage_;
};
Condiment::Condiment(Beverage* beverage)
:beverage_(beverage)
{
}
Condiment::~Condiment()
{
}
//调料:摩卡
class Mocha : public Condiment
{
public:
double cost(){ return 0.2 + beverage_->cost(); }
string getDescription() const { return beverage_->getDescription() + " , Mocha"; }
Mocha(Beverage* beverage);
~Mocha();
};
Mocha::Mocha(Beverage* beverage)
:Condiment(beverage)
{
}
Mocha::~Mocha()
{
}
//调料:牛奶
class Milk : public Condiment
{
public:
double cost(){ return 0.1 + beverage_->cost(); }
string getDescription() const { return beverage_->getDescription() + " , Milk"; }
Milk(Beverage* beverage);
~Milk();
};
Milk::Milk(Beverage* beverage)
:Condiment(beverage)
{
}
Milk::~Milk()
{
}
//调料:豆浆
class Soy : public Condiment
{
public:
double cost(){ return 0.15 + beverage_->cost(); }
string getDescription() const { return beverage_->getDescription() + " , Soy"; }
Soy(Beverage* beverage);
~Soy();
};
Soy::Soy(Beverage* beverage)
:Condiment(beverage)
{
}
Soy::~Soy()
{
}
//调料:奶泡
class Whip : public Condiment
{
public:
double cost(){ return 0.1 + beverage_->cost(); }
string getDescription() const { return beverage_->getDescription() + " , Whip"; }
Whip(Beverage* beverage);
~Whip();
};
Whip::Whip(Beverage* beverage)
:Condiment(beverage)
{
}
Whip::~Whip()
{
}
int _tmain(int argc, _TCHAR* argv[])
{
Beverage* beverage = new Espresso;
cout << beverage->getDescription() << " $" << beverage->cost() << endl;
beverage = new Mocha(beverage);
cout << beverage->getDescription() << " $" << beverage->cost() << endl;
beverage = new Milk(beverage);
cout << beverage->getDescription() << " $" << beverage->cost() << endl;
beverage = new Soy(beverage);
cout << beverage->getDescription() << " $" << beverage->cost() << endl;
beverage = new Whip(beverage);
cout << beverage->getDescription() << " $" << beverage->cost() << endl;
system("pause");
return 0;
}
运行结果
四、分析总结
装饰模式用于动态地给一个对象增加一些额外的职责,就增加对象功 能来说,装饰模式比生成子类实现更为灵活。它是一种对象结构型模 式。
优点:
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
- 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
- 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”
缺点:
- 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
- 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。