GoF设计模式(C++版)

GoF设计模式(C++版)

GoF设计模式,全称是 Gang of Four(四人组)设计模式,指的是由 Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides 四位作者(合称“GoF”)在1994年出版的经典著作《设计模式:可复用面向对象软件的基础》中总结的 23种经典模式这23种模式是面向对象设计的基石,至今仍是架构设计和代码审查中的通用“词汇”。为了更直观地掌握,我将它们按三大类型展开,并特别标注了在C++现代实践中的差异。

1 创建型模式(5种):管对象怎么产生

解决对象创建过程种的耦合问题,将创建与使用分离。

模式名称 核心意图 C++现代实际提示
单例(Singleton) 确保一个类只有一个实例,并提供全局访问点。 C++11起可以使用static局部变量实现线程安全,需要注意禁用拷贝构造。
工厂方法(Factory Method) 父类定义创建对象的接口,让子类决定实例化哪个具体类。 常返回std::unique_prt<T>来明确所有权。
抽象工厂(Abstract Factory) 创建一系列相关或依赖的对象族,无需指定具体类。 常用于切换不同平台或数据库,配合依赖注入。
建造者(Builder) 分步骤构造复杂对象(如含有大量可选参数的类) 结合链式调用(return *this)非常优雅,可用于构建不可变对象。
原型(Prototype) 通过拷贝现有对象来创建新对象,而不是通过构造函数。 定义虚clone()方法,返回std::unique_prt,利用拷贝构造实现。

2 结构型模式(7种):管类/对象如何搭建

解决类或对象的组合关系,确保“大结构”灵活高效。

模式名称 核心意图 C++现代实践提示
适配器 (Adapter) 将一个类的接口转换成客户期望的另一个接口,解决接口不兼容。 可用 std::function 或 lambda 包装不同函数,实现更轻量的适配。
桥接 (Bridge) 将抽象部分与实现部分分离,使它们可以独立变化。 常用于跨平台GUI或驱动开发,避免继承爆炸。
组合 (Composite) 将对象组合成树形结构以表示“部分-整体”的层次结构。 容器和叶子节点定义统一接口,需注意生命周期管理(用 shared_ptr)。
装饰器 (Decorator) 动态地给对象添加额外的职责,比生成子类更灵活。 C++中常通过继承基类并持有基类指针实现,注意析构函数设为虚函数。
外观 (Facade) 为子系统中的一组接口提供一个统一的简化入口。 最常用的模式之一,将复杂库封装为一个简洁的类。
享元 (Flyweight) 共享大量细粒度对象,节省内存(如游戏中的粒子、字体)。 配合对象池和const成员使用,必须确保对象不可变。
代理 (Proxy) 为另一个对象提供替身或占位符以控制对它的访问(延迟加载、权限校验)。 可使用 std::shared_ptr 实现简单的智能指针代理。

 

3 行为型模式(11种):管对象间如何交互

解决对象间如何协作、分配职责已经算法的封装。

模式名称 核心意图 C++现代实践提示
策略 (Strategy) 定义一系列算法,封装起来,使它们可以互相替换。 强烈建议用 std::function + Lambda 替代继承,代码极简。
观察者 (Observer) 定义对象间的一对多依赖,当一个对象改变状态,所有依赖者得到通知。 C++中注意生命周期,常用 std::weak_ptr 防止悬垂指针,或使用信号槽库。
模板方法 (Template Method) 在父类中定义算法骨架,将某些步骤延迟到子类实现。 C++中直接用虚函数实现,注意使用 final 防止过度重写。
状态 (State) 允许对象在内部状态改变时改变其行为(看起来像换了类)。 与策略模式结构极其相似,区别在于状态由上下文内部驱动,策略由外部选择。
责任链 (Chain of Responsibility) 将请求沿链传递,直到有一个处理者处理它。 可用 std::vector 存储处理器,链式遍历。
命令 (Command) 将请求封装为对象,从而支持撤销、排队、日志等操作。 配合 std::function 可轻松实现,常用于实现撤销(Undo)栈。
迭代器 (Iterator) 提供一种方法顺序访问聚合对象中的元素,而不暴露内部表示。 STL容器已内置,只需为自定义容器编写迭代器即可。
中介者 (Mediator) 用一个中介对象来封装一系列对象之间的交互,降低网状耦合。 适合UI控件间的交互,将多对多变成一对多。
备忘录 (Memento) 在不破坏封装的前提下,捕获并外部化一个对象的内部状态(快照)。 常配合序列化(如JSON)实现保存/加载功能。
解释器 (Interpreter) 定义语言的文法,并解释该语言中的句子(如正则表达式、DSL)。 使用频率极低,复杂时建议用现成的解析库(如Boost.Spirit)。
访问者 (Visitor) 表示一个作用于某对象结构中的各元素的操作,在不改变元素类的前提下定义新操作。 使用频率中等,C++中需处理双重分派,配合 std::variant 和 std::visit 可实现更现代的变体。

 

4 特别提醒:GoF模式在C++种的演进

四位大师写书时用的是90年代的C++(当时还没有C++11)。在现代C++(C++11\14\17\20)中,很多模式被语言特性“降维打击”了:

  • 策略模式:被 std::function + Lambda 取代了类继承。
  • 命令模式:被 std::packaged_task 和线程池结合。
  • 迭代器模式:直接融入STL,用范围 for 循环即可。
  • 单例模式:被 static 局部变量的线程安全初始化取代。

 

5 策略模式

策略模式是一种行为型设计模式,它定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户,从而实现了开闭原则——对扩展开放,对修改关闭。

在 C++ 中,策略模式有多种实现方式,从经典的面向对象接口继承,到现代 C++ 的函数式风格(std::function、Lambda)。

下面从概念、结构、实现、示例、优缺点和应用场景等方面详细讲解。

5.1 结构

策略模式包含三个核心角色:

  • Strategy(抽象策略)定义所有支持的算法的公共接口。通常是一个抽象基类,包含一个纯虚函数。

  • ConcreteStrategy(具体策略)实现 Strategy 接口,提供具体的算法实现。

  • Context(上下文)持有一个对 Strategy 对象的引用,负责维护策略对象的生命周期,并委托给策略对象执行算法。上下文本身不关心具体策略,只通过抽象接口调用。

5.2 经典C++实现(基于继承和多态)

  • 定义抽象策略接口
#include <iostream>
#include <memory>

// 抽象策略:支付方式
class PaymentStrategy {
public:
    virtual ~PaymentStrategy() = default;
    virtual void pay(int amount) const = 0;
};
  • 具体策略类
// 具体策略:信用卡支付
class CreditCardPayment : public PaymentStrategy {
public:
    void pay(int amount) const override {
        std::cout << "Paid " << amount << " using Credit Card." << std::endl;
    }
};

// 具体策略:支付宝支付
class AlipayPayment : public PaymentStrategy {
public:
    void pay(int amount) const override {
        std::cout << "Paid " << amount << " using Alipay." << std::endl;
    }
};

// 具体策略:微信支付
class WechatPayment : public PaymentStrategy {
public:
    void pay(int amount) const override {
        std::cout << "Paid " << amount << " using WeChat Pay." << std::endl;
    }
};
  • 上下文类
// 上下文:订单类,包含支付策略
class Order {
private:
    std::unique_ptr<PaymentStrategy> strategy_;  // 持有策略对象

public:
    // 构造时注入策略(也可提供setter动态切换)
    explicit Order(std::unique_ptr<PaymentStrategy> strategy)
        : strategy_(std::move(strategy)) {}

    // 设置新策略(运行时切换)
    void setStrategy(std::unique_ptr<PaymentStrategy> strategy) {
        strategy_ = std::move(strategy);
    }

    void checkout(int amount) const {
        if (strategy_) {
            strategy_->pay(amount);
        } else {
            std::cout << "No payment strategy set!" << std::endl;
        }
    }
};
  • 客户端使用
int main() {
    // 创建订单,使用信用卡支付
    Order order(std::make_unique<CreditCardPayment>());
    order.checkout(100);

    // 切换策略为支付宝
    order.setStrategy(std::make_unique<AlipayPayment>());
    order.checkout(200);

    // 切换策略为微信
    order.setStrategy(std::make_unique<WechatPayment>());
    order.checkout(300);

    return 0;
}

输出:

Paid 100 using Credit Card.
Paid 200 using Alipay.
Paid 300 using WeChat Pay.

5.3 现代C++实现(使用std::function和Lambda)

在 C++11 及以后,我们可以利用 std::function 和 Lambda 表达式,将策略实现为函数对象,无需定义继承层次。这种方式更轻量,适合策略逻辑简单且不需要复杂状态的场景。

#include <iostream>
#include <functional>
#include <memory>

// 上下文直接持有 std::function
class Order {
private:
    std::function<void(int)> payment_func_;  // 策略函数

public:
    // 构造函数接受任何可调用对象
    explicit Order(std::function<void(int)> payment_func)
        : payment_func_(std::move(payment_func)) {}

    void setPaymentStrategy(std::function<void(int)> payment_func) {
        payment_func_ = std::move(payment_func);
    }

    void checkout(int amount) const {
        if (payment_func_) {
            payment_func_(amount);
        } else {
            std::cout << "No payment strategy set!" << std::endl;
        }
    }
};

int main() {
    // 使用 Lambda 定义具体策略
    auto credit_pay = [](int amount) {
        std::cout << "Paid " << amount << " using Credit Card." << std::endl;
    };
    auto alipay_pay = [](int amount) {
        std::cout << "Paid " << amount << " using Alipay." << std::endl;
    };
    auto wechat_pay = [](int amount) {
        std::cout << "Paid " << amount << " using WeChat Pay." << std::endl;
    };

    Order order(credit_pay);
    order.checkout(100);

    order.setPaymentStrategy(alipay_pay);
    order.checkout(200);

    order.setPaymentStrategy(wechat_pay);
    order.checkout(300);

    return 0;
}

这种方式的优点是代码更简洁、类型更灵活,甚至可以使用普通函数、Lambda 或 bind 表达式。缺点是丢失了策略类的类型信息,且不利于策略内部状态管理(除非通过捕获引用或使用 std::shared_ptr 管理状态对象)。

5.4 优缺点

优点

  • 开闭原则:新增策略无需修改上下文,只需添加新的策略类。

  • 避免多重条件语句(如 if-elseswitch),提高代码可读性和可维护性。

  • 运行时切换:策略可以在程序运行过程中动态更换。

  • 算法复用:不同上下文可以共享相同的策略对象。

缺点

  • 类数量增加:每个策略都需要一个具体类(或函数),可能增加系统复杂度。

  • 客户端必须了解策略差异:客户需要知道有哪些策略,以及它们的不同适用场景。

  • 通信开销:上下文与策略之间可能需要进行参数传递,如果策略需要上下文的数据,可能引入耦合。

5.5 使用场景

  • 系统需要动态地在几种算法中选择一种。

  • 算法逻辑复杂,且经常变化,需要独立封装,便于测试和维护。

  • 避免使用庞大的条件判断语句来选择不同行为。

  • 对同一个对象,需要在不同场景下使用不同的处理方式(如支付、排序、压缩、加密等)。

 

posted @ 2026-08-24 17:49  钟齐峰  阅读(7)  评论(0)    收藏  举报