设计模式:3. 装饰者模式
可以这么通俗易懂推荐各位去阅读原书。为加深知识印象对书中内容进行梳理总结书中的案例均由Java实现而笔者本人目前主要使用C因此该文章通过C来描述案例。由于本人水平有限表达会有欠佳处若要深入理解设计模式还是推荐读者能够阅读原书。这次我们要为奶茶店设计一个订单系统用户选择产品后能够正确显示价格。根据面向对象的设计思想我们设计了一个奶茶抽象类。class MilkTea { public: virtual std::string getDescription() { return m_description; } virtual float cost() 0; // 返回奶茶的价格 protected: std::string m_description; // 奶茶的描述 };具体的奶茶会实现这个接口class OriginalMilkTea: public MilkTea { // 原味奶茶 public: float cost() override { return 8.0; } };class BlackTeaMilkTea: public MilkTea { // 红茶 public: float cost() override { return 10.0; } };这样看起来不错不过用户在选择奶茶的时候可以选择添加小料那么如何根据用户选择的小料计给出实际价格呢。总不能将增加了小料的奶茶视作一个全新的类型吧像OriginalMilkTeaWithBubble(加了珍珠的原味奶茶)或OriginalMilkTeaWithPudding加了布丁的原味奶茶。如果这么设计需要创建的类太多太多了明显不可行。那么如果将小料作为奶茶的成员变量来处理呢像这样。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; };在计算价格时就需要增加一系列的判断。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*指针用来接收并包裹被装饰的奶茶本体。// 抽象装饰器所有配料的父类 class CondimentDecorator: public MilkTea { protected: MilkTea* m_milkTea; // 持有被装饰对象的引用 public: explicit CondimentDecorator(MilkTea* milkTea) : m_milkTea(milkTea) {} virtual ~CondimentDecorator() default; };class Bubble: public CondimentDecorator { public: explicit Bubble(MilkTea* milkTea): CondimentDecorator(milkTea){} float cost() override { return 2.0 m_milkTea-cost(); } };这样就可以不断地进行装饰得到需要的奶茶了。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: explicit Bubble(MilkTea* milkTea): CondimentDecorator(milkTea){} float cost() override { updateReceiptInfo(); // 在小票中增加小料的信息 return 2.0 m_milkTea-cost(); } };