一、背景与核心概念:从"代码泥潭"到"策略解耦"
要理解策略模式,我们得先从它要解决的"痛点"说起------在没有策略模式的年代,开发者是如何处理"多算法可选"的场景?
1.1 策略模式的起源:解耦"算法耦合"的历史需求
在结构化编程或早期面向对象开发中,当业务需要"根据不同条件选择不同算法"时,开发者常采用**"硬编码分支"** 实现,例如一个电商折扣计算功能:
cpp
// 硬编码实现折扣计算(反面例子)
double calculateDiscount(double amount, std::string userLevel) {
if (userLevel == "normal") {
// 普通用户:无折扣
return amount;
} else if (userLevel == "vip") {
// VIP用户:9折
return amount * 0.9;
} else if (userLevel == "svip") {
// SVIP用户:8折
return amount * 0.8;
} else {
throw std::invalid_argument("无效用户等级");
}
}
这种写法看似简单,却存在3个致命问题:
- 耦合严重 :算法(折扣规则)与业务逻辑(订单计算)强绑定,修改任何一个折扣规则都要改动
calculateDiscount
函数; - 扩展性差 :若新增"黑金用户7折",需新增
else if
分支,违反"开闭原则"(对扩展开放、对修改关闭); - 维护困难:当算法增多(如10种用户等级),函数会变得冗长,排查bug需遍历所有分支。
为解决这些问题,GoF在1994年《设计模式:可复用面向对象软件的基础》中提出策略模式 ,其核心思路是:把每个算法当成一个"策略",单独封装成类/组件,让业务逻辑通过"组合"而非"分支"调用策略------就像我们出门时,根据天气"动态选择工具"(晴天带伞、雨天带雨衣),而不是把伞和雨衣缝在衣服上(继承)。
1.2 核心术语解析:理解策略模式的"角色分工"
策略模式的实现依赖4个核心角色,它们的分工明确,共同实现"算法解耦"。我们用Mermaid类图直观展示其关系(遵循用户指定的语法规范):
对上述角色的详细解读如下表:
角色名称 | 核心职责 | 实现方式(C++) |
---|---|---|
抽象策略(Strategy) | 定义所有具体策略的"统一接口",声明算法的核心方法(如calculate )。 |
抽象基类(含纯虚函数)或纯虚接口类。 |
具体策略(ConcreteStrategy) | 实现抽象策略的接口,封装具体的算法逻辑(如普通用户折扣、VIP折扣)。 | 继承抽象策略类,重写纯虚函数。 |
上下文(Context) | 作为"算法使用者",持有抽象策略的指针/引用,提供接口让客户端设置/切换策略,并调用策略的算法。 | 类(含Strategy成员变量,提供setStrategy 和execute 方法)。 |
客户端(Client) | 创建具体策略对象,将其传递给上下文;决定"何时使用何种策略"(如根据用户等级选择折扣策略)。 | 通常是main 函数或业务逻辑模块。 |
1.3 C++中的关键实现技术:函数对象与std::function
在C++中,策略模式的实现有两种主流方式:基于抽象类的继承实现 (上文类图示例)和基于"可调用对象"的组合实现(更灵活)。后者依赖C++的"可调用对象"特性,核心技术包括:
(1)函数对象(Functor)
函数对象是"重载了operator()
的类/结构体",它既能像函数一样调用,又能持有状态(如日志策略的文件路径)。例如:
cpp
// 折扣策略:VIP函数对象
struct VIPFunctor {
// 重载operator(),实现折扣算法
double operator()(double amount) const {
return amount * 0.9;
}
};
// 使用:像函数一样调用
VIPFunctor vipStrategy;
double discounted = vipStrategy(100.0); // 结果90.0
(2)std::function(C++11及以上)
std::function
是C++11标准库提供的"通用可调用对象包装器",可容纳函数指针、lambda表达式、函数对象、成员函数等,极大简化策略模式的实现(无需定义抽象类和具体类)。例如:
cpp
#include <functional>
// 上下文持有std::function,而非抽象类指针
class DiscountContext {
private:
// 策略类型:接收double,返回double
std::function<double(double)> strategy;
public:
// 构造时设置默认策略
DiscountContext(std::function<double(double)> s) : strategy(s) {}
// 切换策略
void setStrategy(std::function<double(double)> s) {
strategy = s;
}
// 执行策略
double calculate(double amount) {
return strategy(amount);
}
};
// 客户端使用:直接传lambda作为策略
int main() {
// 普通用户策略(lambda)
auto normal = [](double a) { return a; };
// VIP策略(lambda)
auto vip = [](double a) { return a * 0.9; };
DiscountContext ctx(normal);
std::cout << ctx.calculate(100) << std::endl; // 100.0
// 切换为VIP策略
ctx.setStrategy(vip);
std::cout << ctx.calculate(100) << std::endl; // 90.0
return 0;
}
两种实现方式的对比
实现方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
抽象类继承 | 结构清晰,支持策略类持有复杂状态 | 类数量多(每个策略一个类),灵活性低 | 策略逻辑复杂、需持有状态(如日志文件路径) |
std::function | 无需定义类,支持lambda/函数指针,灵活 | 无法强制策略接口(依赖约定),类型擦除有轻微性能开销 | 策略逻辑简单、需快速切换(如排序比较) |
1.4 现状与趋势:策略模式在现代C++中的应用
随着C++11及后续标准的普及,策略模式的应用更加灵活:
- 结合lambda:无需定义具体策略类,直接用lambda表达式传递策略(如std::sort的比较函数);
- 与模板结合:通过模板参数指定策略类型,避免虚函数和std::function的性能开销(如STL容器的分配器策略);
- 框架级应用:在Qt(QSortFilterProxyModel的排序策略)、Boost(Boost.Algorithm的算法策略)等框架中,策略模式是实现"可定制化"的核心手段。
二、设计意图与考量:为什么策略模式是"组合优于继承"的典范?
策略模式的设计并非"为了模式而模式",而是为了解决具体问题。本节将深入剖析其设计理念、核心目标及权衡取舍。
2.1 核心设计目标:三大核心诉求
策略模式的设计围绕3个核心目标展开,这也是它能成为经典模式的原因:
目标1:解耦算法与客户,实现"算法独立变化"
通过将算法封装为独立策略,业务逻辑(Context)无需知道算法的具体实现------就像我们用手机拍照时,无需知道"美颜算法"的细节,只需切换"美颜等级"(策略)即可。例如:
- 新增"黑金用户7折"策略时,只需新增一个
BlackGoldStrategy
类,无需修改OrderContext
(订单上下文)的代码; - 修改"VIP折扣从9折改为8.5折"时,只需修改
VIPStrategy
的calculate
方法,不影响其他策略和上下文。
目标2:遵循"开闭原则",降低维护成本
"开闭原则"是面向对象设计的核心原则之一,策略模式完美契合:
- 对扩展开放:新增算法只需新增具体策略类;
- 对修改关闭:无需修改已有上下文和策略类。
对比"硬编码分支"的实现,策略模式将"修改代码"变为"新增代码",极大降低了引入bug的风险。
目标3:消除分支判断,简化代码逻辑
用"策略切换"代替"if-else/switch",让代码更简洁。例如:
- 硬编码方式:10种用户等级需要10个
else if
分支; - 策略模式:10种策略对应10个类,客户端通过"用户等级→策略映射"(如
std::map<std::string, std::unique_ptr<Strategy>>
)选择策略,无分支判断。
2.2 设计理念:"组合优于继承"的深度体现
策略模式是"组合优于继承"原则的最佳实践之一。我们通过对比继承实现与组合实现,理解其优越性:
继承实现的痛点(反面例子)
若用继承实现折扣功能,需让Order
类继承不同的折扣类:
cpp
// 继承实现(反面例子)
class Order {};
class NormalOrder : public Order { /* 无折扣 */ };
class VIPOrder : public Order { /* 9折 */ };
class SVIPOrder : public Order { /* 8折 */ };
问题:
- 类爆炸 :10种用户等级需要10个
Order
子类,难以维护; - 无法动态切换 :一个
NormalOrder
对象无法在运行时变为VIPOrder
(继承是编译期确定的); - 职责混乱 :
Order
的核心职责是"管理订单信息",折扣是"计算规则",继承导致职责耦合。
组合实现的优势(策略模式)
策略模式通过"Context持有Strategy"的组合关系,解决上述问题:
- 无类爆炸:Context类唯一,策略类可按需新增;
- 动态切换 :通过
setStrategy
方法,Context对象可在运行时切换策略(如普通用户升级为VIP); - 职责单一:Context负责"使用策略",Strategy负责"实现算法",符合"单一职责原则"。
用一句话总结:继承是"is-a"(Order是一种折扣),组合是"has-a"(Order有一个折扣策略) ------显然,"有一个"比"是一种"更灵活。
2.3 设计权衡:灵活性与成本的平衡
策略模式并非"银弹",使用时需权衡以下因素:
(1)灵活性提升 vs 类数量增加
- 优势:新增策略无需修改原有代码;
- 代价:每个策略对应一个类(若用继承实现),策略过多时会增加类的数量。
- 解决方案 :简单策略用
std::function
+lambda实现(无需定义类),复杂策略用类实现。
(2)性能开销:虚函数 vs std::function vs 模板
不同实现方式的性能差异如下表:
实现方式 | 性能开销来源 | 性能等级 | 适用场景 |
---|---|---|---|
抽象类+虚函数 | 虚函数表查询(运行时多态) | 中 | 策略数量多、需动态切换 |
std::function | 类型擦除(Type Erasure)和间接调用 | 中低 | 策略逻辑简单、需灵活容纳多种可调用对象 |
模板+函数对象 | 编译期多态(无运行时开销) | 高 | 性能敏感场景(如高频排序)、策略类型编译期确定 |
(3)客户端需了解策略差异
策略模式将"选择策略"的责任交给客户端------客户端必须知道"不同策略的区别"(如VIP和SVIP的折扣率),才能选择合适的策略。若策略差异复杂,需提供"策略工厂"辅助客户端选择(如DiscountStrategyFactory::createStrategy(userLevel)
)。
三、实战案例:4个真实场景的完整实现
理论讲完,我们通过4个覆盖不同业务场景的案例,从"需求分析→方案设计→代码实现→编译运行"全方位演示策略模式的落地。每个案例均提供完整可运行代码 、Doxygen风格注释 、Mermaid图表 、Makefile 及操作指南。
案例1:电商折扣系统(基础场景)
1.1 需求场景
某电商平台需要根据用户等级计算订单折扣:
- 普通用户(normal):无折扣;
- VIP用户(vip):9折;
- SVIP用户(svip):8折;
- 未来可能新增"黑金用户(blackgold):7折"。
1.2 方案设计
采用"抽象类+具体策略"的实现方式(策略需持有"用户等级"信息,用类更合适),角色分工:
- 抽象策略:
DiscountStrategy
(声明calculate
方法); - 具体策略:
NormalDiscount
、VIPDiscount
、SVIPDiscount
; - 上下文:
OrderContext
(管理订单金额,持有折扣策略); - 客户端:
main
函数(根据用户等级创建策略,传递给OrderContext
)。
1.3 完整代码实现
(1)头文件:DiscountStrategy.h
cpp
#ifndef DISCOUNT_STRATEGY_H
#define DISCOUNT_STRATEGY_H
#include <string>
#include <memory> // 用于std::unique_ptr
/**
* @brief 折扣策略抽象基类(抽象策略角色)
*
* 定义所有具体折扣策略的统一接口,声明折扣计算方法calculate。
* 所有具体折扣策略需继承此类并实现calculate方法。
*/
class DiscountStrategy {
public:
/**
* @brief 虚析构函数
*
* 确保子类对象通过基类指针销毁时,调用正确的析构函数,避免内存泄漏。
*/
virtual ~DiscountStrategy() = default;
/**
* @brief 计算折扣后金额(纯虚函数,子类必须实现)
*
* 根据具体折扣规则,计算订单的折扣后金额。
*
* @in:
* - originalAmount: 订单原始金额(单位:元,需≥0)
*
* @return:
* double - 折扣后金额(若originalAmount<0,返回-1.0表示错误)
*/
virtual double calculate(double originalAmount) const = 0;
/**
* @brief 获取策略对应的用户等级
*
* 用于标识当前策略对应的用户等级(如"normal"、"vip")。
*
* @return:
* std::string - 用户等级字符串
*/
virtual std::string getUserLevel() const = 0;
};
/**
* @brief 普通用户折扣策略(具体策略角色)
*
* 普通用户无折扣,折扣后金额=原始金额。
*/
class NormalDiscount : public DiscountStrategy {
public:
/**
* @brief 计算普通用户折扣后金额
*
* 普通用户无折扣,直接返回原始金额(若金额为负,返回-1.0)。
*
* @in:
* - originalAmount: 订单原始金额(单位:元)
*
* @return:
* double - 折扣后金额(originalAmount≥0时返回originalAmount,否则返回-1.0)
*/
double calculate(double originalAmount) const override;
/**
* @brief 获取用户等级
*
* @return:
* std::string - "normal"(普通用户)
*/
std::string getUserLevel() const override;
};
/**
* @brief VIP用户折扣策略(具体策略角色)
*
* VIP用户享受9折优惠,折扣后金额=原始金额×0.9。
*/
class VIPDiscount : public DiscountStrategy {
public:
/**
* @brief 计算VIP用户折扣后金额
*
* VIP用户享9折,返回原始金额×0.9(若金额为负,返回-1.0)。
*
* @in:
* - originalAmount: 订单原始金额(单位:元)
*
* @return:
* double - 折扣后金额(originalAmount≥0时返回originalAmount×0.9,否则返回-1.0)
*/
double calculate(double originalAmount) const override;
/**
* @brief 获取用户等级
*
* @return:
* std::string - "vip"(VIP用户)
*/
std::string getUserLevel() const override;
};
/**
* @brief SVIP用户折扣策略(具体策略角色)
*
* SVIP用户享受8折优惠,折扣后金额=原始金额×0.8。
*/
class SVIPDiscount : public DiscountStrategy {
public:
/**
* @brief 计算SVIP用户折扣后金额
*
* SVIP用户享8折,返回原始金额×0.8(若金额为负,返回-1.0)。
*
* @in:
* - originalAmount: 订单原始金额(单位:元)
*
* @return:
* double - 折扣后金额(originalAmount≥0时返回originalAmount×0.8,否则返回-1.0)
*/
double calculate(double originalAmount) const override;
/**
* @brief 获取用户等级
*
* @return:
* std::string - "svip"(SVIP用户)
*/
std::string getUserLevel() const override;
};
/**
* @brief 订单上下文(上下文角色)
*
* 管理订单信息,持有折扣策略对象,提供接口计算折扣后金额和切换策略。
*/
class OrderContext {
private:
double m_originalAmount; // 订单原始金额
std::unique_ptr<DiscountStrategy> m_strategy; // 持有折扣策略(智能指针,自动管理内存)
public:
/**
* @brief 构造函数
*
* 初始化订单原始金额和折扣策略。
*
* @in:
* - originalAmount: 订单原始金额(单位:元,需≥0)
* - strategy: 折扣策略对象(通过unique_ptr传递所有权)
*
* @note:
* 若originalAmount<0,会将m_originalAmount设为0,并输出警告。
*/
OrderContext(double originalAmount, std::unique_ptr<DiscountStrategy> strategy);
/**
* @brief 切换折扣策略
*
* 动态更换当前订单的折扣策略(如普通用户升级为VIP后切换策略)。
*
* @in:
* - newStrategy: 新的折扣策略对象(通过unique_ptr传递所有权)
*/
void setDiscountStrategy(std::unique_ptr<DiscountStrategy> newStrategy);
/**
* @brief 计算订单最终金额
*
* 调用当前持有的折扣策略,计算折扣后金额,并返回结果。
*
* @out:
* 无(仅返回计算结果)
*
* @return:
* double - 折扣后金额(若原始金额<0或策略计算错误,返回-1.0)
*/
double calculateFinalAmount() const;
/**
* @brief 获取当前订单信息(原始金额+用户等级)
*
* @return:
* std::string - 订单信息字符串(格式:"原始金额:X元,用户等级:Y")
*/
std::string getOrderInfo() const;
};
#endif // DISCOUNT_STRATEGY_H
(2)源文件:DiscountStrategy.cpp
cpp
#include "DiscountStrategy.h"
#include <iostream>
#include <iomanip> // 用于std::fixed和std::setprecision
// ------------------------------ NormalDiscount ------------------------------
double NormalDiscount::calculate(double originalAmount) const {
if (originalAmount < 0) {
std::cerr << "[警告] 订单金额为负(" << originalAmount << "元),无法计算折扣!" << std::endl;
return -1.0;
}
return originalAmount;
}
std::string NormalDiscount::getUserLevel() const {
return "normal";
}
// ------------------------------ VIPDiscount ------------------------------
double VIPDiscount::calculate(double originalAmount) const {
if (originalAmount < 0) {
std::cerr << "[警告] 订单金额为负(" << originalAmount << "元),无法计算折扣!" << std::endl;
return -1.0;
}
return originalAmount * 0.9;
}
std::string VIPDiscount::getUserLevel() const {
return "vip";
}
// ------------------------------ SVIPDiscount ------------------------------
double SVIPDiscount::calculate(double originalAmount) const {
if (originalAmount < 0) {
std::cerr << "[警告] 订单金额为负(" << originalAmount << "元),无法计算折扣!" << std::endl;
return -1.0;
}
return originalAmount * 0.8;
}
std::string SVIPDiscount::getUserLevel() const {
return "svip";
}
// ------------------------------ OrderContext ------------------------------
OrderContext::OrderContext(double originalAmount, std::unique_ptr<DiscountStrategy> strategy)
: m_strategy(std::move(strategy)) {
if (originalAmount < 0) {
std::cerr << "[警告] 初始化订单时金额为负(" << originalAmount << "元),已重置为0元!" << std::endl;
m_originalAmount = 0.0;
} else {
m_originalAmount = originalAmount;
}
}
void OrderContext::setDiscountStrategy(std::unique_ptr<DiscountStrategy> newStrategy) {
if (!newStrategy) {
std::cerr << "[警告] 新策略为空指针,无法切换!" << std::endl;
return;
}
m_strategy = std::move(newStrategy);
std::cout << "[信息] 折扣策略已切换为:" << m_strategy->getUserLevel() << "用户" << std::endl;
}
double OrderContext::calculateFinalAmount() const {
if (!m_strategy) {
std::cerr << "[错误] 未设置折扣策略,无法计算金额!" << std::endl;
return -1.0;
}
return m_strategy->calculate(m_originalAmount);
}
std::string OrderContext::getOrderInfo() const {
std::stringstream ss;
ss << std::fixed << std::setprecision(2);
ss << "原始金额:" << m_originalAmount << "元,用户等级:" << m_strategy->getUserLevel();
return ss.str();
}
(3)主函数:main.cpp
cpp
#include "DiscountStrategy.h"
#include <iostream>
#include <iomanip> // 用于格式化输出
/**
* @brief 打印订单计算结果
*
* 格式化输出订单信息和折扣后金额,若计算错误则提示。
*
* @in:
* - ctx: OrderContext对象(订单上下文)
* - finalAmount: 折扣后金额(由ctx.calculateFinalAmount()获取)
*/
void printResult(const OrderContext& ctx, double finalAmount) {
std::cout << std::fixed << std::setprecision(2); // 保留2位小数
std::cout << "------------------------------" << std::endl;
std::cout << "订单信息:" << ctx.getOrderInfo() << std::endl;
if (finalAmount < 0) {
std::cout << "计算结果:失败(无效金额或未设置策略)" << std::endl;
} else {
std::cout << "折扣后金额:" << finalAmount << "元" << std::endl;
}
std::cout << "------------------------------" << std::endl << std::endl;
}
int main() {
std::cout << "===== 电商折扣系统演示 =====" << std::endl << std::endl;
// 1. 创建普通用户订单
std::cout << "[步骤1] 普通用户订单计算:" << std::endl;
auto normalOrder = OrderContext(100.0, std::make_unique<NormalDiscount>());
double normalFinal = normalOrder.calculateFinalAmount();
printResult(normalOrder, normalFinal);
// 2. 创建VIP用户订单
std::cout << "[步骤2] VIP用户订单计算:" << std::endl;
auto vipOrder = OrderContext(100.0, std::make_unique<VIPDiscount>());
double vipFinal = vipOrder.calculateFinalAmount();
printResult(vipOrder, vipFinal);
// 3. 创建SVIP用户订单
std::cout << "[步骤3] SVIP用户订单计算:" << std::endl;
auto svipOrder = OrderContext(100.0, std::make_unique<SVIPDiscount>());
double svipFinal = svipOrder.calculateFinalAmount();
printResult(svipOrder, svipFinal);
// 4. 动态切换策略(普通用户升级为VIP)
std::cout << "[步骤4] 动态切换策略(普通用户→VIP):" << std::endl;
normalOrder.setDiscountStrategy(std::make_unique<VIPDiscount>());
double upgradedFinal = normalOrder.calculateFinalAmount();
printResult(normalOrder, upgradedFinal);
// 5. 测试无效金额(负金额)
std::cout << "[步骤5] 测试无效金额(负金额):" << std::endl;
auto invalidOrder = OrderContext(-50.0, std::make_unique<VIPDiscount>());
double invalidFinal = invalidOrder.calculateFinalAmount();
printResult(invalidOrder, invalidFinal);
return 0;
}
1.4 核心逻辑可视化(Mermaid时序图)
Client (main) OrderContext NormalDiscount VIPDiscount SVIPDiscount 1. 创建订单(100元, NormalDiscount) 2. 调用calculate(100) 3. 返回100.0 4. 返回最终金额100.0 5. 打印结果 6. 调用setStrategy(VIPDiscount) 7. 更新策略为VIPDiscount 8. 提示策略切换成功 9. 调用calculateFinalAmount() 10. 调用calculate(100) 11. 返回90.0 12. 返回最终金额90.0 13. 打印更新后结果 Client (main) OrderContext NormalDiscount VIPDiscount SVIPDiscount
1.5 Makefile配置
makefile
# 编译器设置
CXX = g++
# 编译选项:C++11标准,开启所有警告,生成调试信息
CXXFLAGS = -std=c++11 -Wall -g
# 目标可执行文件名称
TARGET = discount_system
# 源文件列表(所有.cpp文件)
SRCS = main.cpp DiscountStrategy.cpp
# 目标文件列表(将.cpp替换为.o)
OBJS = $(SRCS:.cpp=.o)
# 默认目标:编译生成可执行文件
all: $(TARGET)
# 链接目标文件,生成可执行文件
$(TARGET): $(OBJS)
$(CXX) $(CXXFLAGS) -o $@ $(OBJS)
@echo "编译完成!可执行文件:$(TARGET)"
# 编译源文件为目标文件($<表示当前依赖文件,$@表示当前目标文件)
%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $< -o $@
# 清理目标文件和可执行文件
clean:
rm -f $(OBJS) $(TARGET)
@echo "清理完成!"
# 伪目标:避免与同名文件冲突
.PHONY: all clean
1.6 操作说明
(1)编译方法
-
环境依赖:
- 编译器:GCC 4.8及以上(支持C++11)或Clang 3.3及以上;
- 操作系统:Linux/macOS(Windows需使用MinGW或WSL)。
-
编译命令:
在代码所在目录打开终端,执行:bashmake clean && make
make clean
:清理之前编译生成的目标文件(.o)和可执行文件;make
:编译源文件,生成可执行文件discount_system
。
(2)运行方式
编译成功后,执行以下命令运行程序:
bash
./discount_system
无需额外传参,程序会自动执行5个测试场景。
(3)结果解读
正常输出示例(保留2位小数):
===== 电商折扣系统演示 =====
[步骤1] 普通用户订单计算:
------------------------------
订单信息:原始金额:100.00元,用户等级:normal
折扣后金额:100.00元
------------------------------
[步骤2] VIP用户订单计算:
------------------------------
订单信息:原始金额:100.00元,用户等级:vip
折扣后金额:90.00元
------------------------------
[步骤3] SVIP用户订单计算:
------------------------------
订单信息:原始金额:100.00元,用户等级:svip
折扣后金额:80.00元
------------------------------
[步骤4] 动态切换策略(普通用户→VIP):
[信息] 折扣策略已切换为:vip用户
------------------------------
订单信息:原始金额:100.00元,用户等级:vip
折扣后金额:90.00元
------------------------------
[步骤5] 测试无效金额(负金额):
[警告] 初始化订单时金额为负(-50.00元),已重置为0元!
[警告] 订单金额为负(-0.00元),无法计算折扣!
------------------------------
订单信息:原始金额:0.00元,用户等级:vip
折扣后金额:-1.00元
------------------------------
异常输出及原因:
- 若提示"未设置折扣策略":可能是
OrderContext
构造时传递的strategy
为空指针; - 若提示"新策略为空指针":调用
setDiscountStrategy
时传递了空的unique_ptr
; - 若编译失败(如"error: 'make_unique' was not declared in this scope"):需确保编译器支持C++11(检查
CXXFLAGS
是否包含-std=c++11
)。
案例2:多渠道日志系统(复杂状态策略)
2.1 需求场景
某后端服务需要支持多渠道日志输出,且不同渠道需记录不同格式的日志:
- 控制台日志(ConsoleLog):彩色输出,格式为"[时间] [级别] 消息";
- 文件日志(FileLog):写入指定文件,格式为"时间|级别|消息";
- 数据库日志(DBLog):模拟写入MySQL,格式为"INSERT INTO log(...) VALUES(...)";
- 日志级别支持:DEBUG、INFO、ERROR。
2.2 方案设计
采用"抽象类+具体策略"实现,因策略需持有"输出目标"(如文件路径、数据库连接信息),用类更合适。角色分工:
- 抽象策略:
LogStrategy
(声明log
方法,接收日志级别和消息); - 具体策略:
ConsoleLogStrategy
、FileLogStrategy
、DBLogStrategy
; - 上下文:
Logger
(提供debug
/info
/error
接口,持有日志策略); - 辅助枚举:
LogLevel
(定义日志级别)。
2.3 完整代码实现(关键文件)
(1)头文件:LogStrategy.h
cpp
#ifndef LOG_STRATEGY_H
#define LOG_STRATEGY_H
#include <string>
#include <fstream> // 用于文件操作
#include <ctime> // 用于时间获取
/**
* @brief 日志级别枚举
*
* 定义日志的优先级:DEBUG(调试)< INFO(信息)< ERROR(错误)。
*/
enum class LogLevel {
DEBUG,
INFO,
ERROR
};
/**
* @brief 日志策略抽象基类(抽象策略角色)
*
* 定义所有日志输出策略的统一接口,声明日志记录方法log。
*/
class LogStrategy {
public:
virtual ~LogStrategy() = default;
/**
* @brief 记录日志(纯虚函数,子类必须实现)
*
* 根据具体策略,将日志消息输出到指定渠道(控制台/文件/数据库)。
*
* @in:
* - level: 日志级别(LogLevel枚举)
* - message: 日志消息内容(非空字符串)
*
* @return:
* bool - 日志记录成功返回true,失败返回false
*/
virtual bool log(LogLevel level, const std::string& message) = 0;
/**
* @brief 将LogLevel枚举转换为字符串
*
* 辅助方法,用于将枚举值转换为可读性更强的字符串(如DEBUG→"DEBUG")。
*
* @in:
* - level: 日志级别枚举
*
* @return:
* std::string - 日志级别字符串
*/
static std::string levelToString(LogLevel level);
/**
* @brief 获取当前系统时间(格式化)
*
* 辅助方法,返回当前时间的字符串(格式:YYYY-MM-DD HH:MM:SS)。
*
* @return:
* std::string - 格式化的当前时间
*/
static std::string getCurrentTime();
};
/**
* @brief 控制台日志策略(具体策略角色)
*
* 将日志输出到控制台,支持彩色显示(ERROR为红色,INFO为绿色,DEBUG为蓝色)。
*/
class ConsoleLogStrategy : public LogStrategy {
public:
/**
* @brief 记录控制台日志
*
* 根据日志级别设置颜色,输出格式:"[时间] [级别] 消息"。
*
* @in:
* - level: 日志级别
* - message: 日志消息
*
* @return:
* bool - 始终返回true(控制台输出无失败场景)
*/
bool log(LogLevel level, const std::string& message) override;
};
/**
* @brief 文件日志策略(具体策略角色)
*
* 将日志写入指定文件,格式:"时间|级别|消息"(追加模式)。
*/
class FileLogStrategy : public LogStrategy {
private:
std::string m_filePath; // 日志文件路径
std::ofstream m_file; // 文件输出流(持有文件句柄)
/**
* @brief 打开日志文件
*
* 以追加模式打开文件,若文件不存在则创建。
*
* @return:
* bool - 打开成功返回true,失败返回false
*/
bool openFile();
public:
/**
* @brief 构造函数
*
* 初始化日志文件路径,并尝试打开文件。
*
* @in:
* - filePath: 日志文件路径(如"./app.log")
*/
explicit FileLogStrategy(const std::string& filePath);
/**
* @brief 析构函数
*
* 关闭文件输出流,释放文件句柄。
*/
~FileLogStrategy() override;
/**
* @brief 记录文件日志
*
* 将日志追加到文件中,格式:"时间|级别|消息"。
*
* @in:
* - level: 日志级别
* - message: 日志消息
*
* @return:
* bool - 文件写入成功返回true,失败(如文件未打开)返回false
*/
bool log(LogLevel level, const std::string& message) override;
};
/**
* @brief 数据库日志策略(具体策略角色)
*
* 模拟将日志写入MySQL数据库,输出SQL语句(实际项目中需集成MySQL客户端库)。
*/
class DBLogStrategy : public LogStrategy {
private:
std::string m_dbHost; // 数据库主机地址
std::string m_dbUser; // 数据库用户名
std::string m_dbPass; // 数据库密码
std::string m_dbName; // 数据库名称
/**
* @brief 模拟数据库连接检查
*
* 模拟检查数据库连接是否正常(实际项目中需真实连接数据库)。
*
* @return:
* bool - 连接正常返回true,否则返回false
*/
bool isConnected() const;
public:
/**
* @brief 构造函数
*
* 初始化数据库连接信息。
*
* @in:
* - dbHost: 数据库主机(如"127.0.0.1")
* - dbUser: 用户名(如"root")
* - dbPass: 密码(如"123456")
* - dbName: 数据库名(如"app_log")
*/
DBLogStrategy(const std::string& dbHost, const std::string& dbUser,
const std::string& dbPass, const std::string& dbName);
/**
* @brief 记录数据库日志
*
* 生成INSERT SQL语句,模拟写入数据库(实际项目中需执行SQL)。
*
* @in:
* - level: 日志级别
* - message: 日志消息(需处理SQL注入,本案例简化)
*
* @return:
* bool - 模拟成功返回true,连接失败返回false
*/
bool log(LogLevel level, const std::string& message) override;
};
/**
* @brief 日志上下文(上下文角色)
*
* 提供统一的日志接口(debug/info/error),持有日志策略,支持动态切换。
*/
class Logger {
private:
std::unique_ptr<LogStrategy> m_strategy; // 日志策略
public:
/**
* @brief 构造函数
*
* 初始化日志策略(必须传递有效的策略对象)。
*
* @in:
* - strategy: 日志策略对象(通过unique_ptr传递所有权)
*
* @throw:
* std::invalid_argument - 若strategy为空指针,抛出异常
*/
explicit Logger(std::unique_ptr<LogStrategy> strategy);
/**
* @brief 切换日志策略
*
* 动态更换日志输出渠道(如从控制台切换到文件)。
*
* @in:
* - newStrategy: 新的日志策略对象
*
* @throw:
* std::invalid_argument - 若newStrategy为空指针,抛出异常
*/
void setLogStrategy(std::unique_ptr<LogStrategy> newStrategy);
/**
* @brief 记录DEBUG级别日志
*
* @in:
* - message: 日志消息
*
* @return:
* bool - 日志记录成功返回true,失败返回false
*/
bool debug(const std::string& message);
/**
* @brief 记录INFO级别日志
*
* @in:
* - message: 日志消息
*
* @return:
* bool - 日志记录成功返回true,失败返回false
*/
bool info(const std::string& message);
/**
* @brief 记录ERROR级别日志
*
* @in:
* - message: 日志消息
*
* @return:
* bool - 日志记录成功返回true,失败返回false
*/
bool error(const std::string& message);
};
#endif // LOG_STRATEGY_H
(2)主函数:main.cpp(核心逻辑)
cpp
#include "LogStrategy.h"
#include <iostream>
int main() {
std::cout << "===== 多渠道日志系统演示 =====" << std::endl << std::endl;
// 1. 控制台日志测试
std::cout << "[1] 测试控制台日志(彩色输出):" << std::endl;
auto consoleLogger = Logger(std::make_unique<ConsoleLogStrategy>());
consoleLogger.debug("初始化配置文件:config.json");
consoleLogger.info("服务启动成功,端口:8080");
consoleLogger.error("数据库连接超时,重试次数:3");
std::cout << std::endl;
// 2. 文件日志测试
std::cout << "[2] 测试文件日志(写入./app.log):" << std::endl;
auto fileLogger = Logger(std::make_unique<FileLogStrategy>("./app.log"));
bool fileDebug = fileLogger.debug("用户登录:username=test");
bool fileInfo = fileLogger.info("订单创建:orderId=12345");
bool fileError = fileLogger.error("支付失败:insufficient balance");
std::cout << "文件日志记录结果:DEBUG=" << (fileDebug ? "成功" : "失败")
<< ", INFO=" << (fileInfo ? "成功" : "失败")
<< ", ERROR=" << (fileError ? "成功" : "失败") << std::endl;
std::cout << "提示:可通过'cat ./app.log'查看日志文件" << std::endl << std::endl;
// 3. 数据库日志测试
std::cout << "[3] 测试数据库日志(模拟MySQL):" << std::endl;
auto dbLogger = Logger(std::make_unique<DBLogStrategy>(
"127.0.0.1", "root", "123456", "app_log"
));
dbLogger.debug("清理过期日志:保留30天");
dbLogger.info("用户注册:email=test@example.com");
dbLogger.error("API调用失败:404 Not Found");
std::cout << std::endl;
// 4. 动态切换策略(文件→控制台)
std::cout << "[4] 动态切换策略(文件日志→控制台日志):" << std::endl;
fileLogger.setLogStrategy(std::make_unique<ConsoleLogStrategy>());
fileLogger.info("策略切换后:记录一条INFO日志");
fileLogger.error("策略切换后:记录一条ERROR日志");
return 0;
}
2.4 Makefile与操作说明
(1)Makefile
makefile
CXX = g++
CXXFLAGS = -std=c++11 -Wall -g
TARGET = log_system
SRCS = main.cpp LogStrategy.cpp
OBJS = $(SRCS:.cpp=.o)
all: $(TARGET)
$(TARGET): $(OBJS)
$(CXX) $(CXXFLAGS) -o $@ $(OBJS)
@echo "编译完成!可执行文件:$(TARGET)"
%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET) ./app.log # 清理日志文件
@echo "清理完成!"
.PHONY: all clean
(2)运行结果解读
-
控制台日志:ERROR级日志为红色,INFO为绿色,DEBUG为蓝色(依赖终端支持ANSI颜色码);
-
文件日志:在当前目录生成
app.log
,内容示例:2024-05-20 15:30:45|DEBUG|用户登录:username=test 2024-05-20 15:30:45|INFO|订单创建:orderId=12345 2024-05-20 15:30:45|ERROR|支付失败:insufficient balance
-
数据库日志:输出模拟SQL语句,示例:
[DB Log] INSERT INTO log(time, level, message) VALUES('2024-05-20 15:30:45', 'INFO', '用户注册:email=test@example.com');
案例3:动态排序系统(std::function实现)
3.1 需求场景
某数据处理工具需要支持多种排序策略,且排序规则可由用户自定义:
- 整数数组排序:支持升序、降序、绝对值升序;
- 字符串数组排序:支持字典序升序、字典序降序、长度升序;
- 无需定义大量策略类,用
std::function
简化实现。
3.2 方案设计
采用"std::function+lambda"实现,无需抽象类,直接用std::function
作为策略类型:
- 上下文:
Sorter
(持有排序策略std::function<bool(T, T)>
,提供sort
方法); - 客户端:通过lambda表达式传递排序策略(如升序
[](int a, int b){return a < b;}
)。
3.3 完整代码实现
cpp
#include <iostream>
#include <vector>
#include <functional> // 用于std::function
#include <algorithm> // 用于std::sort
#include <string>
/**
* @brief 排序上下文类(通用模板类,支持任意可比较类型)
*
* 持有排序策略(std::function),提供sort方法对容器进行排序。
*
* @tparam T:容器中元素的类型(需支持策略中的比较操作)
*/
template <typename T>
class Sorter {
private:
// 排序策略:接收两个T类型元素,返回bool(true表示第一个元素应排在前面)
std::function<bool(const T&, const T&)> m_strategy;
public:
/**
* @brief 构造函数
*
* 初始化排序策略(必须传递有效的策略)。
*
* @in:
* - strategy: 排序策略(std::function<bool(const T&, const T&)>)
*
* @throw:
* std::invalid_argument - 若strategy为空,抛出异常
*/
explicit Sorter(std::function<bool(const T&, const T&)> strategy) {
if (!strategy) {
throw std::invalid_argument("排序策略不能为空!");
}
m_strategy = strategy;
}
/**
* @brief 切换排序策略
*
* 动态更换排序规则(如从升序切换到降序)。
*
* @in:
* - newStrategy: 新的排序策略
*
* @throw:
* std::invalid_argument - 若newStrategy为空,抛出异常
*/
void setStrategy(std::function<bool(const T&, const T&)> newStrategy) {
if (!newStrategy) {
throw std::invalid_argument("新排序策略不能为空!");
}
m_strategy = newStrategy;
std::cout << "[信息] 排序策略已切换" << std::endl;
}
/**
* @brief 对容器进行排序
*
* 使用当前持有的策略,对传入的vector容器进行排序(原地排序)。
*
* @in/out:
* - data: 待排序的vector容器(排序后内容被修改)
*
* @return:
* void
*/
void sort(std::vector<T>& data) const {
if (data.empty()) {
std::cout << "[警告] 待排序容器为空,无需排序" << std::endl;
return;
}
// 调用STL的sort,传入策略作为比较函数(STL sort的策略模式应用)
std::sort(data.begin(), data.end(), m_strategy);
}
/**
* @brief 打印容器内容
*
* 辅助方法,格式化输出容器中的元素。
*
* @in:
* - data: 待打印的vector容器
* - title: 输出标题(如"升序排序结果")
*
* @return:
* void
*/
static void printData(const std::vector<T>& data, const std::string& title) {
std::cout << title << ":";
for (size_t i = 0; i < data.size(); ++i) {
if (i > 0) {
std::cout << ", ";
}
std::cout << data[i];
}
std::cout << std::endl;
}
};
// ------------------------------ 辅助函数:生成随机整数数组 ------------------------------
std::vector<int> generateRandomInts(int count, int min = 0, int max = 100) {
std::vector<int> data;
srand(time(nullptr)); // 初始化随机种子
for (int i = 0; i < count; ++i) {
// 生成min到max之间的随机数(包含负数)
int num = min + rand() % (max - min + 1);
if (rand() % 2 == 0) {
num = -num; // 50%概率为负数
}
data.push_back(num);
}
return data;
}
// ------------------------------ 主函数:测试不同排序策略 ------------------------------
int main() {
std::cout << "===== 动态排序系统演示 =====" << std::endl << std::endl;
// ------------------------------ 1. 整数数组排序 ------------------------------
std::cout << "[1] 整数数组排序测试:" << std::endl;
auto intData = generateRandomInts(5, -20, 20); // 生成5个-20~20的随机整数
Sorter<int>::printData(intData, "原始数组");
// 1.1 升序排序(策略:a < b)
auto intSorter = Sorter<int>([](const int& a, const int& b) {
return a < b;
});
auto dataAsc = intData;
intSorter.sort(dataAsc);
Sorter<int>::printData(dataAsc, "升序排序结果");
// 1.2 切换为降序排序(策略:a > b)
intSorter.setStrategy([](const int& a, const int& b) {
return a > b;
});
auto dataDesc = intData;
intSorter.sort(dataDesc);
Sorter<int>::printData(dataDesc, "降序排序结果");
// 1.3 切换为绝对值升序(策略:abs(a) < abs(b))
intSorter.setStrategy([](const int& a, const int& b) {
return std::abs(a) < std::abs(b);
});
auto dataAbsAsc = intData;
intSorter.sort(dataAbsAsc);
Sorter<int>::printData(dataAbsAsc, "绝对值升序排序结果");
std::cout << std::endl;
// ------------------------------ 2. 字符串数组排序 ------------------------------
std::cout << "[2] 字符串数组排序测试:" << std::endl;
std::vector<std::string> strData = {"apple", "banana", "cherry", "date", "elderberry"};
Sorter<std::string>::printData(strData, "原始数组");
// 2.1 字典序升序(默认string比较)
auto strSorter = Sorter<std::string>([](const std::string& a, const std::string& b) {
return a < b;
});
auto strAsc = strData;
strSorter.sort(strAsc);
Sorter<std::string>::printData(strAsc, "字典序升序结果");
// 2.2 切换为字典序降序
strSorter.setStrategy([](const std::string& a, const std::string& b) {
return a > b;
});
auto strDesc = strData;
strSorter.sort(strDesc);
Sorter<std::string>::printData(strDesc, "字典序降序结果");
// 2.3 切换为长度升序
strSorter.setStrategy([](const std::string& a, const std::string& b) {
return a.size() < b.size();
});
auto strLenAsc = strData;
strSorter.sort(strLenAsc);
Sorter<std::string>::printData(strLenAsc, "长度升序结果");
return 0;
}
3.4 关键说明
- 本案例使用模板类
Sorter
,支持任意可比较类型(int、string等); std::function<bool(const T&, const T&)>
作为策略类型,直接兼容lambda、函数指针、函数对象;- STL的
std::sort
本身就是策略模式的应用------其第三个参数(比较函数)就是"排序策略",本案例的Sorter
类正是对std::sort
的封装,让策略切换更直观。
案例4:多支付系统(策略工厂+动态切换)
4.1 需求场景
某电商平台需要支持多种支付方式,且每种支付方式的流程不同:
- 微信支付(WeChatPay):调用微信支付API,需传递openid;
- 支付宝支付(Alipay):调用支付宝API,需传递user_id;
- 银联支付(UnionPay):调用银联API,需传递card_no;
- 支付结果需返回"成功/失败"及交易单号。
4.2 方案设计
采用"抽象类+具体策略+策略工厂"实现,解决"客户端选择策略复杂"的问题:
- 抽象策略:
PaymentStrategy
(声明pay
方法); - 具体策略:
WeChatPayStrategy
、AlipayStrategy
、UnionPayStrategy
; - 策略工厂:
PaymentStrategyFactory
(根据支付类型创建策略,简化客户端调用);