依赖倒置原则(Dependency Inversion Principle,DIP)是面向对象设计中的一个重要原则,它强调高层次的代码不应该依赖低层次的代码,抽象的代码不应该依赖具体的代码,而是低层次代码应该依赖高层次的抽象。下面结合 C++ 代码详细讲解这一原则。
未遵循依赖倒置原则的示例
假设我们要实现一个简单的日志系统,包含一个 FileLogger类用于将日志信息写入文件,还有一个 Application类,它依赖于 FileLogger来记录日志。
cpp
#include <iostream>
#include <fstream>
#include <string>
// 低层次模块:文件日志记录器
class FileLogger {
public:
void log(const std::string& message) {
std::ofstream file("log.txt", std::ios::app);
if (file.is_open()) {
file << message << std::endl;
file.close();
}
}
};
// 高层次模块:应用程序
class Application {
private:
FileLogger logger;
public:
void doSomething() {
logger.log("Doing something...");
}
};
int main() {
Application app;
app.doSomething();
return 0;
}
在这个例子中,Application是高层次模块,FileLogger是低层次模块。Application直接依赖于 FileLogger,这违反了依赖倒置原则。如果我们想要更换日志记录方式,比如改为将日志记录到控制台,就需要修改 Application类的代码。
遵循依赖倒置原则的示例
为了遵循依赖倒置原则,我们引入一个抽象的日志记录接口 ILogger,让 Application依赖于这个抽象接口,而 FileLogger和 ConsoleLogger都实现这个接口。
cpp
#include <iostream>
#include <fstream>
#include <string>
// 抽象接口:日志记录器
class ILogger {
public:
virtual void log(const std::string& message) = 0;
virtual ~ILogger() {}
};
// 低层次模块:文件日志记录器
class FileLogger : public ILogger {
public:
void log(const std::string& message) override {
std::ofstream file("log.txt", std::ios::app);
if (file.is_open()) {
file << message << std::endl;
file.close();
}
}
};
// 低层次模块:控制台日志记录器
class ConsoleLogger : public ILogger {
public:
void log(const std::string& message) override {
std::cout << message << std::endl;
}
};
// 高层次模块:应用程序
class Application {
private:
ILogger* logger;
public:
Application(ILogger* logger) : logger(logger) {}
void doSomething() {
logger->log("Doing something...");
}
};
int main() {
// 使用文件日志记录器
FileLogger fileLogger;
Application app1(&fileLogger);
app1.doSomething();
// 使用控制台日志记录器
ConsoleLogger consoleLogger;
Application app2(&consoleLogger);
app2.doSomething();
return 0;
}
代码解释
抽象接口 ILogger:定义了一个纯虚函数 log,任何具体的日志记录器都必须实现这个函数。这是一个抽象的代码,高层次的 Application 类依赖于这个抽象接口,而不是具体的日志记录器实现。
具体实现类 FileLogger 和 ConsoleLogger:它们都实现了 ILogger 接口,因此可以作为 ILogger 类型的对象传递给 Application 类。
高层次模块 Application:通过构造函数接收一个 ILogger 指针,从而依赖于抽象接口。这样,我们可以在运行时动态地选择不同的日志记录器,而不需要修改 Application 类的代码。
优点
可维护性:当需要更换日志记录方式时,只需要创建一个新的实现 ILogger 接口的类,而不需要修改 Application 类的代码。
可扩展性:可以轻松地添加新的日志记录器,如数据库日志记录器、网络日志记录器等,而不会影响现有的代码。
可测试性:可以使用模拟对象(Mock Object)来实现 ILogger 接口,从而方便地对 Application 类进行单元测试。