设计模式之装饰模式

1 核心定义

装饰模式是一种结构型设计模式,它允许在不改变现有对象结构的情况下,动态地给该对象添加一些额外的职责。装饰模式通过创建包装对象(装饰器)来包裹真实对象,提供了比继承更有弹性的替代方案。

2 核心思想

继承的替代方案:提供比继承更灵活的扩展方式

开闭原则:对扩展开放,对修改封闭

动态性:可以在运行时动态添加或撤销功能

透明性:装饰对象和真实对象具有相同的接口

3 结构组成

Component(组件接口):定义对象接口,可动态添加职责

ConcreteComponent(具体组件):实现组件接口的具体对象

Decorator(装饰器抽象类):持有一个组件引用,实现组件接口

ConcreteDecorator(具体装饰器):向组件添加具体职责

4 应用场景举例

以咖啡订单系统为例,假设我们需要计算不同咖啡饮品的价格和描述:

基础咖啡:浓缩咖啡、美式咖啡

配料:牛奶、摩卡、奶油等

顾客可以自由组合基础咖啡和配料

5 UML

6 c++ 代码实现

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 抽象组件:咖啡接口
class Coffee {
public:
    virtual ~Coffee() = default;
    virtual string getDescription() const = 0;
    virtual double cost() const = 0;
};

// 具体组件:浓缩咖啡
class Espresso : public Coffee {
public:
    string getDescription() const override {
        return "浓缩咖啡";
    }
    
    double cost() const override {
        return 25.0;  // 基础价格25元
    }
};

// 具体组件:美式咖啡
class Americano : public Coffee {
public:
    string getDescription() const override {
        return "美式咖啡";
    }
    
    double cost() const override {
        return 20.0;  // 基础价格20元
    }
};

// 抽象装饰器:配料装饰器
class CoffeeDecorator : public Coffee {
protected:
    Coffee* coffee;  // 持有的被装饰对象
    
public:
    CoffeeDecorator(Coffee* c) : coffee(c) {}
    
    string getDescription() const override {
        return coffee->getDescription();
    }
    
    double cost() const override {
        return coffee->cost();
    }
    
    virtual ~CoffeeDecorator() {
        delete coffee;
    }
};

// 具体装饰器:牛奶
class Milk : public CoffeeDecorator {
public:
    Milk(Coffee* c) : CoffeeDecorator(c) {}
    
    string getDescription() const override {
        return coffee->getDescription() + " + 牛奶";
    }
    
    double cost() const override {
        return coffee->cost() + 5.0;  // 牛奶加5元
    }
};

// 具体装饰器:摩卡
class Mocha : public CoffeeDecorator {
public:
    Mocha(Coffee* c) : CoffeeDecorator(c) {}
    
    string getDescription() const override {
        return coffee->getDescription() + " + 摩卡";
    }
    
    double cost() const override {
        return coffee->cost() + 8.0;  // 摩卡加8元
    }
};

// 具体装饰器:奶油
class Whip : public CoffeeDecorator {
public:
    Whip(Coffee* c) : CoffeeDecorator(c) {}
    
    string getDescription() const override {
        return coffee->getDescription() + " + 奶油";
    }
    
    double cost() const override {
        return coffee->cost() + 6.0;  // 奶油加6元
    }
};

// 客户端代码
int main() {
    // 点一杯浓缩咖啡加牛奶和摩卡
    Coffee* coffee = new Espresso();
    coffee = new Milk(coffee);
    coffee = new Mocha(coffee);
    
    cout << "订单: " << coffee->getDescription() << endl;
    cout << "价格: " << coffee->cost() << "元" << endl;
    
    delete coffee;
    return 0;
}

7 总结

如果不使用装饰模式,可能会有以下几种实现方式

方式1:使用继承(每个组合创建一个子类)

cpp 复制代码
// 问题:类爆炸
class EspressoWithMilk : public Espresso { /* ... */ };
class EspressoWithMocha : public Espresso { /* ... */ };
class EspressoWithMilkAndMocha : public Espresso { /* ... */ };
class AmericanoWithMilk : public Americano { /* ... */ };
// ... 需要为每种组合创建类,如果有3种基础咖啡和5种配料,就需要3*2^5=96个类!

方式2:使用属性标志

cpp 复制代码
class Coffee {
private:
    bool hasMilk;
    bool hasMocha;
    bool hasWhip;
    // ... 需要为每种配料添加标志
    
public:
    double cost() {
        double total = basePrice;
        if (hasMilk) total += 5;
        if (hasMocha) total += 8;
        if (hasWhip) total += 6;
        return total;
    }
};

主要问题:

1 违反开闭原则:每次添加新的配料都需要修改Coffee类

2 代码臃肿:类的职责过重,包含所有配料的逻辑

3 维护困难:添加新配料需要修改现有代码,容易引入bug

4 灵活性差:无法动态改变配料组合,配料的组合在编译时就固定了

5 代码重复:如果配料的价格计算逻辑复杂,不同咖啡类中会有重复代码

装饰模式的优势

1 灵活扩展:可以在不修改现有代码的情况下添加新功能

2 组合自由:可以在运行时自由组合不同的装饰器

3 符合单一职责:每个装饰器只负责一个特定的功能

4 避免类爆炸:不需要为每个组合创建新的子类

5 可撤销:可以动态地添加或移除装饰器

装饰模式通过将复杂的功能分解为简单的小功能,并通过组合的方式实现复杂功能,是一种非常灵活的代码组织方式。

相关推荐
bigbread2 小时前
迭代器模式-在区域健康档案导出中的实战应用
设计模式
装不满的克莱因瓶4 小时前
设计模式之策略模式核心概念和应用场景
java·后端·设计模式·架构·系统架构·策略模式
是2的10次方啊18 小时前
设计模式与生活㉑:一句“建议复查”,是好几句话拼出来的
设计模式
杯子不吃紫菜2 天前
LangGraph 体系实战(下):中断恢复、流式、子图与设计模式
单片机·嵌入式硬件·设计模式
是2的10次方啊2 天前
设计模式与生活⑳:楼里的事都找前台,你就再没见过师傅
设计模式
是2的10次方啊3 天前
设计模式与生活⑲:投影连不上,别急着换投影仪
设计模式
cjz38994 天前
规则树:把一百行if-else拆成一棵树
设计模式
Adellle4 天前
设计模式指南 (Java 版) ㊁
java·开发语言·设计模式
是2的10次方啊4 天前
设计模式与生活⑱:工资还没焐热,账单已经凑成一摞
设计模式
百思不得小李5 天前
一个复杂的动态表单是如何实现的
前端·设计模式