日志与策略模式
策略模式(Strategy Pattern)
策略模式是一种行为型设计模式,它允许你定义一系列算法或行为,并将它们封装在独立的类中,使得它们可以互相替换。策略模式使得算法可以独立于使用它的客户端而变化。
1. 策略模式的核心思想
- 分离变化:将可能变化的算法或行为抽象出来,独立于客户端。
- 多态性:通过接口或基类定义算法的通用行为,具体实现由子类完成。
- 动态切换:客户端可以在运行时动态切换具体的策略。
2. 策略模式的组成部分
-
策略接口(Strategy Interface):
- 定义算法的通用接口。
- 所有具体策略类必须实现这个接口。
-
具体策略类(Concrete Strategies):
- 实现策略接口,提供具体的算法或行为。
-
上下文类(Context):
- 持有一个策略对象的引用。
- 通过策略接口调用具体的算法。
3. 策略模式的优点
- 开闭原则:新增策略无需修改现有代码。
- 消除条件语句:避免使用大量的
if-else或switch-case语句。 - 提高代码复用性:策略可以被多个上下文共享。
4. 策略模式的适用场景
- 当一个系统需要在多种算法或行为之间动态切换时。
- 当一个类有多种行为,且这些行为在类中以条件语句的形式出现时。
- 当算法的实现需要独立于使用它的客户端时。
简单日志模拟
根据策略模式,我们可以实现一个简单的日志模块。
1. 日志模块的核心功能
我们的日志模块需要支持以下功能:
- 日志等级:支持
DEBUG、INFO、WARNING、ERROR、FATAL五个等级。 - 日志输出方式:支持控制台输出和文件输出。
- 日志格式化:每条日志包含时间、日志等级、进程 ID、文件名和行号。
- 线程安全:确保多线程环境下日志输出的正确性。
2. 设计思路
为了实现上述功能,我们采用以下设计思路:
- 策略模式:将日志的输出方式抽象为策略接口,支持动态切换。
- RAII 技术:通过对象的析构函数自动刷新日志。
- 宏定义:简化日志调用,自动填充文件名和行号。
3. 代码实现
3.1 日志等级
我们使用枚举类 LogLevel 定义日志等级:
enum class LogLevel {
DEBUG = 1,
INFO,
WARNING,
ERROR,
FATAL
};
3.2 日志策略接口
日志策略接口 LogStrategy 定义了日志输出的通用行为:
class LogStrategy {
public:
virtual ~LogStrategy() = default;
virtual void SyncLog(const std::string &message) = 0;
};
3.3 具体策略类
-
控制台策略:将日志输出到控制台。
class ConsoleLogStrategy : public LogStrategy { public: void SyncLog(const std::string &message) override { LockGuard lockGuard(_mutex); std::cout << message << std::endl; } private: Mutex _mutex; }; -
文件策略:将日志输出到文件。
class FileLogStrategy : public LogStrategy { public: FileLogStrategy(const std::string &logpath = "./log", const std::string &logname = "log.txt") : _logpath(logpath), _logname(logname) { if (!std::filesystem::exists(_logpath)) { std::filesystem::create_directories(_logpath); } } void SyncLog(const std::string &message) override { LockGuard lockguard(_mutex); std::string log = _logpath + _logname; std::ofstream out(log, std::ios::app); if (!out.is_open()) return; out << message << '\n'; out.close(); } private: std::string _logpath; std::string _logname; Mutex _mutex; };
3.4 日志记录器
Logger 类是日志模块的核心,负责管理日志策略和生成日志消息:
class Logger {
public:
Logger() {
_strategy = std::make_shared<ConsoleLogStrategy>(); // 默认策略
}
void EnableConsoleLog() {
_strategy = std::make_shared<ConsoleLogStrategy>();
}
void EnableFileLog() {
_strategy = std::make_shared<FileLogStrategy>();
}
class LogMessage {
public:
LogMessage(LogLevel level, const std::string &filename, int line, Logger &logger)
: _currtime(CurrentTime()),
_level(level),
_pid(::getpid()),
_filename(filename),
_line(line),
_logger(logger)
{
std::stringstream ssbuffer;
ssbuffer << "[" << _currtime << "] "
<< "[" << Level2String(_level) << "] "
<< "[" << _pid << "] "
<< "[" << _filename << "] "
<< "[" << _line << "] - ";
_loginfo = ssbuffer.str();
}
template<typename T>
LogMessage &operator<<(const T &info) {
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this;
}
~LogMessage() {
if (_logger._strategy)
_logger._strategy->SyncLog(_loginfo);
}
private:
std::string _currtime;
LogLevel _level;
pid_t _pid;
std::string _filename;
int _line;
Logger &_logger;
std::string _loginfo;
};
LogMessage operator()(LogLevel level, const std::string &filename, int line) {
return LogMessage(level, filename, line, *this);
}
private:
std::shared_ptr<LogStrategy> _strategy;
};
3.5 宏定义
为了方便使用,我们定义了以下宏:
#define LOG(level) logger(level, __FILE__, __LINE__)
#define ENABLECONSOLELOG() logger.EnableConsoleLog()
#define ENABLEFILELOG() logger.EnableFileLog()
4. 使用示例
以下是日志模块的使用示例:
#include "Log.hpp"
using namespace LogModule;
int main() {
LOG(LogLevel::DEBUG) << "This is a debug log.";
LOG(LogLevel::INFO) << "This is an info log.";
ENABLEFILELOG();
LOG(LogLevel::WARNING) << "This is a warning log written to a file.";
return 0;
}
5. 逻辑分析
通过 LOG(LogLevel::DEBUG) 这行代码,日志模块首先会调用 Logger 类的 operator() 函数,创建一个 LogMessage 临时对象。这个对象的构造函数会初始化日志的基本信息,包括当前时间、日志等级、进程 ID、文件名和行号,并将这些信息格式化为日志头(如 [2023-10-01 12:34:56] [DEBUG] [1234] [main.cpp] [42] -)。接着,通过链式调用 operator<<,日志内容(如 "hello file"、1、2)被逐步追加到日志信息中。最后,当 LogMessage 对象超出作用域时,其析构函数会自动调用 SyncLog 方法,将完整的日志信息(如 [2023-10-01 12:34:56] [DEBUG] [1234] [main.cpp] [42] - hello file12)根据当前的日志策略(控制台或文件)输出到目标位置。整个过程通过 RAII 技术确保日志的自动刷新,并通过策略模式支持动态切换输出方式。
674

被折叠的 条评论
为什么被折叠?



