当前位置: 首页 > news >正文

【读书笔记】《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(对象适配器)

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

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(类适配器)

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

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 的对比

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

Adapter 的缺点分析

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

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

Observer 模式的基本原理

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

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

关键点:

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

经典实现方式

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 代替原始指针和继承层次:

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)

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

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

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


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

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


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

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++ 软件设计中的“三剑客”。

http://www.dtcms.com/a/277429.html

相关文章:

  • 实现“micro 关键字搜索全覆盖商品”并通过 API 接口提供实时数据(一个方法)
  • fatal: active `post-checkout` hook found during `git clone`
  • mapstruct与lombok冲突原因及解决方案
  • 【Linux 学习指南】网络基础概念(一):从协议到分层,看透计算机通信的底层逻辑
  • LeetCode|Day9|976. 三角形的最大周长|Python刷题笔记
  • 通过反射,提取 Cat 类 泛型 父类 接口 属性 的具体类型参数
  • 【一起来学AI大模型】部署优化推理加速:TensorRT-LLM
  • 华为交换机 undo negotiation auto功能(华为交换机端口接光纤两端起不来)
  • Jvm优化高手-笔记
  • Cursor精准上下文指定
  • 印度纱丽变革:传统靛蓝工艺在无性别斗篷中的延续
  • TensorFlow深度学习实战(24)——变分自编码器详解与实现
  • 基于Springboot+UniApp+Ai实现模拟面试小工具三:后端项目基础框架搭建上
  • AI 助力:如何批量提取 Word 表格字段并导出至 Excel
  • React强大且灵活hooks库——ahooks入门实践之生命周期类hook(lifecycle)详解
  • vite---环境变量和模式配置(.env 文件)
  • 【论文阅读】Think Only When You Need with Large Hybrid-Reasoning Models
  • Linux进程状态实战指南:转换关系、监控命令与状态解析
  • 【Linux | 网络】应用层(HTTP)
  • html-input 系列
  • 二进制、八进制、十进制、十六进制的转换
  • 用 Node.js 构建模块化的 CLI 脚手架工具,从 GitHub 下载远程模板
  • HarmonyOS-ArkUI Web控件基础铺垫1-HTTP协议-数据包内容
  • 【基于开源大模型(如deepseek)开发应用及其发展趋势的一点思考】
  • 早期 CNN 的经典模型—卷积神经网络(LeNet)
  • 在Linux文件写入软件设计中,直接写入SSD时磁盘写入抖动(I/O延迟波动)的解决方案
  • [CH582M入门第六步]软件IIC驱动AHT10
  • Leetcode 3613. Minimize Maximum Component Cost
  • Vue文件上传实战指南
  • 深入理解 Linux 文件系统层级结构