当前位置: 首页 > 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.lryc.cn/news/586854.html

相关文章:

  • 开机自动启动同花顺,并设置进程优先级为高
  • Linux驱动开发1:设备驱动模块加载与卸载
  • 【Linux学习笔记】认识信号和信号的产生
  • JAVA JVM虚拟线程
  • HTML 初体验
  • 软件文档体系深度解析:工程视角下的文档架构与治理
  • OneCode3.0 VFS分布式文件管理API速查手册
  • jenkins使用Jenkinsfile部署springboot+docker项目
  • 代码随想录|图论|15并查集理论基础
  • Docker一键安装中间件(RocketMq、Nginx、MySql、Minio、Jenkins、Redis)脚步
  • SDN软件定义网络架构深度解析:分层模型与核心机制
  • Redis缓存设计与性能优化指南
  • 解码冯・诺依曼:操作系统是如何为进程 “铺路” 的?
  • [Nagios Core] CGI接口 | 状态数据管理.dat | 性能优化
  • 基于Redis Streams的实时消息处理实战经验分享
  • Appium源码深度解析:从驱动到架构
  • 使用macvlan实现容器的跨主机通信
  • 在Intel Mac的PyCharm中设置‘add bin folder to the path‘的解决方案
  • React强大且灵活hooks库——ahooks入门实践之常用场景hook
  • p4 大小写检查
  • Rust赋能文心大模型4.5智能开发
  • QCustomPlot绘图保存成PDF文件
  • 软考中级学习系列-- 阶码与尾数
  • 香港服务器Python自动化巡检脚本开发与邮件告警集成
  • 详解Linux下多进程与多线程通信(一)
  • Leetcode 3615. Longest Palindromic Path in Graph
  • OpenLoong技术观察 | 卓益得十年磨一剑:“行者”系列人形机器人技术演进观察
  • 构造函数延伸应用
  • DH(Denavit–Hartenberg)矩阵
  • redis汇总笔记