1. 模式导言
-
前置条件
咖啡市场的竞争十分激烈,新的咖啡豆和咖啡口感层出不穷
假设,使用Beverage对咖啡进行抽象。我们考虑两个主要的参数,咖啡价格和咖啡描述class Beverage { public: float getcost(void){return cost} string getdesc(void){return desc} private: float cost; string desc; }; -
方案设计
我们考虑,每增加一种咖啡豆和配料的组合,我们就增加一种Beverage的子类。
如下图示,显而易见
我们有4种咖啡类别,HouseBlend,DarkRoast,Espresso,Decaf。
消费者可以选择基于以上4种咖啡进行调配,加Milk、加Suger或者是加Mocha(不加调味,加一种、两种或者三种调味)简单计算:消费者有多少种选择?
4 + 4 * 3 + 4 * 3 + 4 = 32种 -
方案问题
当咖啡调味或者咖啡类别变多,代码中的品类太多。维护起来会很困难。

2. 解决方案
2.1 存储方案
我们可以考虑把调味类型和主要咖啡类型写入配置文件或者数据库,
但是,这里有一个限制条件。
不要,永远不要把咖啡类别和调味的组合写入配置。排列组合的工作交给代码来做。
2.2 代码方案
代码解析配置文件或者数据库,分别根据不同的组合方案来计算价格和生成描述信息。
这,就是我们的装饰模式要做的。
3. 装饰模式
3.1 设计方案
左侧代码描述的是基准咖啡类别,
右侧代码描述了基准咖啡类别上可以进行何种调味(装饰)。
其基本思想是,
Decorator 持有一个成员子对象,m_beverage.
Decorator 的工作是基于m_beverage做一个增量,这样逻辑上就符合了咖啡+调味。
当然,咖啡+牛奶后,还能够再加点糖吗? 显然是需要的。
这就要求Decorator 和 在Decorator的基准上做一个增量,在代码中就会形成一种递归
- 红色的圈中,getDescription 和 getcost是不包含递归的
- 蓝色的圈中,getDescription 和getcost 计算m_beverage和自身增量的一个结果(存在递归)

3.2 设计代码
#include <iostream>
#include <string>
using namespace std;
class Beverage
{
public:
Beverage (string desc = "Unknown Beverage", float cost = 0):m_cost (cost), m_description (desc)
{
}
virtual ~ Beverage ()
{
}
virtual string getDescription (void)
{
return m_description;
}
virtual float getcost (void)
{
return m_cost;
}
string m_description;
float m_cost;
};
class Decaf:public Beverage
{
public:
Decaf (string desc = "Decaf", float cost = 1):Beverage (desc, cost)
{
}
private:
};
class DarkRoast:public Beverage
{
public:
DarkRoast (string desc = "DarkRoast", float cost = 2):Beverage (desc,
cost)
{
}
};
class Espresso:public Beverage
{
public:
Espresso (string desc = "Espresso", float cost = 3):Beverage (desc,
cost)
{
}
};
class HouseBlend:public Beverage
{
public:
HouseBlend (string desc = "HouseBlend", float cost = 4):Beverage (desc,
cost)
{
}
};
class Decorator:public Beverage
{
public:
Decorator (string descA = "Unknow Decorator", float costA = 0, Beverage * beverageBase = NULL):Beverage (descA,
costA),
m_beverage (beverageBase)
{
}
string getDescription (void)
{
return m_beverage->getDescription() + m_description;
}
float getcost (void)
{
return m_beverage->getcost() + m_cost;
}
private:
Beverage * m_beverage;
};
class addMilkDecorator:public Decorator
{
public:
addMilkDecorator (string concreteDescA = "Milk", float concreteCostA = 0.1, Beverage * beverage = NULL):Decorator (concreteDescA, concreteCostA,
beverage)
{
}
};
class addMochaDecorator:public Decorator
{
public:
addMochaDecorator (string concreteDescA = "Mocha", float concreteCostA = 0.2, Beverage * beverage = NULL):Decorator (concreteDescA, concreteCostA,
beverage)
{
}
};
class addSugerDecorator:public Decorator
{
public:
addSugerDecorator (string concreteDescA = "Suger", float concreteCostA = 0.3, Beverage * beverage = NULL):Decorator (concreteDescA, concreteCostA,
beverage)
{
}
};
int
main ()
{
Decaf *decaf = new Decaf();
DarkRoast *darkroast = new DarkRoast();
Espresso *espresso = new Espresso();
HouseBlend *houseblend = new HouseBlend();
Beverage* decafwithMilk = new addMilkDecorator("+Milk", 0.1, decaf);
cout << decafwithMilk->getDescription() << " " << decafwithMilk->getcost() << endl;
Beverage* decafwithMilkandSuger = new addSugerDecorator("+Suger", 0.2,decafwithMilk);
cout << decafwithMilkandSuger->getDescription() << " " << decafwithMilkandSuger->getcost() << endl;
Beverage* darkroastwithMocha = new addMochaDecorator("+Mocha",0.3,darkroast);
cout << darkroastwithMocha->getDescription() << " " << darkroastwithMocha->getcost() << endl;
delete decaf;
delete darkroast;
delete espresso;
delete houseblend;
delete decafwithMilk;
delete decafwithMilkandSuger;
delete darkroastwithMocha;
return 0;
}
代码输出
Decaf+Milk 1.1
Decaf+Milk+Suger 1.3
DarkRoast+Mocha 2.3
该博客探讨了一个咖啡售卖系统面临种类繁多的咖啡和调料组合导致的代码维护难题。通过引入装饰模式,将咖啡基类和调料作为装饰器,实现了灵活的组合和动态扩展,有效解决了代码膨胀的问题。在装饰模式中,基准咖啡类和调料装饰类分离,允许任意组合,简化了代码并提高了可维护性。示例代码展示了如何用C++实现这一模式,演示了如何计算价格和生成描述。
712

被折叠的 条评论
为什么被折叠?



