【读书笔记】《C++ Software Design》第六章深入剖析 Adapter、Observer 和 CRTP 模式

《C++ Software Design》第六章深入剖析 Adapter、Observer 和 CRTP 模式

在现代 C++ 软件设计中,设计模式(Design Patterns) 早已超越传统的"套路"概念,演化为一种构建高扩展性、高抽象性的系统结构指南。《C++ Software Design》一书第六章精炼地介绍了三类实用的设计模式:Adapter、Observer 和 CRTP(Curiously Recurring Template Pattern)


Guideline 24:使用 Adapter 模式统一接口(Standardize Interfaces)

Adapter 模式的基本原理

Adapter(适配器) 模式的核心目的是将不兼容接口的类转换为用户所期望的接口。你可以把它想象成"接口翻译器"。

UML 示意:
复制代码
[Client] → [Target Interface] ← [Adapter] ← [Adaptee]

应用场景:

  • 旧有系统迁移到新接口标准
  • 第三方库的接口无法修改
  • 多个接口标准需要统一封装

Object Adapter vs Class Adapter

C++ 中可以通过两种方式实现适配器:

1. Object Adapter(对象适配器)

通过组合方式,将已有类的对象作为成员,并在适配器内部转换调用。

cpp 复制代码
class Adaptee {
public:
    void specificRequest() {
        std::cout << "Adaptee called\n";
    }
};

class Target {
public:
    virtual void request() = 0;
};

class Adapter : public Target {
    Adaptee adaptee;
public:
    void request() override {
        adaptee.specificRequest(); // 转换调用
    }
};
2. Class Adapter(类适配器)

使用多重继承,使适配器既继承目标接口,也继承现有实现类:

cpp 复制代码
class Adapter : public Target, public Adaptee {
public:
    void request() override {
        specificRequest(); // 直接调用继承来的方法
    }
};

缺点:类适配器强依赖继承,侵入性强,不适合多适配组合。


标准库中的 Adapter 示例

  • std::function 可以适配任意可调用对象(函数指针、lambda、functor)
  • std::back_insert_iterator 是对容器插入操作的适配
  • <functional> 中的 std::bind, std::mem_fn, std::not1 都是函数适配器

Adapter 与 Strategy 的对比

特性 Adapter Strategy
目标 兼容已有接口 提供可替换的算法
封装行为 不是行为封装,而是接口转换 封装行为并可切换
应用对象 面向接口兼容问题 面向策略多样性

Adapter 的缺点分析

  • 多重适配链容易导致复杂依赖
  • 隐藏真实接口,可能造成可读性下降
  • 类适配器过度依赖继承(不可组合)

Guideline 25:使用 Observer 实现解耦的通知机制(Abstract Notification)

Observer 模式的基本原理

Observer(观察者)是一种 发布-订阅(Publish-Subscribe)机制,当"主题对象"状态变化时,通知所有观察者对象。

UML 示意:
复制代码
[Subject] → maintains list of → [Observers]
[Observer] ← notified by ← [Subject::notify()]

关键点:

  • 抽象通知机制
  • 支持多观察者
  • 动态添加/移除观察者

经典实现方式

cpp 复制代码
class Observer {
public:
    virtual void update(int value) = 0;
};

class Subject {
    std::vector<Observer*> observers;
    int value;
public:
    void attach(Observer* o) {
        observers.push_back(o);
    }

    void setValue(int v) {
        value = v;
        for (auto* o : observers) {
            o->update(value);
        }
    }
};

值语义的观察者实现(Modern C++ 风格)

使用 std::functionstd::unordered_map 代替原始指针和继承层次:

cpp 复制代码
class Subject {
    using Callback = std::function<void(int)>;
    std::unordered_map<int, Callback> observers;
    int nextId = 0;
public:
    int subscribe(Callback cb) {
        int id = nextId++;
        observers[id] = std::move(cb);
        return id;
    }

    void unsubscribe(int id) {
        observers.erase(id);
    }

    void notify(int val) {
        for (auto& [_, cb] : observers) cb(val);
    }
};

Observer 的缺陷

  • 如果未正确解除绑定,可能出现 悬挂引用
  • 串联通知容易形成 更新风暴
  • 缺乏线程安全机制时存在并发问题

Guideline 26:使用 CRTP 实现静态类型特性(Static Type Categories)

CRTP 的动机

Curiously Recurring Template Pattern(奇异递归模板模式) 通过将派生类作为模板参数传递给基类,实现 静态多态(Static Polymorphism)

cpp 复制代码
template<typename Derived>
class Base {
public:
    void interface() {
        static_cast<Derived*>(this)->implementation();
    }
};

class Derived : public Base<Derived> {
public:
    void implementation() {
        std::cout << "Derived logic\n";
    }
};

使用场景

  • 静态分发函数行为
  • 消除虚函数开销
  • 编译期强类型行为(如标签分类)

CRTP 的缺点分析

  • 可读性差:对新手不友好,调试困难
  • 耦合性强:派生类必须符合模板要求,不能动态切换行为
  • 模板膨胀:容易造成代码 bloat

CRTP vs C++20 Concepts

特性 CRTP Concepts
类型约束 编译期类型匹配 明确声明概念接口
抽象性 依赖实例化机制 抽象表达能力更强
可组合性 较差(需结构匹配) 支持组合概念

✅ 在现代 C++ 中,CRTP 仍是 concepts 的低阶静态行为模拟手段。


Guideline 27:使用 CRTP 构建静态 Mixin 类(Static Mixins)

Mixin 是一种通过组合而非继承 复用行为的技术。在 C++ 中,CRTP 非常适合做 静态 Mixin


动机示例:添加强类型比较支持

cpp 复制代码
template<typename Derived>
class EqualityComparable {
public:
    friend bool operator==(const Derived& lhs, const Derived& rhs) {
        return lhs.equals(rhs);
    }
    friend bool operator!=(const Derived& lhs, const Derived& rhs) {
        return !lhs.equals(rhs);
    }
};

class Person : public EqualityComparable<Person> {
    std::string name;
public:
    bool equals(const Person& other) const {
        return name == other.name;
    }
};

用于静态能力增强的模式

  • boost::operators<>
  • std::enable_shared_from_this<T> 本质就是 CRTP Mixin
  • Policy-based Design 中每个 policy 类也是 CRTP mixin

总结

第六章系统揭示了三种 C++ 中非常典型的设计模式及其静态实现技巧:

模式名称 本质功能 推荐场景
Adapter 接口兼容/转换 第三方库/统一标准
Observer 解耦事件通知机制 GUI、MVC、数据流架构
CRTP 静态多态与类型增强 零运行开销的行为复用、类型封装

它们分别强调了接口适配、事件解耦和静态行为增强的不同维度,是 C++ 软件设计中的"三剑客"。

相关推荐
程序员编程指南3 小时前
Qt 嵌入式 Linux 系统定制全指南
linux·c语言·开发语言·c++·qt
R-G-B9 小时前
【08】C++实战篇——C++ 生成动态库.dll 及 C++调用DLL,及实际项目中的使用技巧
c++·c++ 生成动态库.dll·c++ 生成静态库.lib·c++调用动态库.dll·c++调用静态库.lib·c++调用dll·c++调用lib
pointers_syc9 小时前
【设计模式】2.策略模式
java·设计模式·策略模式
朝朝又沐沐10 小时前
算法竞赛阶段二-数据结构(40)数据结构栈的STL
开发语言·数据结构·c++·算法
比特森林探险记11 小时前
Go语言常用的设计模式
开发语言·设计模式·golang
Antonio91511 小时前
【网络编程】WebSocket 实现简易Web多人聊天室
前端·网络·c++·websocket
清朝牢弟12 小时前
Ubuntu系统VScode实现opencv(c++)图像放缩与插值
c++·vscode·opencv·ubuntu·计算机视觉
呆瑜nuage12 小时前
list的使用和模拟
c++·list
tomato0913 小时前
Codeforces Round 1040 (Div. 2)(补题)
c++