1. 设计模式

设计模式是软件开发中反复出现的问题的通用解决方案,它是一套套被反复使用、多数人知晓、经过分类编目的代码设计经验总结

设计模式并非具体的代码实现,而是针对特定问题的抽象设计思路和方法论。它描述了在特定场景下,如何组织类、对象、接口等元素,以解决常见的设计问题,比如如何降低代码耦合度、提高复用性、增强可维护性等。

设计模式主要有以下几个特点

  • 通用性:适用于不同的编程语言和应用场景,只要面临相似的设计问题,就可以借鉴相应模式。
  • 经验性:是众多开发者在长期实践中总结出来的最佳实践,经过了实践的验证。
  • 抽象性:关注的是结构和交互关系,而非具体实现细节。

常见的设计模式分类(以经典的 GoF 设计模式为例):

  • 创建型模式:用于处理对象创建机制,如单例模式(保证一个类仅有一个实例)、工厂模式(隐藏对象创建的细节)、建造者模式(分步构建复杂对象)等。
  • 结构型模式:关注类和对象的组合方式,如适配器模式(使不兼容的接口能一起工作)、装饰器模式(动态给对象添加功能)、代理模式(为对象提供代理以控制访问)等。
  • 行为型模式:描述对象之间的交互和职责分配,如观察者模式(对象状态变化时通知依赖它的对象)、策略模式(封装不同算法,可动态切换)、迭代器模式(提供遍历集合的统一接口)等。

合理使用设计模式可以让代码更具可读性灵活性可扩展性,尤其在大型项目开发中,能帮助团队形成共识,提高协作效率。但需注意,设计模式并非银弹,不应过度使用,而应根据实际问题选择合适的模式。

2. 策略模式

策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法(或行为),并将每个算法封装起来,使它们可以相互替换。这种模式让算法的变化独立于使用算法的客户端,从而实现了算法的灵活切换和复用。

当一个问题有多种解决方案(算法),且这些方案可能随需求变化时,策略模式可以避免使用大量的if-else或switch语句,使代码更清晰、易维护。

策略模式的核心思想分离算法的定义与使用

  • 将不同的算法(策略)封装成独立的类
  • 客户端通过统一接口调用不同的策略,无需关心具体实现
  • 可以在运行时动态切换策略,而不影响客户端代码。

策略模式通常包含三个核心角色:

  • 环境类(Context): 持有策略对象的引用;提供接口给客户端使用,本身不实现具体算法;负责在运行时切换策略。
  • 抽象策略接口(Strategy): 定义所有具体策略类的公共接口;声明算法的核心方法。
  • 具体策略类(Concrete Strategy): 实现抽象策略接口,提供具体的算法实现;可以有多个不同的具体策略类。

简单来说,环境类负责目标功能的大部分实现,而有多种策略可选的核心算法部分则交由具体策略类来实现。环境类包含抽象策略接口类的引用(或指针),指向具体策略类,环境类通过该引用或者指针来调用具体策略类提供的具体算法。

3. 用策略模式设计日志类

我们的目标是输出如下格式的日志:

[2025-07-21 14:10:56] [DEBUG] [244601] [main.cpp] [11] - hello world!
[2025-07-21 14:10:56] [DEBUG] [244601] [main.cpp] [12] - hello world!
[2025-07-21 14:10:56] [DEBUG] [244601] [main.cpp] [13] - hello world![时间] [日志等级] [进程pid] [程序对应的源文件] [行数] - 日志信息

其中,日志等级最常见的划分方法是分为五个等级:

enum class LogLevel
{DEBUG,    // 调试信息INFO,     // 普通信息WARNNING, // 警告ERROR,    // 错误(并未导致程序退出)FATAL     // 致命(导致程序退出)
};

将日志输出到什么地方?这是日志类最核心的可选策略部分,据此我们可以确定以下设计:

  • 环境类:Log日志类,负责日志信息的生成与封装。
  • 抽象策略接口:LogStrategy日志策略类,负责提供Write接口用于输出日志。
  • 具体策略类:实现Write接口,将日志输出到不同的目的地。

3.1 日志策略类

class LogStrategy
{
public:virtual ~LogStrategy() = default;virtual void Write(const std::string &message) = 0;protected:MutexModule::mutex _mutex;
};

这里的互斥锁是为了保证在多线程环境下的互斥输出,避免各个线程输出的日志之间相互杂糅。

3.2 具体策略类

// 控制台输出策略
class ConsoleStrategy : public LogStrategy
{
public:void Write(const std::string &message) override{MutexModule::LockGuard lockguard(_mutex);std::cout << message << std::endl;}
};// 文件输出策略
class FileStrategy : public LogStrategy
{
public:explicit FileStrategy(const std::string &path = "./log/test.log"): _file(path, std::ios::app){if (!_file.is_open()){throw std::runtime_error("无法打开日志文件: " + path);}}void Write(const std::string &message) override{MutexModule::LockGuard lockguard(_mutex);_file << message << std::endl;}~FileStrategy(){_file.close();}private:std::ofstream _file;
};

3.3 日志类

首先我们要明确日志类的大致框架:

  • 成员变量:指向策略对象的指针(抽象类无法实例化,只能用其指针指向具体策略类)。
  • 构造函数:初始化策略对象指针,默认采用控制台输出策略。
  • 成员函数:设置策略的接口,完成日志输出的接口。
class Logger
{
public:// 构造时指定策略(默认控制台输出)explicit Logger(std::unique_ptr<LogStrategy> strategy = std::make_unique<ConsoleStrategy>()): _strategy(std::move(strategy)){}void SetStrategy(std::unique_ptr<LogStrategy> strategy){_strategy = move(strategy);}// 日志输出接口void Log(){}private:std::unique_ptr<LogStrategy> _strategy;
};

接下来,就差日志输出的接口需要完成,在使用时,我们希望能达到如下效果:

#include <iostream>
#include "log.hpp"
using namespace LogModule;int main()
{Logger logger;logger.Log(LogLevel::DEBUG, __FILE__, __LINE__) << "hello" << " world!";
}

也就是支持可变参数,并用流插入的形式传参。为了实现这一点,我们需要设计一个内部类,作为Log函数的返回值。

这个内部类需要重载 " << " 操作符,并在析构函数当中,将拼接起来的日志输出:

class Message
{
public:Message(LogLevel level, const std::string filename, int line, Logger &logger): _logger(logger){char buffer[128];sprintf(buffer, "[%s] [%s] [%d] [%s] [%d] - ",GetTimeStamp().c_str(), LevelToString(level).c_str(), getpid(), filename.c_str(), line);_loginfo = buffer;}template <typename T>Message &operator<<(const T &message){_loginfo += message;return *this;}~Message(){_logger._strategy->Write(_loginfo);}private:Logger &_logger;      // 引用外部logger类, 方便使用策略进行刷新std::string _loginfo; // 一条合并完成的,完整的日志信息
};

于是,我们可以这样来设计Log函数:

Message Log(LogLevel level, const std::string filename, int line)
{return Message(level, filename, line, *this);
}

除此之外,为了使得日志输出函数的使用更加方便,我们还可以提供仿函数接口:

Message operator()(LogLevel level, const std::string filename, int line)
{return Message(level, filename, line, *this);
}或者Message operator()(LogLevel level, const std::string filename, int line)
{return Log(level, filename, line);
}

3.4 使用宏来简化操作

我们注意到在调用Log或者伪函数时,每次都传入的后两个参数是固定不变的;除此之外,SetStrategy接口每次调用也需要手动使用make_unique接口(或其他方式)。

于是,我们可以在头文件当中直接定义一个Logger对象,并加入如下的

Logger logger;
#define LOG(level) logger(level, __FILE__, __LINE__)
// 或者 #define LOG(level) logger.Log(level, __FILE__, __LINE__)
#define USE_CONSOLE_STRATEGY() logger.SetStrategy(std::make_unique<ConsoleStrategy>())
#define USE_FILE_STRATEGY() logger.SetStrategy(std::make_unique<FileStrategy>())

使用示例:

#include <iostream>
#include "log.hpp"
using namespace LogModule;int main()
{LOG(LogLevel::DEBUG) << "hello world!";LOG(LogLevel::DEBUG) << "hello world!";LOG(LogLevel::DEBUG) << "hello world!";USE_FILE_STRATEGY();LOG(LogLevel::DEBUG) << "hello world!";LOG(LogLevel::DEBUG) << "hello world!";LOG(LogLevel::DEBUG) << "hello world!";
}

3.5 完整代码

#pragma once
#include <iostream>
#include <fstream>
#include <memory>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include "mutex.hpp"namespace LogModule
{class LogStrategy{public:virtual ~LogStrategy() = default;virtual void Write(const std::string &message) = 0;protected:MutexModule::mutex _mutex;};// 控制台输出策略class ConsoleStrategy : public LogStrategy{public:void Write(const std::string &message) override{MutexModule::LockGuard lockguard(_mutex);std::cout << message << std::endl;}};// 文件输出策略(支持路径和追加模式)class FileStrategy : public LogStrategy{public:explicit FileStrategy(const std::string &path = "./log/test.log"): _file(path, std::ios::app){if (!_file.is_open()){throw std::runtime_error("无法打开日志文件: " + path);}}void Write(const std::string &message) override{MutexModule::LockGuard lockguard(_mutex);_file << message << std::endl;}~FileStrategy(){_file.close();}private:std::ofstream _file;};enum class LogLevel{DEBUG,    // 调试信息INFO,     // 普通信息WARNNING, // 警告ERROR,    // 错误FATAL     // 致命};std::string GetTimeStamp(){time_t cur = time(nullptr);struct tm tinfo;localtime_r(&cur, &tinfo);char buffer[128];sprintf(buffer, "%04d-%02d-%02d %02d:%02d:%02d", 1900 +\tinfo.tm_year, tinfo.tm_mon, tinfo.tm_mday,tinfo.tm_hour, tinfo.tm_min, tinfo.tm_sec);return buffer;}std::string LevelToString(LogLevel level){switch (level){case LogLevel::DEBUG:return "DEBUG";case LogLevel::INFO:return "INFO";case LogLevel::WARNNING:return "WARNNING";case LogLevel::ERROR:return "ERROR";case LogLevel::FATAL:return "FATAL";default:return "UNKNOWN";}}class Logger{public:// 构造时指定策略(默认控制台输出)explicit Logger(std::unique_ptr<LogStrategy> strategy = std::make_unique<ConsoleStrategy>()): _strategy(std::move(strategy)){}void SetStrategy(std::unique_ptr<LogStrategy> strategy){_strategy = move(strategy);}class Message{public:Message(LogLevel level, const std::string filename, int line, Logger &logger): _logger(logger){char buffer[128];sprintf(buffer, "[%s] [%s] [%d] [%s] [%d] - ",GetTimeStamp().c_str(), LevelToString(level).c_str(), getpid(), filename.c_str(), line);_loginfo = buffer;}template <typename T>Message &operator<<(const T &message){_loginfo += message;return *this;}~Message(){_logger._strategy->Write(_loginfo);}private:Logger &_logger;      // 引用外部logger类, 方便使用策略进行刷新std::string _loginfo; // 一条合并完成的,完整的日志信息};Message Log(LogLevel level, const std::string filename, int line){return Message(level, filename, line, *this);}Message operator()(LogLevel level, const std::string filename, int line){return Log(level, filename, line);}private:std::unique_ptr<LogStrategy> _strategy;};Logger logger;#define LOG(level) logger.Log(level, __FILE__, __LINE__)#define USE_CONSOLE_STRATEGY() logger.SetStrategy(std::make_unique<ConsoleStrategy>())#define USE_FILE_STRATEGY() logger.SetStrategy(std::make_unique<FileStrategy>())
}

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

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

相关文章

关于多个el-input的自动聚焦,每输入完一个el-input,自动聚焦到下一个

讲解原理或者思路&#xff1a;如果你有多个el-input,想要实现每输入完一个输入框&#xff0c;然后自动聚焦到下一个输入框&#xff0c;同理&#xff0c;如果每删除一个输入框的值&#xff0c;自动聚焦到上一个输入框。条件那么首先要做的就是&#xff0c;设置条件&#xff0c;在…

AI 赋能教育变革:机遇、实践与展望

引言说明教育在社会发展中的重要地位&#xff0c;以及传统教育面临的困境。引出 AI 技术为教育变革带来新机遇&#xff0c;阐述研究其在教育中应用的价值。AI 为教育带来的机遇个性化学习支持&#xff1a;讲解 AI 通过分析学生学习数据&#xff0c;如答题情况、学习时间等&…

(一)八股(数据库/MQ/缓存)

文章目录 项目地址 一、数据库 1.1 事务隔离级别 1. 事务的四大特性 2. Read Uncommited脏读(未提交读) 3. Read Commited幻读(sql默认已提交读) 4. Repeatable Read 5. Serializable 6. Snapshot(快照隔离) 7. 代码开启 8. For update和Repeatable Read的区别 1.2 各种锁 …

STM32H750 CoreMark跑分测试

STM32H750 CoreMark跑分测试&#x1f50e;CoreMark跑分测试查询网站&#xff1a;https://www.eembc.org/coremark/scores.php&#x1f4dc; CoreMark源码&#xff1a;https://www.github.com/eembc/coremarkCoreMark移植和配置参考&#xff1a;https://community.st.com/t5/stm…

RabbitMQ如何确保消息发送和消息接收

消息发送确认 1 ConfirmCallback方法 ConfirmCallback 是一个回调接口&#xff0c;消息发送到 Broker 后触发回调&#xff0c;确认消息是否到达 Broker 服务器&#xff0c;也就是只 确认是否正确到达 Exchange 中。 2 ReturnCallback方法 通过实现 ReturnCallback 接口&#xf…

Linux:进程间通信-管道

Linux&#xff1a;进程间通信-管道 前言&#xff1a;为什么需要进程间通信&#xff1f; 你有没有想过&#xff0c;当你在电脑上同时打开浏览器、音乐播放器和文档时&#xff0c;这些程序是如何协同工作的&#xff1f;比如&#xff0c;浏览器下载的文件&#xff0c;为什么能被文…

Jmeter + FFmpeg 直播压测遇到的问题及解决方案

1、压测机安装FFmpeg&#xff0c;下载安装步骤可见&#xff1a;https://zhuanlan.zhihu.com/p/692019886 2、Jmeter与FFmpeg位数要一致&#xff0c;不允许在32位的进程中运行一个64位的程序&#xff0c;反之亦然 3、OS进程取样器&#xff08;Thread Group -> Add -> Sa…

安卓app、微信小程序等访问多个api时等待提示调用与关闭问题

安卓app、微信小程序访问webapi&#xff0c;将需要一时间&#xff0c;我们称之为耗时操作&#xff0c;其它诸如密集型计算、访问文件与设备等亦是如此。在这个期间我们应该跳出提示&#xff0c;告知用户正在等待&#xff0c;并且很多时候&#xff0c;在等待时不允许用户再对UI进…

一个状态机如何启动/停止另一个状态机

一个状态机如何启动/停止另一个状态机 这个过程主要依赖于动作列表&#xff08;Action List&#xff09; 中的特定动作项和状态管理服务&#xff08;ARA::SM&#xff09;提供的API。 1. 通过动作列表&#xff08;Action List&#xff09;进行预配置控制 这是最常见的方式&#…

基于IPO智能粒子优化的IIR滤波器参数识别算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.部分程序 4.算法理论概述 5.完整程序 1.程序功能描述 IIR&#xff08;Infinite Impulse Response&#xff09;滤波器即无限冲激响应滤波器&#xff0c;其输出不仅与当前和过去的输入有关&#xff0c;还与过去的输出…

欧州服务器String 转 double 有BUG?

string 转 double 的常见问题通常与文化差异、格式解析或特殊值处理相关&#xff0c;而非框架本身的 “BUG”。以下是可能导致转换异常的常见场景及解决方案&#xff1a; 文化差异导致的解析问题 现象&#xff1a;同样的字符串&#xff08;如 “1.23” 或 “1,23”&#xff09;…

鸿蒙中网络诊断:Network分析

上面的图很熟悉吧 Network 面板的表格列出了所有请求&#xff0c;每一列都提供了关键信息&#xff1a; Name: 请求的资源名称和路径。 Status: HTTP 状态码&#xff08;诊断核心&#xff09;。200成功&#xff0c;304未修改&#xff08;缓存&#xff09;&#xff0c;404找不到…

HarmonyOS 实战:6 种实现实时数据更新的方案全解析(含完整 Demo)

摘要 在当下的应用开发中&#xff0c;用户体验越来越依赖“实时性”。消息要第一时间送达、订单状态要立刻刷新、数据变化不能延迟……这些需求推动了“实时数据更新”成为应用的必备功能。在鸿蒙系统&#xff08;HarmonyOS&#xff09;中&#xff0c;我们既可以用系统内置的数…

第十六届蓝桥杯青少组C++省赛[2025.8.10]第二部分编程题(4、矩阵圈层交错旋转)

参考程序&#xff1a;#include <bits/stdc.h> using namespace std;const int MAXN 105; int a[MAXN][MAXN];int main() {int n;if (!(cin >> n)) return 0;for (int i 0; i < n; i)for (int j 0; j < n; j)cin >> a[i][j];int layers n / 2; // 每…

AI供应链情报预警 | 恶意Py包伪装AI框架库开展数据窃密及应用劫持攻击

AI供应链情报概述近日&#xff08;18th Aug. , 2025&#xff09;&#xff0c;悬镜安全情报中心在Python官方仓库中捕获1起伪装成知名AI框架库pytensor&#xff08;https://pypi.org/project/pytensor&#xff09;的组件投毒事件。在北京时间8月18日凌晨&#xff0c;投毒者连续发…

AI需要防火墙,云计算需要重新构想

Akamai创始人Tom Leighton欲终结云膨胀&#xff0c;从内到外守护AI安全 Akamai创始人Tom Leighton 当前超大规模云服务商主导着企业IT市场&#xff0c;鲜有人敢挑战云计算经济模式、AI基础设施和网络安全架构的现状。但Akamai联合创始人兼CEO Tom Leighton正是这样的挑战者。他…

线段树详解【数据结构】

简介 线段树是一种应用极其广泛&#xff0c;使用范围较广并且非常知名的树形数据结构&#xff0c;主要用于进行区间操作&#xff0c;如区间修改&#xff0c;区间查询等。这种数据结构唯一的不足就是巨大的代码量&#xff0c;因此处理一些较简单的问题时建议用树状数组。 原理…

Maven 入门与进阶:聚合、继承与生命周期详解

Maven 是 Java 项目管理的核心工具&#xff0c;其强大的依赖管理、项目构建和模块化设计能力&#xff0c;极大地提升了开发效率。本文将深入探讨 Maven 的 聚合&#xff08;Multi-module&#xff09;、继承&#xff08;Inheritance&#xff09; 和 生命周期&#xff08;Lifecyc…

手搓MCP客户端动态调用多MCP服务,调用哪个你说了算!

01 引言 前两天&#xff0c;有个粉丝朋友咨询MCP服务如何动态调用&#xff0c;动态加载MCP服务的链接&#xff1f;我们都知道MCP客户端可以配置多个MCP服务的地址&#xff1a; spring.ai.mcp.client.sse.connections.server1.urlhttp://localhost:xxxx spring.ai.mcp.client.ss…

Go语言中的优雅并发控制:通道信号量模式详解

在Go语言的并发编程中&#xff0c;“通过通信共享内存”的设计哲学贯穿始终。当面对高并发场景时&#xff0c;无限制创建goroutine可能导致资源耗尽、CPU过载等问题&#xff0c;通道信号量模式&#xff08;Channel Semaphore Pattern&#xff09; 正是一种基于Go通道特性的优雅…