STL中的设计模式二1. 观察者模式核心思想定义对象间的一种一对多的依赖关系当一个对象的状态发生改变时所有依赖于它的对象都得到通知并被自动更新。STL/现代 C 中的体现C 标准库并没有提供原生的信号/槽机制但现代 C 实现观察者模式的标准惯用法是使用std::vectorstd::function来管理订阅者列表。此外C20 引入的std::stop_source和std::stop_token也是观察者模式的一种具体应用请求取消的通知机制。代码示例基于 STL 组件的信号槽#include iostream #include vector #include functional #include string // 利用 STL 组件构建的通用信号发布者 templatetypename... Args class Signal { public: // 使用 std::function 封装各种可调用对象观察者 using SlotType std::functionvoid(Args...); // 订阅注册观察者 void connect(SlotType slot) { slots_.push_back(std::move(slot)); } // 发布通知 void emit(Args... args) { for (auto slot : slots_) { slot(args...); // 通知所有观察者 } } private: std::vectorSlotType slots_; // 观察者列表 }; int main() { Signalstd::string, int newsSignal; // 观察者 1Lambda newsSignal.connect([](const std::string title, int priority) { std::cout [Subscriber 1] Breaking: title (P: priority )\n; }); // 观察者 2带捕获的 Lambda std::string name Alice; newsSignal.connect([name](const std::string title, int priority) { std::cout [Subscriber 2] name notified about: title \n; }); // 触发事件 newsSignal.emit(C23 Released!, 1); return 0; }2. 组合模式核心思想将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。STL/现代 C 中的体现传统组合模式依赖统一的虚基类接口。而在 C17 后std::variant结合递归数据结构提供了一种类型安全、无需虚函数的全新组合模式实现方式。这在构建 AST抽象语法树或 JSON 树时极为常见。代码示例基于std::variant的静态组合模式#include iostream #include vector #include variant #include memory // 前向声明 struct CompositeNode; // 叶子节点只包含值 struct LeafNode { int value; }; // 组合节点可以包含叶子或别的组合节点树形结构 struct CompositeNode { std::string name; // 核心使用 variant 实现递归的树形结构统一了叶子与组合的存储 std::vectorstd::variantLeafNode, std::unique_ptrCompositeNode children; }; // 统一的操作接口访问者/遍历器 void printTree(const std::variantLeafNode, std::unique_ptrCompositeNode node, int indent 0) { std::string prefix(indent, ); // std::visit 实现对统一接口的分发 std::visit([](auto arg) { using T std::decay_tdecltype(arg); if constexpr (std::is_same_vT, LeafNode) { // 对叶子节点的操作 std::cout prefix - Leaf: arg.value \n; } else if constexpr (std::is_same_vT, std::unique_ptrCompositeNode) { // 对组合节点的操作递归对外表现为与叶子同级的接口 std::cout prefix Composite: arg-name \n; for (const auto child : arg-children) { printTree(child, indent 4); } } }, node); } int main() { auto root std::make_uniqueCompositeNode(); root-name Root; root-children.push_back(LeafNode{10}); root-children.push_back(LeafNode{20}); auto subDir std::make_uniqueCompositeNode(); subDir-name Sub; subDir-children.push_back(LeafNode{30}); root-children.push_back(std::move(subDir)); std::variantLeafNode, std::unique_ptrCompositeNode rootVariant std::move(root); printTree(rootVariant); return 0; }3. 享元模式核心思想运用共享技术有效地支持大量细粒度的对象避免拥有相同内容的多余对象造成的内存开销。STL/现代 C 中的体现标准库没有直接的享元工厂但利用std::unordered_map和std::weak_ptr/std::shared_ptr是实现缓存型享元模式的标准 C 惯用法。std::weak_ptr允许在对象无人使用时被自动销毁完美解决了享元对象的生命周期管理问题。代码示例标准库实现的享元工厂#include iostream #include string #include unordered_map #include memory // 享元对象假设非常耗费内存如大纹理/字体数据 class HeavyFont { public: HeavyFont(const std::string name) : name_(name) { std::cout Loading heavy font: name_ \n; // 模拟耗时加载 } void render(const std::string text) { std::cout Rendering [ text ] with name_ \n; } private: std::string name_; }; // 享元工厂 class FontFactory { public: using FontPtr std::shared_ptrHeavyFont; FontPtr getFont(const std::string name) { // 检查缓存是否过期weak_ptr 的妙用 auto it cache_.find(name); if (it ! cache_.end()) { if (auto ptr it-second.lock()) { std::cout Cache hit: name \n; return ptr; // 缓存命中直接共享 } } // 缓存未命中或已过期创建新对象 std::cout Cache miss: name \n; FontPtr newFont std::make_sharedHeavyFont(name); cache_[name] newFont; // weak_ptr 赋值 return newFont; } private: std::unordered_mapstd::string, std::weak_ptrHeavyFont cache_; // 享元池 }; int main() { FontFactory factory; auto font1 factory.getFont(Arial); // 加载 auto font2 factory.getFont(Arial); // 共享 font1-render(Hello); font1.reset(); // 释放 font1 // 此时 Arial 的内存并没有被销毁因为 font2 还在引用 auto font3 factory.getFont(Arial); // 依然共享 font2.reset(); font3.reset(); // 真正释放 Arial 内存 auto font4 factory.getFont(Arial); // 重新加载享元被回收了 return 0; }4. 装饰器模式核心思想动态地给一个对象添加一些额外的职责。就增加功能来说装饰器模式相比生成子类更为灵活。STL/现代 C 中的体现C20 引入的Ranges 库std::views是装饰器模式的巅峰之作。views::filter、views::transform就像一层层装饰器动态地给底层容器叠加过滤、转换逻辑而不会拷贝底层数据且支持延迟计算。代码示例C20 Ranges 视图装饰#include iostream #include vector #include ranges int main() { std::vectorint data {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 原始数据像是一个核心对象 auto view data | std::views::filter([](int n) { return n % 2 0; }) // 装饰器1只保留偶数 | std::views::transform([](int n) { return n * 2; }); // 装饰器2数值翻倍 // 此时 view 并没有真正执行计算惰性求值它只是被装饰了 // 当我们遍历时装饰逻辑才生效 for (int val : view) { std::cout val ; // 输出 4 8 12 16 20 } std::cout \n; return 0; }5. 桥接模式核心思想将抽象部分与它的实现部分分离使它们都可以独立地变化。STL/现代 C 中的体现经典案例是std::iostream家族与std::streambuf。iostream是抽象接口输入/输出操作而streambuf是底层实现具体的读写字节源文件、字符串、控制台。你可以将任意streambuf桥接到iostream上改变底层行为而无需改变接口。代码示例流与缓冲区的桥接#include iostream #include sstream #include fstream void processInput(std::istream stream) { // 抽象接口只关心从流中读取不关心数据来自哪里 std::string word; while (stream word) { std::cout Read: word \n; } } int main() { // 实现A从字符串读取 std::istringstream iss(Hello from string); std::cout --- From String ---\n; processInput(iss); // 实现B从文件读取假设有 data.txt // std::ifstream ifs(data.txt); // processInput(ifs); // 桥接的动态性运行时替换实现 std::ostringstream oss; // 将 ooss 的底层缓冲区桥接到 cout 上导致所有输出重定向 auto oldBuf std::cout.rdbuf(oss.rdbuf()); std::cout This goes to string, not console! std::endl; std::cout.rdbuf(oldBuf); // 恢复 std::cout Captured: oss.str() \n; return 0; }6. 访问者模式核心思想表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。STL/现代 C 中的体现传统访问者模式需要复杂的双分派和虚函数。现代 C 使用std::variantstd::visit实现了编译时静态访问者也称为代数数据类型。这种方式完全消除了虚函数开销且类型极其安全。代码示例std::visit实现的静态访问者#include iostream #include variant #include vector // 被访问的元素定义 struct Circle { double radius; }; struct Square { double side; }; struct Triangle { double base; double height; }; // 使用 variant 定义对象结构 using Shape std::variantCircle, Square, Triangle; // 访问者利用 C17 的 overloaded 惯用法推导指引 templateclass... Ts struct overloaded : Ts... { using Ts::operator()...; }; templateclass... Ts overloaded(Ts...) - overloadedTs...; int main() { std::vectorShape shapes { Circle{5.0}, Square{4.0}, Triangle{6.0, 3.0} }; // 新操作1计算面积无需修改 Circle/Square 等结构体定义 auto areaVisitor overloaded { [](const Circle c) { return 3.14159 * c.radius * c.radius; }, [](const Square s) { return s.side * s.side; }, [](const Triangle t) { return 0.5 * t.base * t.height; } }; // 新操作2绘制描述同样无需修改结构体 auto drawVisitor overloaded { [](const Circle c) { std::cout Drawing Circle\n; }, [](const Square s) { std::cout Drawing Square\n; }, [](const Triangle t) { std::cout Drawing Triangle\n; } }; for (const auto shape : shapes) { std::cout Area: std::visit(areaVisitor, shape) \n; std::visit(drawVisitor, shape); } return 0; }7. 责任链模式核心思想使多个对象都有机会处理请求从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链并沿着这条链传递请求直到有一个对象处理它为止。STL/现代 C 中的体现C 标准库没有直接提供责任链但基于std::vectorstd::function或std::forward_list构建中间件/拦截器链是标准 C 网络库如 Boost.Beast 非官方设计或日志框架的常见惯用法。代码示例基于 STL 容器的函数链#include iostream #include vector #include functional #include string // 模拟 HTTP 请求 struct Request { std::string url; bool isHandled false; }; // 责任链节点返回 true 表示请求已处理终止传递返回 false 传递给下一个 using Handler std::functionbool(Request); class MiddlewareChain { public: void addHandler(Handler handler) { handlers_.push_back(std::move(handler)); } void process(Request req) { for (auto handler : handlers_) { if (handler(req)) { std::cout [Chain] Request handled, stopping propagation.\n; break; // 责任链终止 } } if (!req.isHandled) { std::cout [Chain] Request reached the end unhandled.\n; } } private: std::vectorHandler handlers_; }; int main() { MiddlewareChain chain; // 处理者 1鉴权中间件 chain.addHandler([](Request req) { if (req.url.find(/admin) ! std::string::npos) { std::cout [Auth] Admin area detected. Unauthorized!\n; req.isHandled true; return true; // 拦截不继续传递 } std::cout [Auth] Public area. Passing...\n; return false; }); // 处理者 2日志中间件 chain.addHandler([](Request req) { std::cout [Logger] Logging request to: req.url \n; return false; // 记录日志后总是放行 }); // 处理者 3业务处理 chain.addHandler([](Request req) { std::cout [Business] Handling business logic.\n; req.isHandled true; return true; }); // 测试 1普通请求贯穿 1 - 2 - 3 Request req1{/home, false}; chain.process(req1); std::cout -------------------\n; // 测试 2受限请求在 1 处被拦截 Request req2{/admin/panel, false}; chain.process(req2); return 0; }