深入浅出策略模式:一个C++模板实现的灵活策略管理库
个人专著《C++元编程与通用设计模式实现》由清华大学出版社出版。该书内容源于工业级项目实践,出版后市场反馈积极(已加印)。其专业价值获得了图书馆系统的广泛认可:不仅被中国国家图书馆作为流通与保存本收藏,还被近半数省级公共图书馆及清华大学、浙江大学等超过35所高校图书馆收录为馆藏。
个人软仓,gitee搜索“galaxy_0”
深入浅出策略模式:一个C++模板实现的灵活策略管理库
1. 策略模式简介
在软件开发中,我们常常会遇到这样的情况:根据不同的条件,需要采用不同的处理方式。比如支付时根据用户选择的渠道调用不同的接口,或者游戏角色根据状态触发不同的技能。传统的if-else或switch虽然简单直接,但随着条件增多,代码会变得臃肿且难以维护。这时候,策略模式就派上用场了——它把每个算法封装成独立的策略,让它们可以互相替换,就像更换工具一样方便。
今天,我将带你认识一个基于C++模板实现的策略模式管理库。它利用std::function和std::unordered_map,将策略与键绑定,支持普通函数、lambda、成员函数等多种可调用对象,而且接口非常简洁。无论你是C++新手还是老手,都能轻松上手。
2. 代码设计解析
2.1 整体设计思路
这个库的核心思想其实很简单:用一把“钥匙”(键)对应一个“策略”(函数)。我们需要什么策略,就用对应的钥匙去取。为了做到这一点,它借助了C++标准库中的std::unordered_map来存储键值对,而值的类型是std::function<Ret(Args...)>——一个能够包装任何可调用对象的万能容器。
但问题来了:用户可能用普通函数指针、lambda、成员函数等各种方式定义策略,它们的类型各不相同。怎么让它们都能被std::function接纳呢?答案就是模板特化。库通过三个特化版本,自动将各种函数类型转换为统一的std::function,从而实现了“一次编写,随处可用”的便利。
2.2 类模板结构
先看主模板,它只是一个空架子,真正的实现在特化版本中:
template<typenamekeyType,typenamefuncType>structstrategy{};第一个特化:针对std::function类型
这是最核心的版本,内部维护了一个unordered_map,键是keyType,值是std::function<Ret(Args...)>。
template<typenamekeyType,typenameRet,typename...Args>classstrategy<keyType,std::function<Ret(Args...)>>{public:usingcallee_type=std::function<Ret(Args...)>;usingiterator=typenamestd::unordered_map<keyType,callee_type>::iterator;protected:std::unordered_map<keyType,callee_type>m_strates__;// 策略仓库public:strategy(){}virtual~strategy(){}// 添加策略template<typenameFunct_t>booladd(constkeyType&key,Funct_t&&callFn);// 移除策略boolerase(constkeyType&key);// 清空策略voidclear();// 获取策略数量size_tcount();// 对所有策略执行一次(不关心返回值)template<typename...Params>voidcall_each(Params&&...args);// 根据键执行策略,返回结果template<typename...Params>Retcall(constkeyType&key,Params&&...args);};add方法在键不存在时插入新的策略,erase移除指定键,clear一键清空,count告诉你仓库里有多少策略。call_each会遍历所有策略并依次调用,适合广播场景;call则根据键找到对应的策略并执行,如果找不到,会抛出std::runtime_error异常。
第二个特化:支持普通函数指针
为了让用户能直接用函数类型(如void(int))实例化,库提供了这个继承版本:
template<typenamekeyType,typenameRet,typename...Args>classstrategy<keyType,Ret(Args...)>:publicstrategy<keyType,std::function<Ret(Args...)>>{};这样,strategy<int, void(int)>就等价于strategy<int, std::function<void(int)>>,省去了手动包装的麻烦。
第三个特化:支持成员函数指针
如果你想把某个类的成员函数作为策略,这个特化会帮你自动转换成对应的std::function:
template<typenamekeyType,typenameclassType,typenamefuncType>classstrategy<keyType,funcType classType::*>:publicstrategy<keyType,std::function<funcType>>{};比如strategy<int, decltype(&MyClass::method)>也能正常工作。
2.3 关键方法实现
- add:使用了完美转发,将传入的可调用对象直接构造为
std::function,然后插入map。如果键已存在,则添加失败(返回false),避免意外覆盖。 - call:先在map中查找键,找到就直接调用函数,并将参数完美转发进去;找不到则抛出异常。
- call_each:简单遍历map,依次调用每个函数,忽略返回值。
所有方法都通过模板支持任意参数列表,非常灵活。
3. 使用示例
3.1 统一接口策略(继承同一接口)
假设我们有一组策略类,都继承自同一个基类stra_base,实现了统一的callee接口:
classstra_base{public:virtualvoidcallee(inta)=0;virtual~stra_base(){}