C++设计模式之结构型模式:装饰器模式(Decorator)

装饰器模式(Decorator)是结构型设计模式的一种,它通过动态地给对象添加额外功能,同时不改变其原有的结构和接口。这种模式相比继承更灵活,能够在运行时灵活组合不同功能,避免了类爆炸问题。

一、核心思想与角色

装饰器模式的核心是"功能扩展与原对象解耦",通过包裹(wrap)原对象实现功能叠加。其核心角色如下:

角色名称 核心职责
抽象组件(Component) 定义被装饰对象和装饰器的共同接口,是所有组件的抽象基类。
具体组件(ConcreteComponent) 实现抽象组件接口,是被装饰的原始对象(基础功能)。
抽象装饰器(Decorator) 继承抽象组件,内部包含一个抽象组件的引用,实现与抽象组件一致的接口。
具体装饰器(ConcreteDecorator) 继承抽象装饰器,添加具体的额外功能,在调用原组件方法前后执行附加逻辑。

核心思想:用"组合"代替"继承"实现功能扩展,每个装饰器专注于单一功能,通过嵌套组合实现多功能叠加,且扩展过程对客户端透明。

二、实现示例(咖啡订单系统)

假设我们需要设计一个咖啡订单系统,基础咖啡(如美式咖啡)可添加多种配料(牛奶、糖、巧克力等),每种配料会增加价格和描述。使用装饰器模式可灵活组合配料:

cpp 复制代码
#include <iostream>
#include <string>

// 1. 抽象组件(Component):咖啡
class Coffee {
public:
    virtual std::string getDescription() const = 0; // 获取描述
    virtual double cost() const = 0; // 获取价格
    virtual ~Coffee() = default;
};

// 2. 具体组件(ConcreteComponent):基础咖啡(美式咖啡)
class Espresso : public Coffee {
public:
    std::string getDescription() const override {
        return "美式咖啡";
    }

    double cost() const override {
        return 25.0; // 基础价格
    }
};

// 3. 抽象装饰器(Decorator):配料装饰器
class CoffeeDecorator : public Coffee {
protected:
    Coffee* coffee; // 被装饰的咖啡对象(组合关系)

public:
    // 构造函数:接收被装饰的咖啡
    CoffeeDecorator(Coffee* c) : coffee(c) {}

    // 析构函数:释放被装饰的对象(若为装饰器链,需注意释放顺序)
    ~CoffeeDecorator() override {
        delete coffee;
    }
};

// 4. 具体装饰器1:牛奶
class Milk : public CoffeeDecorator {
public:
    Milk(Coffee* c) : CoffeeDecorator(c) {}

    // 扩展描述:添加"加牛奶"
    std::string getDescription() const override {
        return coffee->getDescription() + ",加牛奶";
    }

    // 扩展价格:+5元
    double cost() const override {
        return coffee->cost() + 5.0;
    }
};

// 4. 具体装饰器2:糖
class Sugar : public CoffeeDecorator {
public:
    Sugar(Coffee* c) : CoffeeDecorator(c) {}

    std::string getDescription() const override {
        return coffee->getDescription() + ",加糖";
    }

    double cost() const override {
        return coffee->cost() + 2.0;
    }
};

// 4. 具体装饰器3:巧克力
class Chocolate : public CoffeeDecorator {
public:
    Chocolate(Coffee* c) : CoffeeDecorator(c) {}

    std::string getDescription() const override {
        return coffee->getDescription() + ",加巧克力";
    }

    double cost() const override {
        return coffee->cost() + 8.0;
    }
};

// 客户端代码:组合不同配料
int main() {
    // 1. 基础咖啡(无配料)
    Coffee* espresso = new Espresso();
    std::cout << "订单1:" << espresso->getDescription() 
              << ",价格:" << espresso->cost() << "元" << std::endl;
    delete espresso;

    // 2. 美式咖啡 + 牛奶
    Coffee* milkCoffee = new Milk(new Espresso());
    std::cout << "订单2:" << milkCoffee->getDescription() 
              << ",价格:" << milkCoffee->cost() << "元" << std::endl;
    delete milkCoffee;

    // 3. 美式咖啡 + 牛奶 + 糖 + 巧克力(多层装饰)
    Coffee* deluxeCoffee = new Chocolate(
        new Sugar(
            new Milk(
                new Espresso()
            )
        )
    );
    std::cout << "订单3:" << deluxeCoffee->getDescription() 
              << ",价格:" << deluxeCoffee->cost() << "元" << std::endl;
    delete deluxeCoffee;

    return 0;
}

三、代码解析

  1. 抽象组件(Coffee) :定义了所有咖啡(包括基础咖啡和装饰后的咖啡)的通用接口(getDescription()cost())。

  2. 具体组件(Espresso):实现了基础咖啡的功能,提供默认描述(美式咖啡)和价格(25元),是被装饰的原始对象。

  3. 抽象装饰器(CoffeeDecorator) :继承Coffee接口,内部持有一个Coffee*指针(被装饰对象),实现了装饰器的基础结构。其析构函数负责释放被装饰对象,避免内存泄漏。

  4. 具体装饰器

    • 每个装饰器(MilkSugar等)专注于单一功能(添加某种配料)。
    • getDescription()中扩展原咖啡的描述,在cost()中增加配料价格,同时通过coffee指针调用原对象的方法,实现"功能叠加"。
  5. 客户端使用 :通过嵌套构造函数组合不同装饰器(如Chocolate(Sugar(Milk(Espresso())))),动态创建具有多种功能的对象,且无需修改原有类。

四、核心优势与适用场景

优势
  1. 灵活扩展:可在运行时动态添加/移除功能,比继承更灵活(继承是编译时静态扩展)。
  2. 单一职责:每个装饰器只负责一种功能,符合单一职责原则,便于维护。
  3. 避免类爆炸 :若用继承实现"基础咖啡+3种配料"的组合,需2^3-1=7个类,而装饰器模式只需3个装饰器类,组合更灵活。
  4. 透明扩展:客户端使用装饰后的对象与使用原始对象完全一致(接口相同),无需区分。
适用场景
  1. 需动态为对象添加/移除功能(如动态添加日志、缓存、权限校验等)。
  2. 功能可组合且组合方式多样(如咖啡配料、UI组件的多层样式)。
  3. 不适合用继承扩展(如类层次过深、功能组合过多)。

五、与其他模式的区别

模式 核心差异点
装饰器模式 动态添加功能,不改变接口,装饰器与被装饰者实现同一接口,强调"功能扩展"。
适配器模式 转换接口使不兼容的类协作,强调"接口适配",不关注功能扩展。
代理模式 控制对对象的访问(如延迟加载、权限控制),不添加新功能,强调"访问控制"。
组合模式 构建树形结构表示"部分-整体"关系,统一单个对象和组合对象的使用。

六、实践建议

  1. 保持接口一致:装饰器必须与被装饰对象实现相同的接口,确保客户端透明使用。
  2. 最小职责原则:每个装饰器只实现一个额外功能,避免设计复杂的"万能装饰器"。
  3. 注意内存管理:装饰器链的释放需谨慎(如示例中装饰器析构函数自动释放被装饰对象,避免二次删除)。
  4. 避免过度装饰:过多的装饰器嵌套可能导致代码可读性下降,必要时可封装常用组合为便捷方法。

装饰器模式的核心价值在于"在不改变原有对象的前提下,动态、灵活地扩展功能"。它通过组合而非继承的方式,解决了功能扩展导致的类爆炸问题,是实现"开放-封闭原则"的经典方案,特别适合需要灵活组合多种功能的场景。

相关推荐
_OP_CHEN3 小时前
C++:(四)类和对象(中)—— 构造、析构与重载
开发语言·c++·类和对象·构造函数·析构函数·运算符重载·日期类
不搞学术柒柒3 小时前
设计模式-行为型设计模式(针对对象之间的交互)
python·设计模式·交互
Madison-No74 小时前
【C++】string类的常见接口的使用
开发语言·c++·算法
一只雄牧慕4 小时前
【C++】哈希表
开发语言·数据结构·c++·散列表
代码村新手4 小时前
C语言-操作符
开发语言·c++
老赵的博客4 小时前
c++ 之多态虚函数表
java·jvm·c++
liu****4 小时前
负载均衡式的在线OJ项目编写(四)
运维·c++·负载均衡·个人开发
@卞5 小时前
第十六届蓝桥杯软件赛C组省赛C++题解(京津冀)
c语言·c++·蓝桥杯
Hilaku5 小时前
前端的设计模式?我觉得90%都是在过度设计!
前端·javascript·设计模式