设计模式(C++)详解——策略模式(2) - 指南

news/2025/10/21 15:12:19/文章来源:https://www.cnblogs.com/slgkaifa/p/19155444

设计模式(C++)详解——策略模式(2) - 指南

一、背景与核心概念:从“代码泥潭”到“策略解耦”

要理解策略模式,我们得先从它要解决的“痛点”说起——在没有策略模式的年代,开发者是如何处理“多算法可选”的场景?

1.1 策略模式的起源:解耦“算法耦合”的历史需求

在结构化编程或早期面向对象开发中,当业务需要“根据不同条件选择不同算法”时,开发者常采用**“硬编码分支”** 实现,例如一个电商折扣计算功能:

// 硬编码实现折扣计算(反面例子)
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类图直观展示其关系(遵循用户指定的语法规范):

classDiagramdirection TBclass Strategy {<>+ ~Strategy()+ virtual calculate(amount: double) : double = 0}class NormalStrategy {+ calculate(amount: double) : double}class VIPStrategy {+ calculate(amount: double) : double}class SVIPStrategy {+ calculate(amount: double) : double}class Context {- strategy: Strategy*+ Context(strategy: Strategy*)+ setStrategy(strategy: Strategy*) : void+ execute(amount: double) : double}Context "1" o--> "1" Strategy : holds (组合关系)Strategy <|-- NormalStrategy : inheritsStrategy <|-- VIPStrategy : inheritsStrategy <|-- SVIPStrategy : inheritsNote over Strategy,Context: 核心:Context通过组合持有Strategy,而非继承

对上述角色的详细解读如下表:

角色名称核心职责实现方式(C++)
抽象策略(Strategy)定义所有具体策略的“统一接口”,声明算法的核心方法(如calculate)。抽象基类(含纯虚函数)或纯虚接口类。
具体策略(ConcreteStrategy)实现抽象策略的接口,封装具体的算法逻辑(如普通用户折扣、VIP折扣)。继承抽象策略类,重写纯虚函数。
上下文(Context)作为“算法使用者”,持有抽象策略的指针/引用,提供接口让客户端设置/切换策略,并调用策略的算法。类(含Strategy成员变量,提供setStrategyexecute方法)。
客户端(Client)创建具体策略对象,将其传递给上下文;决定“何时使用何种策略”(如根据用户等级选择折扣策略)。通常是main函数或业务逻辑模块。

1.3 C++中的关键实现技术:函数对象与std::function

在C++中,策略模式的实现有两种主流方式:基于抽象类的继承实现(上文类图示例)和基于“可调用对象”的组合实现(更灵活)。后者依赖C++的“可调用对象”特性,核心技术包括:

(1)函数对象(Functor)

函数对象是“重载了operator()的类/结构体”,它既能像函数一样调用,又能持有状态(如日志策略的文件路径)。例如:

// 折扣策略: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表达式、函数对象、成员函数等,极大简化策略模式的实现(无需定义抽象类和具体类)。例如:

#include <functional>// 上下文持有std::function,而非抽象类指针class DiscountContext {private:// 策略类型:接收double,返回doublestd::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.0return 0;}
两种实现方式的对比
实现方式优点缺点适用场景
抽象类继承结构清晰,支持策略类持有复杂状态类数量多(每个策略一个类),灵活性低策略逻辑复杂、需持有状态(如日志文件路径)
std::function无需定义类,支持lambda/函数指针,灵活无法强制策略接口(依赖约定),类型擦除有轻微性能开销策略逻辑简单、需快速切换(如排序比较)

1.4 现状与趋势:策略模式在现代C++中的应用

随着C++11及后续标准的普及,策略模式的应用更加灵活:

二、设计意图与考量:为什么策略模式是“组合优于继承”的典范?

策略模式的设计并非“为了模式而模式”,而是为了解决具体问题。本节将深入剖析其设计理念、核心目标及权衡取舍。

2.1 核心设计目标:三大核心诉求

策略模式的设计围绕3个核心目标展开,这也是它能成为经典模式的原因:

目标1:解耦算法与客户,实现“算法独立变化”

通过将算法封装为独立策略,业务逻辑(Context)无需知道算法的具体实现——就像我们用手机拍照时,无需知道“美颜算法”的细节,只需切换“美颜等级”(策略)即可。例如:

  • 新增“黑金用户7折”策略时,只需新增一个BlackGoldStrategy类,无需修改OrderContext(订单上下文)的代码;
  • 修改“VIP折扣从9折改为8.5折”时,只需修改VIPStrategycalculate方法,不影响其他策略和上下文。
目标2:遵循“开闭原则”,降低维护成本

“开闭原则”是面向对象设计的核心原则之一,策略模式完美契合:

对比“硬编码分支”的实现,策略模式将“修改代码”变为“新增代码”,极大降低了引入bug的风险。

目标3:消除分支判断,简化代码逻辑

用“策略切换”代替“if-else/switch”,让代码更简洁。例如:

  • 硬编码方式:10种用户等级需要10个else if分支;
  • 策略模式:10种策略对应10个类,客户端通过“用户等级→策略映射”(如std::map<std::string, std::unique_ptr<Strategy>>)选择策略,无分支判断。

2.2 设计理念:“组合优于继承”的深度体现

策略模式是“组合优于继承”原则的最佳实践之一。我们通过对比继承实现与组合实现,理解其优越性:

继承实现的痛点(反面例子)

若用继承实现折扣功能,需让Order类继承不同的折扣类:

// 继承实现(反面例子)
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 需求场景

某电商平台需要根据用户等级计算订单折扣:

1.2 方案设计

采用“抽象类+具体策略”的实现方式(策略需持有“用户等级”信息,用类更合适),角色分工:

  • 抽象策略:DiscountStrategy(声明calculate方法);
  • 具体策略:NormalDiscountVIPDiscountSVIPDiscount
  • 上下文:OrderContext(管理订单金额,持有折扣策略);
  • 客户端:main函数(根据用户等级创建策略,传递给OrderContext)。
1.3 完整代码实现
(1)头文件:DiscountStrategy.h
#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
#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
#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)OrderContextNormalDiscountVIPDiscountSVIPDiscount1. 创建订单(100元, NormalDiscount)2. 调用calculate(100)3. 返回100.04. 返回最终金额100.05. 打印结果6. 调用setStrategy(VIPDiscount)7. 更新策略为VIPDiscount8. 提示策略切换成功9. 调用calculateFinalAmount()10. 调用calculate(100)11. 返回90.012. 返回最终金额90.013. 打印更新后结果Client (main)OrderContextNormalDiscountVIPDiscountSVIPDiscount
1.5 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)编译方法
  1. 环境依赖:
    • 编译器:GCC 4.8及以上(支持C++11)或Clang 3.3及以上;
    • 操作系统:Linux/macOS(Windows需使用MinGW或WSL)。
  2. 编译命令:
    在代码所在目录打开终端,执行:
    make clean && make
    • make clean:清理之前编译生成的目标文件(.o)和可执行文件;
    • make:编译源文件,生成可执行文件discount_system
(2)运行方式

编译成功后,执行以下命令运行程序:

./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 需求场景

某后端服务需要支持多渠道日志输出,且不同渠道需记录不同格式的日志:

2.2 方案设计

采用“抽象类+具体策略”实现,因策略需持有“输出目标”(如文件路径、数据库连接信息),用类更合适。角色分工:

  • 抽象策略:LogStrategy(声明log方法,接收日志级别和消息);
  • 具体策略:ConsoleLogStrategyFileLogStrategyDBLogStrategy
  • 上下文:Logger(提供debug/info/error接口,持有日志策略);
  • 辅助枚举:LogLevel(定义日志级别)。
2.3 完整代码实现(关键文件)
(1)头文件:LogStrategy.h
#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(核心逻辑)
#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
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 需求场景

某数据处理工具需要支持多种排序策略,且排序规则可由用户自定义:

3.2 方案设计

采用“std::function+lambda”实现,无需抽象类,直接用std::function作为策略类型:

  • 上下文:Sorter(持有排序策略std::function<bool(T, T)>,提供sort方法);
  • 客户端:通过lambda表达式传递排序策略(如升序[](int a, int b){return a < b;})。
3.3 完整代码实现
#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 方案设计

采用“抽象类+具体策略+策略工厂”实现,解决“客户端选择策略复杂”的问题:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/942308.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

粗略写一个计算器 实现加减乘除 - I

//作业:写一个计算器,要求实现加减乘除功能,并且能够循环接受新的数据,通过用户交互实现 package com.zhang.method;import java.util.Scanner;public class homeworkDemo {public static void main(String[] args…

WPF使用MediaCapture开发相机应用(三、相机拍照)

上期弄好了相机的预览功能,现在可以进行拍照了。对上期有疑惑的点这里。 MediaCapture的教程是用UWP写的,教程里拍照是用MediaCapture.CapturePhotoToStorageFileAsync方法,老实说我研究了好久都没搞明白WPF什么创建…

2025年磨粉机厂家权威推荐榜:雷蒙磨粉机/环辊磨粉机/摆式磨粉机/矿石磨粉机/超细磨粉机/高压磨粉机,专业实力与高效生产之选

2025年磨粉机厂家权威推荐榜:雷蒙磨粉机/环辊磨粉机/摆式磨粉机/矿石磨粉机/超细磨粉机/高压磨粉机,专业实力与高效生产之选 在工业制造领域,磨粉设备作为物料加工的核心装备,其技术水平直接影响着生产效率和产品质…

我的第一份开源贡献:小米工程师程赛的社区之旅

打开链接点亮社区Star,照亮技术的前进之路。每一个点赞,都是社区技术大佬前进的动力Github 地址: https://github.com/secretflow/secretflow他是从校园走进开源世界的年轻工程师; 他在小米工作,却把开源当成生活…

2025陶瓷过滤机实力厂家推荐,铜陵杰达机械专注固液分离设备制造

2025陶瓷过滤机实力厂家推荐,铜陵杰达机械专注固液分离设备制造 固液分离领域的技术挑战与创新突破 在当前的工业生产中,固液分离设备作为关键环节,面临着诸多技术挑战。根据行业数据显示,传统过滤设备在处理微细颗…

2025信息流代运营公司推荐:线尚网络专注效果营销与品牌增长

2025信息流代运营公司推荐:线尚网络专注效果营销与品牌增长 信息流代运营领域面临的技术挑战 随着数字营销进入深水区,信息流代运营行业正面临前所未有的技术挑战。据行业数据显示,超过67%的企业在信息流广告投放中…

2025冷链解冻设备厂家推荐广东科恩,专业定制高湿静电解冻方案

2025冷链解冻技术新突破:专业定制高湿静电解冻方案引领行业变革 在食品工业快速发展的今天,解冻设备作为冷链环节中的重要组成部分,其技术水平直接影响着食品品质与安全。随着2025年的临近,冷链解冻设备行业正迎来…

SecureCRT 批量创建会话-cnblog

SecureCRT 批量创建会话 前言: 在使用CRT的过程中,如果需要创建大量的会话连接 如:使用telnet连接ensp的网络设备 如:一次性需要连接多台linux主机 开篇: 下载SecureCRT:https://www.vandyke.com/download/ 这里…

2025干燥设备厂家权威推荐:常州亿干专业定制实验室喷雾与真空耙式干燥机

2025干燥设备厂家权威推荐:常州亿干专业定制实验室喷雾与真空耙式干燥机 一、干燥设备行业面临的技术挑战 在当今工业生产领域,干燥设备作为关键工艺装备,其技术水平直接影响着产品质量和生产效率。小型喷雾干燥机、…

2025磨粉机厂家权威推荐:海城八里镇机械厂专注矿石超细研磨设备制造

2025磨粉机厂家权威推荐:海城八里镇机械厂专注矿石超细研磨设备制造 行业技术挑战与创新需求 在当前的工业制造领域,磨粉设备的技术革新正面临着前所未有的挑战。随着矿产资源的深度开发和精细化加工需求的提升,传统…

Vue技术之Vxe-Table的虚拟滚动

虚拟滚动 纵向虚拟滚动:scroll-y 横向虚拟滚动:scroll-x 1.首先留意项目中使用的版本,我这边在项目用的是"vxe-pc-ui": "4.0.95"版本"vxe-table": "4.7.65"版本 所以scrol…

实用指南:一次借助ChatGPT抵御恶意攻击的经历,为个人服务器添加自动防御系统Fail2ban

实用指南:一次借助ChatGPT抵御恶意攻击的经历,为个人服务器添加自动防御系统Fail2banpre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important;…

Docker补充

Docker知识点学习。目录Docker实际应用docker 管理镜像操作容器操作Dockerfile命令学习一些Docker的图解 Docker实际应用 docker 管理 镜像操作搜索对应镜像docker serarch xxx查看现有镜像docker images docker imag…

【QNX】Socket ServerClient 源代码

1 Socket Server 源代码 服务端计划只启动一个线程,所以功能实现时使用了一些全局变量。1 int32_t skt_s_listen_fd{-1};2 pthread_t thr_server;3 struct sockaddr_un srv_addr;4 struct sockaddr_un cli_addr;5 std…

Linux环境--文件系统--动静态库

Linux环境--文件系统--动静态库2025-10-21 14:44 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !importa…

详细介绍:大模型落地的四大核心引擎:从技术突破到产业重构

详细介绍:大模型落地的四大核心引擎:从技术突破到产业重构pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Conso…

2025机电安装厂家推荐:太仓华芃专注工业设备安装,实力厂家可靠之选

2025机电安装厂家推荐:太仓华芃专注工业设备安装,实力厂家可靠之选 工业设备安装领域的技术挑战与行业现状 随着制造业向智能化、精密化方向快速发展,工业设备安装行业正面临着前所未有的技术挑战。据统计数据显示,…

EasyCVR视频汇聚平台GB28181级联异常排查:上级订阅信息无响应的根源解析

EasyCVR视频汇聚平台GB28181级联异常排查:上级订阅信息无响应的根源解析在视频监控平台的级联部署中,GB28181协议的稳定运行是保障上下级平台数据互通的核心。最近我们收到用户反馈:EasyCVR向下级平台级联时,状态显…