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

【读书笔记】《C++ Software Design》第五章:The Strategy and Command Design Patterns

《C++ Software Design》第五章:The Strategy and Command Design Patterns

本章重点介绍两种“行为型设计模式”:策略模式(Strategy Pattern)命令模式(Command Pattern)。核心问题分别是:

  • Strategy:隔离如何做(算法实现)
  • Command:隔离做什么(执行动作)

深入探讨传统面向对象实现存在的不足,以及如何借助现代 C++ 的特性(如模板、std::function、值语义)来替代经典写法。


Guideline 19: Use Strategy to Isolate How Things Are Done

目的

我们不希望主类直接控制算法细节,而是通过抽象策略接口将具体算法实现分离出去,使行为具有可配置性、可替换性和可测试性

Analyzing the Design Issues

问题:如何在不修改主类逻辑的前提下,动态切换算法行为?

示例情境:有一个 Shape 类,它支持不同的“绘图策略”,例如 Vector 和 Raster。

The Strategy Design Pattern Explained

struct DrawingStrategy {virtual void draw() const = 0;virtual ~DrawingStrategy() = default;
};struct VectorDrawing : DrawingStrategy {void draw() const override {std::cout << "Drawing with vector lines.\n";}
};struct RasterDrawing : DrawingStrategy {void draw() const override {std::cout << "Drawing with pixels.\n";}
};class Shape {std::unique_ptr<DrawingStrategy> strategy_;
public:Shape(std::unique_ptr<DrawingStrategy> s) : strategy_(std::move(s)) {}void draw() const { strategy_->draw(); }
};

你可以在运行时将不同策略注入 Shape 对象中。

Analyzing the Shortcomings of the Naive Solution

  • 使用虚函数有运行时开销;
  • 所有策略都要继承接口类,样板代码多;
  • 无法表达“静态策略”:某些行为在编译期就应决定。

Comparison Between Visitor and Strategy

维度StrategyVisitor
用于扩展算法的实现操作的种类
使用方式客户端选择策略对象类型自身 accept(visitor) 回调
增加新操作只需新建策略类必须修改 visitor 接口和实现类
动态 vs 静态可动态切换通常是静态绑定

Policy-Based Design(模板策略)

借助模板参数实现“编译期策略注入”,零运行时成本:

template<typename DrawingPolicy>
class Shape {
public:void draw() const {DrawingPolicy{}.draw();}
};struct SVGPolicy {void draw() const { std::cout << "SVG drawing\n"; }
};Shape<SVGPolicy> s1;
s1.draw(); // 输出:SVG drawing

优势

  • 零开销(没有虚表);
  • 策略间互不耦合;
  • 更易于组合多个策略(如日志 + 绘图)。

Guideline 20: Favor Composition over Inheritance

原则解释

继承是紧耦合,破坏封装;组合是一种更灵活的复用方式。

class Engine {
public:virtual void start() = 0;
};class Car {std::unique_ptr<Engine> engine_;
public:Car(std::unique_ptr<Engine> e) : engine_(std::move(e)) {}void drive() {engine_->start();}
};

将“引擎行为”作为组合注入而非通过继承扩展 Car,支持运行时替换、功能复用。


Guideline 21: Use Command to Isolate What Things Are Done

概念

命令模式将“动作”封装为对象,可用于延迟执行、宏命令、撤销、操作记录等场景。

Command Design Pattern Explained

struct Command {virtual void execute() = 0;virtual ~Command() = default;
};struct PrintCommand : Command {void execute() override {std::cout << "Printing document...\n";}
};class Invoker {std::vector<std::unique_ptr<Command>> queue_;
public:void add(std::unique_ptr<Command> cmd) {queue_.push_back(std::move(cmd));}void run() {for (auto& cmd : queue_) {cmd->execute();}}
};

🆚 Strategy vs Command

特征StrategyCommand
抽象的目标算法的实现方式动作的执行内容
使用方式context->run(strategy)cmd->execute()
通常搭配对象算法、排序、日志用户事件、GUI、请求调度

Shortcomings of Classic Command

  • 每种命令都需创建一个类,数量膨胀;
  • 动作封装不够灵活,类耦合度高。

Guideline 22: Prefer Value Semantics over Reference Semantics

GoF 风格的问题

经典实现中,大量使用 shared_ptr 或裸指针管理命令/策略对象,容易造成资源管理混乱:

auto cmd = std::make_shared<PrintCommand>();

一旦多个地方持有相同指针,生命周期不易掌控。

现代 C++:值语义策略

struct PrintCommand {void operator()() const {std::cout << "Print\n";}
};std::vector<PrintCommand> cmds = { PrintCommand{} };
  • 无需担心内存泄露;
  • 容器友好;
  • 更安全、易于并发使用。

Value Semantics in std::function

using Command = std::function<void()>;std::vector<Command> cmds;
cmds.push_back([](){ std::cout << "Execute\n"; });

将命令行为直接封装为“值”,即函数对象。


Guideline 23: Prefer a Value-Based Implementation of Strategy and Command

std::function 简化命令和策略

using Strategy = std::function<void()>;class Shape {Strategy draw_;
public:Shape(Strategy s): draw_(std::move(s)) {}void draw() const { draw_(); }
};Shape s([](){ std::cout << "Draw with OpenGL\n"; });
s.draw(); // 输出:Draw with OpenGL

不需要接口类、不需要虚函数,不需要手动管理生命周期。

性能分析

实现方式是否需堆分配是否有虚函数性能
传统 Command是(智能指针)中等
Policy 模板策略高(内联优化)
std::function否(小对象优化)中高(取决闭包大小)

std::function 的限制

  • 不支持接口描述:没有显式接口要求;
  • 不能静态类型检查(传错参数不会编译失败);
  • 拷贝时需注意闭包大小(超过 SBO 时可能 heap 分配)。

小结

模式作用建议实现方式场景适用性
Strategy隔离“如何做”(算法)模板策略 / std::function日志、绘图、压缩算法
Command隔离“做什么”(动作)std::functionGUI、宏命令、异步请求
Value Semantics简化资源管理,提高安全性使用函数对象 / RAII所有现代 C++ 项目
Policy零运行时开销的行为注入模板性能敏感、泛型组件设计
http://www.dtcms.com/a/277445.html

相关文章:

  • Java学习------设计模式(1)
  • ZKmall开源商城技术攻略:轻松掌握规则引擎与Spring Boot3接口的开发技巧
  • Linux V4L2应用编程常用结构体介绍
  • STEP 7-Micro/WIN SMART 编程软件:从入门到精通的使用指南
  • 面试150 从前序与中序遍历构造二叉树
  • STM32-第五节-TIM定时器-1(定时器中断)
  • Clojure和Golang中的Channel有什么异同(TBC)
  • 构建应用内智能:衡石嵌入式BI如何打造“指标中台”驱动的场景化分析
  • Python文件路径操作全面指南:从基础到高级应用
  • 深入理解数据库连接池:原理、实现与Druid实战
  • MCU中的系统控制器(System Controller)是什么?
  • Spring Boot + MyBatis 实现用户登录功能详解(基础)
  • PaperPel
  • Oracle SQL - 使用行转列PIVOT减少表重复扫描(实例)
  • AI驱动的软件工程(上):人机协同的设计与建模
  • 【读书笔记】《C++ Software Design》第六章深入剖析 Adapter、Observer 和 CRTP 模式
  • 实现“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