【C++设计模式】第五篇:装饰器模式

发布于:2025-09-14 ⋅ 阅读:(15) ⋅ 点赞:(0)

C++设计模式系列文章目录

【C++设计模式】第一篇 C++单例模式–懒汉与饿汉以及线程安全

【C++设计模式】第二篇:策略模式(Strategy)–从基本介绍,内部原理、应用场景、使用方法,常见问题和解决方案进行深度解析

【C++设计模式】第三篇:观察者模式(别名:发布-订阅模式、模型-视图模式、源-监听器模式)

【C++设计模式】第五篇:装饰器模式

原文链接:https://blog.csdn.net/AAADiao/article/details/148846165


装饰模式(Decorator Pattern)是一种【结构型】设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构。这种模式通过创建一个装饰器类,来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。装饰模式在不使用继承的情况下,实现了对象功能的动态扩展。

一、模式核心概念与结构

装饰模式包含四个核心角色:

抽象组件(Component):定义对象的接口,可以给这些对象动态地添加职责。
具体组件(Concrete Component):实现抽象组件接口,定义具体的对象,装饰器可以给它增加额外的职责。
抽象装饰器(Decorator):继承自抽象组件,并持有一个抽象组件的引用,用于装饰具体组件。
具体装饰器(Concrete Decorator):实现抽象装饰器的方法,在调用具体组件方法的前后,添加额外的功能。

二、C++ 实现示例:咖啡与配料的装饰

以下是一个经典的装饰模式示例,演示如何动态添加咖啡的配料:

#include <iostream>
#include <string>
#include <memory>

// 抽象组件:饮料
class Beverage {
public:
    virtual ~Beverage() {}
    virtual std::string getDescription() const = 0;
    virtual double cost() const = 0;
};

// 具体组件:浓缩咖啡
class Espresso : public Beverage {
public:
    std::string getDescription() const override {
        return "Espresso";
    }
    
    double cost() const override {
        return 1.99;
    }
};

// 具体组件:黑咖啡
class DarkRoast : public Beverage {
public:
    std::string getDescription() const override {
        return "Dark Roast Coffee";
    }
    
    double cost() const override {
        return 0.99;
    }
};

// 抽象装饰器:配料
class CondimentDecorator : public Beverage {
protected:
    std::shared_ptr<Beverage> beverage;  // 持有被装饰对象的引用

public:
    CondimentDecorator(std::shared_ptr<Beverage> b) : beverage(b) {}
};

// 具体装饰器:牛奶
class Milk : public CondimentDecorator {
public:
    Milk(std::shared_ptr<Beverage> b) : CondimentDecorator(b) {}
    
    std::string getDescription() const override {
        return beverage->getDescription() + ", Milk";
    }
    
    double cost() const override {
        return beverage->cost() + 0.30;
    }
};

// 具体装饰器:摩卡
class Mocha : public CondimentDecorator {
public:
    Mocha(std::shared_ptr<Beverage> b) : CondimentDecorator(b) {}
    
    std::string getDescription() const override {
        return beverage->getDescription() + ", Mocha";
    }
    
    double cost() const override {
        return beverage->cost() + 0.45;
    }
};

// 客户端代码
int main() {
    // 纯浓缩咖啡
    std::shared_ptr<Beverage> beverage = std::make_shared<Espresso>();
    std::cout << beverage->getDescription() << " $" << beverage->cost() << std::endl;
    
    // 加牛奶的黑咖啡
    std::shared_ptr<Beverage> beverage2 = std::make_shared<DarkRoast>();
    beverage2 = std::make_shared<Milk>(beverage2);
    std::cout << beverage2->getDescription() << " $" << beverage2->cost() << std::endl;
    
    // 加双份摩卡的浓缩咖啡
    std::shared_ptr<Beverage> beverage3 = std::make_shared<Espresso>();
    beverage3 = std::make_shared<Mocha>(beverage3);
    beverage3 = std::make_shared<Mocha>(beverage3);
    std::cout << beverage3->getDescription() << " $" << beverage3->cost() << std::endl;
    
    return 0;
}

三、装饰模式与继承的对比

传统继承方式的局限性:

  • 功能扩展通过创建子类实现,导致类数量爆炸。
  • 功能扩展是静态的,编译时确定,无法在运行时动态调整。

装饰模式的优势:

  • 动态组合对象功能,运行时灵活扩展。
  • 避免继承导致的类层次过深问题。
  • 符合开闭原则:无需修改原有代码,即可添加新的装饰器。

四、应用场景

  1. 动态添加功能:当需要给对象动态添加功能,且不影响其他对象时,例如:
  • 图形界面组件的边框、滚动条、阴影效果。
  • 网络请求的加密、压缩、缓存功能。
  1. 替代多重继承:当使用继承会导致类爆炸时,例如:
  • 文件流的缓冲、加密、压缩处理。
  • 游戏角色的装备、技能组合。
  1. 功能增强链:当需要按顺序执行多个功能增强时,例如:
  • 日志处理(过滤、格式化、存储)。
  • HTTP 请求处理(身份验证、参数解析、权限检查)。

五、C++ 实现注意事项

  1. 接口一致性:

装饰器必须实现与被装饰对象相同的接口(继承同一抽象类)。
确保装饰器不改变接口签名,只增强功能。
2. 智能指针管理:

// 使用智能指针避免内存泄漏
std::shared_ptr<Beverage> beverage = std::make_shared<Espresso>();
beverage = std::make_shared<Mocha>(beverage);

  1. 初始化顺序:
  • 装饰器的初始化顺序可能影响最终结果,需谨慎设计。
  1. 避免重复装饰:
  • 某些场景需防止对同一对象重复应用相同装饰器。

六、装饰模式与其他设计模式的关系

  1. 适配器模式:适配器模式详解
  • 适配器模式改变对象接口,装饰模式增强对象功能。
  • 适配器模式是 “适配”,装饰模式是 “增强”。
  1. 代理模式:
  • 代理模式控制对象访问,装饰模式增加对象功能。
  • 代理模式的重点是访问控制,装饰模式的重点是功能扩展。
  1. 建造者模式:建造者模式详解
  • 建造者模式分步构建复杂对象,装饰模式动态增强对象。
  • 建造者模式关注对象构建过程,装饰模式关注对象运行时功能。

七、实战案例:网络请求处理链

以下是一个网络请求处理链的装饰模式实现:

#include <iostream>
#include <string>
#include <memory>

// 抽象组件:请求处理器
class RequestHandler {
public:
    virtual ~RequestHandler() {}
    virtual void handleRequest(const std::string& request) const = 0;
};

// 具体组件:基础请求处理器
class BaseRequestHandler : public RequestHandler {
public:
    void handleRequest(const std::string& request) const override {
        std::cout << "Base handler processing request: " << request << std::endl;
    }
};

// 抽象装饰器:请求处理器装饰器
class RequestHandlerDecorator : public RequestHandler {
protected:
    std::shared_ptr<RequestHandler> handler;

public:
    RequestHandlerDecorator(std::shared_ptr<RequestHandler> h) : handler(h) {}
};

// 具体装饰器:日志记录
class LoggingDecorator : public RequestHandlerDecorator {
public:
    LoggingDecorator(std::shared_ptr<RequestHandler> h) : RequestHandlerDecorator(h) {}
    
    void handleRequest(const std::string& request) const override {
        std::cout << "Logging: Request received - " << request << std::endl;
        handler->handleRequest(request);
        std::cout << "Logging: Request processed" << std::endl;
    }
};

// 具体装饰器:权限检查
class AuthDecorator : public RequestHandlerDecorator {
public:
    AuthDecorator(std::shared_ptr<RequestHandler> h) : RequestHandlerDecorator(h) {}
    
    void handleRequest(const std::string& request) const override {
        std::cout << "Auth: Checking permissions..." << std::endl;
        handler->handleRequest(request);
        std::cout << "Auth: Permissions checked" << std::endl;
    }
};

// 客户端代码
int main() {
    // 创建基础处理器
    std::shared_ptr<RequestHandler> baseHandler = std::make_shared<BaseRequestHandler>();
    
    // 添加日志和权限装饰
    std::shared_ptr<RequestHandler> authHandler = std::make_shared<AuthDecorator>(baseHandler);
    std::shared_ptr<RequestHandler> loggingAuthHandler = std::make_shared<LoggingDecorator>(authHandler);
    
    // 处理请求
    loggingAuthHandler->handleRequest("GET /api/data");
    
    return 0;
}

八、优缺点分析

优点:

  • 灵活扩展:可以在运行时动态添加或删除功能。
  • 单一职责:每个装饰器专注于一个特定功能,符合单一职责原则。
  • 开闭原则:无需修改现有代码即可添加新装饰器。

缺点:

  • 复杂性增加:多层装饰会导致系统复杂,调试困难。
  • 对象嵌套:过多装饰器可能导致对象嵌套过深,影响性能。
  • 接口一致性:装饰器必须严格遵循组件接口,否则可能破坏系统。

九、C++ 标准库中的装饰模式应用

  1. 输入 / 输出流(iostream):
  • std::basic_ios是抽象组件,std::ifstream、std::ofstream是具体组件。
  • std::ios_base::iword和std::ios_base::pword可用于动态添加流的属性。
  1. 智能指针:
  • std::shared_ptr和std::unique_ptr可视为对原始指针的装饰器,添加了内存管理功能。
  1. STL 迭代器适配器:
  • std::reverse_iterator、std::insert_iterator等是对基础迭代器的装饰。

装饰模式是 C++ 中实现对象功能动态扩展的重要工具,通过合理使用装饰器,可以构建出灵活、可维护的软件系统,同时避免继承带来的局限性。

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

原文链接:https://blog.csdn.net/janeqi1987/article/details/103946459

一、模式动机

比如,给自家宠物小狗的画框需要不同的颜色等。
在这里插入图片描述
一般有两种方式可以实现给一个类或对象增加行为:

  • 继承机制,使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。

  • 关联机制,即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)。

    装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。这就是装饰模式的模式动机。 装饰器模式提供了改变子类的灵活方案。装饰器模式在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。当用于一组子类时,装饰器模式更加有用。如果你拥有一族子类(从一个父类派生而来),你需要在与子类独立使用情况下添加额外的特性,你可以使用装饰器模式,以避免代码重复和具体子类数量的增加。

二、模式定义

装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。

三、ULM图:

在这里插入图片描述

装饰模式包含如下角色:

Component抽象构件: 定义我们最核心的对象的一个接口或者是抽象类,即最原始的对象(必然有一个最基本、最核心、最原始的接口或抽象类充当Component抽象构件)

ConcreteComponent 具体构件:定义一个要被装饰器装饰的对象,Component 的具体实现,即被装饰者。

抽象Decorator装饰角色: 一般是一个抽象类(便于根据不同的装饰逻辑去实现其子类)且一定会持有一个private变量指向Component抽象构件的引用(它里面不一定只有抽象的方法呀)维护对组件对象和其子类组件的引用,但是要注意的是所谓装饰者仅仅是发挥锦上添花的作用,核心的本质功能还是应该由构件提供,相当于是把在构件的基础上进行升级,所需要继承构件。

具体装饰器角色(ConcreteDecorator): 通常是一群子装饰器组合共同为组件添加新的功能。

四、装饰器模式的优点和缺点及可用场景

1、装饰器模式的优点

虽然装饰模式与继承关系的都是为了要在基类的基础上扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性,装饰模式允许系统动态决定“贴上”或者除掉一个“装饰”而不需要改变代码的层次;而继承关系是静态的,如果想要增加或者减少一个功能只能通过增加继承层次或降低层次。

使用者可以随时根据具体的业务逻辑灵活组织所需要的功能,通过使用不同的具体装饰类以及这些装饰类的组合即可

装饰者类可以在被装饰者的行为前面或后面加上自己的行为,甚至取代被装饰者的行为

装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型

通过使用装饰器模式,我们可以实现不改变原有代码,开放现有代码的方式来实现更多的功能。

装饰类和被装饰类是可以独立发展且不会相互耦合。即Component类无须知
道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具
体的构件。

2、装饰器模式的缺点

使用装饰模式会产生比使用继承关系更多的对象和复杂的逻辑,降低了可读性,因此,合理使用装饰类,控制其数量,以便降低系统的复杂度

3、装饰器模式的适用场景及注意事项

在不影响其他对象的情况下,需要以动态、透明的方式给随时给被装饰对象添加或者移除某些功能时而不影响原有逻辑,想通过灵活组合对象的方式动态地改变被装饰对象的行为
需要扩展一个类的功能或给一个类增加或随时移除附加功能。
当需要以多层次的继承关系才能实现扩充时,可以考虑装饰器模式。
需要为某一类型的兄弟类进行改装或加装功能,首选装饰模式。
装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。
装饰对象包含一个真实对象的引用(reference)
装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。
装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。

4、 和建造者模式的区别

建造者模式要求建造的过程必须是稳定的,而装饰模式的建造过程是不稳定的,可以有各种各样的组合方式。


网站公告

今日签到

点亮在社区的每一天
去签到