目录
一.专栏简介
本专栏是我学习《head first》设计模式的笔记。这本书中是用Java语言为基础的,我将用C++语言重写一遍,并且详细讲述其中的设计模式,涉及是什么,为什么,怎么做,自己的心得等等。希望阅读者在读完我的这个专题后,也能在开发中灵活且正确的使用,或者在面对面试官时,能够自信地说自己熟悉常用设计模式。
在策略模式和观察者模式的学习之后,本章将开始装饰者模式的学习。
二.星巴兹咖啡
书中以星巴兹咖啡为例,这里的星巴兹就是以星巴克为原型的。它是一家扩张速度极快的咖啡店,所以着急更新其下单系统,以匹配他们的饮料供应需求。他们订单系统中的饮料类一开始设计像这样:

根据这个图我们就懂得了饮料类的继承关系,非常的简单。值得一提的是,description在基类Beverage中应该是protected的,这样子类在拿到后可以在构造函数中修改它,成为子类具体饮料自己的描述。
另外,除了咖啡本身,我们还要求各种调料,例如:蒸奶(steamed milk)、豆奶(soy)、摩卡(mocha)或覆盖奶泡。星巴兹会对每个调料收取一点费用,所以需要把这些调料构造进订单系统。这是第一次尝试:

每个cost方法计算咖啡加上订单上其他调料的价格。
What f**k!!!这简直就是class boom(类爆炸)!于我而言,这就是一种可怕的硬编码,后期的维护和扩展将非常麻烦。想想看,增加一种配料或者增加一种咖啡,那都是笛卡尔积数量的增加。违反了封装变化这一设计原则。
以上方法非常之愚蠢,现在我们使用实例变量和继承来跟踪调研。从Beverage基类开始,加上实例变量代表是否加上调料(牛奶、豆奶、摩卡、奶泡......)
类图与介绍如下:

加上子类的类图与介绍如下:

代码如下:
Beverage.h:
cpp
#pragma once
#include <iostream>
#include <string>
using namespace std;
class Beverage
{
public:
Beverage();
string& getDescription();
virtual double cost() = 0;
bool hasMilk();
void setMilk(bool add);
bool hasSoy();
void setSoy(bool add);
bool hasMocha();
void setMocha(bool add);
bool hasWhip();
void setWhip(bool add);
protected:
string description;
bool milk;
bool soy;
bool mocha;
bool whip;
};
class DarkRoast : public Beverage
{
public:
DarkRoast();
double cost() override;
private:
};
Beverage.cpp:
cpp
#include "Beverage.h"
Beverage::Beverage():
description("星巴兹饮料"), milk(false), soy(false), mocha(false), whip(false)
{
}
string& Beverage::getDescription()
{
return description;
}
double Beverage::cost()
{
return milk ? 0.1 : 0 + soy ? 0.3 : 0 + mocha ? 0.5 : 0 + whip ? 0.2 : 0;
}
bool Beverage::hasMilk()
{
return milk;
}
void Beverage::setMilk(bool add)
{
milk = add;
}
bool Beverage::hasSoy()
{
return soy;
}
void Beverage::setSoy(bool add)
{
soy = add;
}
bool Beverage::hasMocha()
{
return mocha;
}
void Beverage::setMocha(bool add)
{
mocha = add;
}
bool Beverage::hasWhip()
{
return whip;
}
void Beverage::setWhip(bool add)
{
whip = add;
}
DarkRoast::DarkRoast()
{
description = "Most Excellent Dark Roast";
}
double DarkRoast::cost()
{
return Beverage::cost() + 7.5;
}
这里有个要点,子类不能在构造函数的初始化列表里初始化从父类继承过来的变量,因为这会导致这个成员变量被初始化两次,这是不被允许的。子类若非想在初始化列表里初始化从父类继承过来的成员变量,就调用父类的有参构造函数,而不是默认调用父类的无参或者默认构造函数。
经过这次优化,只有五个类了,确实不错,但是还是有很多潜在的问题:
- 调料价格改变会迫使我们更改现有的代码。
- 新的调料会迫使我们添加新的方法,并改变超类中的cost方法。
- 可能有新的饮料。对于其中一些饮料(冰茶?),调料可能不适合,然而,Tea(茶)子类任将继承像hasWhip()(是否加奶泡)这样的方法。(这就是前面博客说过的继承的缺点:基类的变更会不经意地影响其它子类)
- 顾客想要双倍摩卡,怎么办?
三.开放-关闭原则
我们在面对最重要的设计原则之一:

我们的目标是允许类容易扩展以扩容新的行为,而不用修改已有代码。达成这个目标,有什么好处?这样的设计可以弹性应对变化,有足够弹性接受新的功能来应对改变的需求。
需要注意的是,我们不能使得我们的代码处处都遵循开闭原则,首先这非常难,其次还会让代码很抽象,增加代码的复杂度。我们只需要将代码中最有可能改变的区域应用开闭原则。
四.结识装饰者模式
我们将从饮料开始并在运行时用调料"装饰"它。例如,如果顾客要一份带摩卡和奶泡的深度烘培,那么我们将:
- 从DarkRoast(深度烘培)对象开始。
- 用一个Mocha(摩卡)对象装饰它。
- 用一个Whip对象装饰它。
- 调用cost()方法,依靠委托来累加调料价钱。
但如何"装饰"一个对象,委托是怎么来的?提示:把装饰者对象想成"包装者"。我们来看看这如何运作......
五.用装饰者构造饮料订单
咱们直接看书里的图吧,书里说得好为什么不用呢是吧。


现在我们知道了装饰者模式的一些信息:
- 装饰者有着和所装饰对象同样的超类型。
- 你可以用一个或多个装饰者包裹一个对象。
- 鉴于装饰者有着和所装饰对象同样的超类型,在需要原始对象的场合,我们可以传递一个被装饰的对象。
- 装饰者在委托给所装饰对象之前或之后添加自己的行为,来做剩下的工作。
- 对象可以在任何时候被装饰,因此我们可以在运行时用任意数量的装饰者动态地装饰对象,只要我们乐意。
六.装饰者模式的定义
装饰者模式动态地将额外责任附加到对象上。对于扩展功能,装饰者提供子类化之外的弹性替代方案。

七.装饰我们的饮料
我们使用装饰者模式来重做星巴兹饮料。

代码如下:
Beverage.h:
cpp
class Beverage
{
public:
virtual string getDescription();
virtual double cost() = 0;
protected:
string description;
};
class HouseBlend : public Beverage
{
public:
HouseBlend();
double cost() override;
};
class DarkRoast : public Beverage
{
public:
DarkRoast();
double cost() override;
};
class Espresso : public Beverage
{
public:
Espresso();
double cost() override;
};
class Decaf : public Beverage
{
public:
Decaf();
double cost() override;
};
class CondimentDecorator : public Beverage
{
public:
protected:
Beverage* beverage = nullptr;
};
class Milk : public CondimentDecorator
{
public:
Milk(Beverage* b);
double cost() override;
string getDescription() override;
private:
};
class Mocha : public CondimentDecorator
{
public:
Mocha(Beverage* b);
double cost() override;
string getDescription() override;
private:
};
class Soy : public CondimentDecorator
{
public:
Soy(Beverage* b);
double cost() override;
string getDescription() override;
private:
};
class Whip : public CondimentDecorator
{
public:
Whip(Beverage* b);
double cost() override;
string getDescription() override;
private:
};
Beverage.cpp:
cpp
string Beverage::getDescription()
{
return description;
}
HouseBlend::HouseBlend()
{
description = "HouseBlend饮料";
}
double HouseBlend::cost()
{
return 8.1;
}
DarkRoast::DarkRoast()
{
description = "DarkRoast饮料";
}
double DarkRoast::cost()
{
return 7.7;
}
Espresso::Espresso()
{
description = "Espresso饮料";
}
double Espresso::cost()
{
return 6.6;
}
Decaf::Decaf()
{
description = "Decaf饮料";
}
double Decaf::cost()
{
return 7.7;
}
Milk::Milk(Beverage* b)
{
beverage = b;
}
double Milk::cost()
{
return beverage->cost() + 1.1;
}
string Milk::getDescription()
{
return beverage->getDescription() + ", milk";
}
Mocha::Mocha(Beverage* b)
{
beverage = b;
}
double Mocha::cost()
{
return beverage->cost() + 2.2;
}
string Mocha::getDescription()
{
return beverage->getDescription() + ", mocha";
}
Soy::Soy(Beverage* b)
{
beverage = b;
}
double Soy::cost()
{
return beverage->cost() + 3.3;
}
string Soy::getDescription()
{
return beverage->getDescription() + ", soy";
}
Whip::Whip(Beverage* b)
{
beverage = b;
}
double Whip::cost()
{
return beverage->cost() + 4.4;
}
string Whip::getDescription()
{
return beverage->getDescription() + ", whip";
}
在代码中,CondimentDecorator也继承了Beverage,但是这里是**使用继承达到类型匹配,但不使用继承来获得行为。**实际上我们还是通过将对象组合在一起获得新的行为。
八.样例测试代码
我们来点一杯双倍摩卡豆奶加牛奶,main.cpp代码如下:
cpp
#include "Beverage.h"
int main()
{
Beverage* dr = new DarkRoast();
Beverage* m1 = new Mocha(dr);
Beverage* m2 = new Mocha(m1);
Beverage* s = new Soy(m2);
Beverage* m = new Milk(s);
cout << m->cost() << endl;
return 0;
}
运行结果:

调用cost()方法时的流程图:

这就像递归一样,太美妙了。
九.装饰者模式的缺点
- 不适用于针对特定类型的代码
- 引入了大量的小对象,可能让代码混乱或者难以理解。
十.扩展(含最终代码)

代码如下:
Beverage.h:
cpp
#pragma once
#include <iostream>
#include <string>
using namespace std;
class Beverage
{
public:
enum Size
{
TALL,
GRANDE,
VENTI
};
virtual string getDescription();
virtual double cost() = 0;
void setSize(Size sz);
virtual Size getSize();
protected:
string description;
Size size = TALL;
};
class HouseBlend : public Beverage
{
public:
HouseBlend();
double cost() override;
};
class DarkRoast : public Beverage
{
public:
DarkRoast();
double cost() override;
};
class Espresso : public Beverage
{
public:
Espresso();
double cost() override;
};
class Decaf : public Beverage
{
public:
Decaf();
double cost() override;
};
class CondimentDecorator : public Beverage
{
public:
Size getSize() override;
protected:
Beverage* beverage = nullptr;
};
class Milk : public CondimentDecorator
{
public:
Milk(Beverage* b);
double cost() override;
string getDescription() override;
private:
};
class Mocha : public CondimentDecorator
{
public:
Mocha(Beverage* b);
double cost() override;
string getDescription() override;
private:
};
class Soy : public CondimentDecorator
{
public:
Soy(Beverage* b);
double cost() override;
string getDescription() override;
private:
};
class Whip : public CondimentDecorator
{
public:
Whip(Beverage* b);
double cost() override;
string getDescription() override;
private:
};
Beverage.cpp:
cpp
#include "Beverage.h"
string Beverage::getDescription()
{
return description;
}
void Beverage::setSize(Size sz)
{
size = sz;
}
Beverage::Size Beverage::getSize()
{
return size;
}
HouseBlend::HouseBlend()
{
description = "HouseBlend饮料";
}
double HouseBlend::cost()
{
return 8.1;
}
DarkRoast::DarkRoast()
{
description = "DarkRoast饮料";
}
double DarkRoast::cost()
{
return 7.7;
}
Espresso::Espresso()
{
description = "Espresso饮料";
}
double Espresso::cost()
{
return 6.6;
}
Decaf::Decaf()
{
description = "Decaf饮料";
}
double Decaf::cost()
{
return 7.7;
}
Milk::Milk(Beverage* b)
{
beverage = b;
}
double Milk::cost()
{
double cost = beverage->cost();
switch (getSize())
{
case TALL: cost += 9; break;
case GRANDE: cost += 7; break;
case VENTI: cost += 5; break;
default: break;
}
return cost;
}
string Milk::getDescription()
{
return beverage->getDescription() + ", milk";
}
Mocha::Mocha(Beverage* b)
{
beverage = b;
}
double Mocha::cost()
{
double cost = beverage->cost();
switch (getSize())
{
case TALL: cost += 17; break;
case GRANDE: cost += 15; break;
case VENTI: cost += 12; break;
default: break;
}
return cost;
}
string Mocha::getDescription()
{
return beverage->getDescription() + ", mocha";
}
Soy::Soy(Beverage* b)
{
beverage = b;
}
double Soy::cost()
{
double cost = beverage->cost();
switch (getSize())
{
case TALL: cost += 22; break;
case GRANDE: cost += 17; break;
case VENTI: cost += 12; break;
default: break;
}
return cost;
}
string Soy::getDescription()
{
return beverage->getDescription() + ", soy";
}
Whip::Whip(Beverage* b)
{
beverage = b;
}
double Whip::cost()
{
double cost = beverage->cost();
switch (getSize())
{
case TALL: cost += 20; break;
case GRANDE: cost += 15; break;
case VENTI: cost += 10; break;
default: break;
}
return cost;
}
string Whip::getDescription()
{
return beverage->getDescription() + ", whip";
}
CondimentDecorator::Size CondimentDecorator::getSize()
{
return beverage->getSize();
}
main.cpp:
cpp
#include "Beverage.h"
int main()
{
Beverage* dr = new DarkRoast();
dr->setSize(Beverage::GRANDE);
Beverage* m1 = new Mocha(dr);
Beverage* m2 = new Mocha(m1);
Beverage* s = new Soy(m2);
Beverage* m = new Milk(s);
cout << m->cost() << endl;
cout << m->getDescription() << endl;
return 0;
}
输出结果:

用计算器计算结果,答案正确,测试通过!!!
另外的扩展,想象一个CondimentPrettyPrint装饰者,它解析最后的描述,并打印"Mocha.Whip,Mocha"为"Whip,Double Mocha"。这样我们就得到一个漂亮的输出。注意,getDescription()可以返回一个描述的AttrayList。这样更容易编码。