笔者目前正在阅读《Head First设计模式》,首先对该书发表我的个人观点,非常有趣生动的一本书,在阅读这本书之前,我一直对设计模式抱有畏惧的心理,阅读该书之后,才发现设计模式可以这么通俗易懂,推荐各位去阅读原书。为加深知识印象,对书中内容进行梳理总结,书中的案例均由Java实现,而笔者本人目前主要使用C++,因此该文章通过C++来描述案例。由于本人水平有限,表达会有欠佳处,若要深入理解设计模式,还是推荐读者能够阅读原书。
这次我们要为奶茶店设计一个订单系统,用户选择产品后能够正确显示价格。根据面向对象的设计思想,我们设计了一个奶茶抽象类。
c++
class MilkTea
{
public:
virtual std::string getDescription()
{
return m_description;
}
virtual float cost() = 0; // 返回奶茶的价格
protected:
std::string m_description; // 奶茶的描述
};
具体的奶茶会实现这个接口
c++
class OriginalMilkTea: public MilkTea
{
// 原味奶茶
public:
float cost() override
{
return 8.0;
}
};
c++
class BlackTeaMilkTea: public MilkTea
{
// 红茶
public:
float cost() override
{
return 10.0;
}
};
这样看起来不错,不过用户在选择奶茶的时候,可以选择添加小料,那么如何根据用户选择的小料计给出实际价格呢。总不能将增加了小料的奶茶视作一个全新的类型吧,像OriginalMilkTeaWithBubble(加了珍珠的原味奶茶)或OriginalMilkTeaWithPudding(加了布丁的原味奶茶)。如果这么设计,需要创建的类太多太多了,明显不可行。
那么,如果将小料作为奶茶的成员变量来处理呢?像这样。
c++
class MilkTea
{
public:
virtual std::string getDescription()
{
return m_description;
}
virtual float cost() = 0; // 返回奶茶的价格
protected:
std::string m_description; // 奶茶的描述
// 通过bool判断有没有增加小料
bool m_hasBubble;
bool m_hasPudding;
};
在计算价格时,就需要增加一系列的判断。
c++
class OriginalMilkTea: public MilkTea
{
// 原味奶茶
public:
float cost() override
{
float price = 8.0;
if (m_hasBubble)
{
price += 2.0;
}
if (m_hasPudding)
{
price += 3.0;
}
return price;
}
};
如果这么写,会有新的问题出现。当增添新的小料,或者小料的价格发生改变时,还需要修改奶茶的代码;又或者用户想要点两份布丁呢?这显然违背了开闭原则。
那么,有没有一种办法,能够动态地"装饰"奶茶(添加小料),且装饰后的奶茶仍然保持奶茶的类型(因为我们依然需要通过统一的 cost() 函数获取价格),同时又能叠加自己的行为(改变价格和描述)?这正是装饰者模式的用武之地。
为了实现这种层层包装的效果,我们需要设计一个抽象装饰器(CondimentDecorator)。它有两个关键职责:
- 它必须继承自
MilkTea,因为它本身也要表现得像一杯奶茶,以便能被继续装饰或被外部调用。 - 它内部需要持有一个
MilkTea*指针,用来接收并包裹被装饰的奶茶本体。
c++
// 抽象装饰器:所有配料的父类
class CondimentDecorator: public MilkTea
{
protected:
MilkTea* m_milkTea; // 持有被装饰对象的引用
public:
explicit CondimentDecorator(MilkTea* milkTea) : m_milkTea(milkTea) {}
virtual ~CondimentDecorator() = default;
};
c++
class Bubble: public CondimentDecorator
{
public:
explicit Bubble(MilkTea* milkTea): CondimentDecorator(milkTea){}
float cost() override
{
return 2.0 + m_milkTea->cost();
}
};
这样,就可以不断地进行装饰,得到需要的奶茶了。
c++
int main()
{
MilkTea* milk1 = new OriginalMilkTea();
MilkTea* milk2 = new Bubble(milk1);
MilkTea* milk3 = new Pudding(milk2);
std::cout << "final price: " << milk3->cost() << std::endl;
return 0;
}
你可能会有疑问:要计算不同加料的奶茶价格,我直接在奶茶类里创建一个容器(如 vector)来存储小料,计算价格时遍历容器累加不就可以了吗?
当然可以!但这仅仅解决了"价格累加"的问题。装饰者模式最核心的优势在于:它允许我们在委托给被装饰者的行为之前或之后,灵活地附加自己的额外行为。 比如在计算价格之前,我还可以更新小票上的信息。
class Bubble: public CondimentDecorator
{
public:
Bubble(MilkTea* milkTea):m_milkTea(milkTea){}
float cost() override
{
updateReceiptInfo(); // 在小票中增加小料的信息
return 2.0 + m_milkTea->cost();
}
private:
MilkTea* m_milkTea;
};
最后,给出装饰者模式的定义:动态地将责任附加到对象上。想要扩展功能,装饰者提供有别于继承的另一种选择。