【设计模式】如何用C++实现依赖倒置
一、什么是依赖倒置?
依赖倒置原则(Dependency Inversion Principle,DIP)是SOLID面向对象设计原则中的一项。它的核心思想是:
- 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。
- 抽象不应该依赖于细节,细节应该依赖于抽象。
这个原则的目的在于减少代码耦合性,使代码更具灵活性和扩展性。按照依赖倒置原则,我们让上层的逻辑不直接依赖底层实现,而是通过抽象(接口或抽象类)来控制两者之间的关系。
二、为什么使用依赖倒置?
在没有依赖倒置的设计中,高层模块(如业务逻辑)往往会直接依赖于低层模块(如数据访问、服务调用)。这种依赖会使得高层模块无法独立更改,一旦低层模块发生变动,所有依赖它的高层模块都需要跟着修改,导致代码维护难度和出错率增加。
通过依赖倒置可以实现以下特性:
- 更易于扩展:可以随时替换低层模块的实现而不需要改动高层模块。
- 增强测试性:可以方便地替换低层模块,便于Mock或打桩,提升了代码的可测试性。
- 提高灵活性:可以根据需求切换不同的实现,方便扩展和维护。
三、实现步骤
-
定义抽象接口(基类) :定义一个抽象类
Service.h
实现了一个Test
纯虚函数,此抽象类不实现任何具体逻辑,仅对外提供接口Service.h
cpp#ifndef DIP_SERVICE #define DIP_SERVICE namespace DIP { class Service { public: virtual ~Service() = default; virtual void Test() = 0; }; } #endif
-
实现接口的具体类 :定义一个实现类
ServiceImpl
实现构造、析构、Test等函数逻辑。ServiceImpl.h
cpp#ifndef DIP_SERVICEIMPL #define DIP_SERVICEIMPL #include "Service.h" namespace DIP { class ServiceImpl : public Service { public: ServiceImpl(int a); ~ServiceImpl() override; void Test() override; private: int m_a; }; } #endif
ServiceImpl.cpp
cpp#include "ServiceImpl.h" #include <iostream> using namespace DIP; ServiceImpl::ServiceImpl(int a) : m_a(a) { std::cout << "In ServiceImpl, m_a = " << m_a << "." << std::endl; } ServiceImpl::~ServiceImpl() { std::cout << "In ~ServiceImpl, m_a = " << m_a << "." << std::endl; } void ServiceImpl::Test() { m_a++; std::cout << "In Test, m_a = " << m_a << "." << std::endl; }
-
注入依赖 :通过构造函数依赖注入,将实现
DIP::Service
接口的对象传入,将具体实现传递给高层模块的接口指针或引用,从而对具体实现DIP::ServiceImpl
解耦。main.cpp
cpp#include "ServiceImpl.h" #include "Service.h" #include <memory> int main() { int a = 3; std::shared_ptr<DIP::Service> serviceImpl = std::make_shared<DIP::ServiceImpl>(a); serviceImpl->Test(); }
在这种设计中,高层的
main
函数依赖于抽象接口Service
,而不是具体的实现类。这样只要继承了Service
接口的实现类都可以被使用。这就是依赖倒置带来的灵活性和扩展性。
四、完整实现
-
在以上三点的基础上继续编写
CMakeLists.txt
文件。cpp# 设置项目名称和最低CMake版本 cmake_minimum_required(VERSION 3.10) set(ProjectName Service) project(${ProjectName}) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) add_executable(${ProjectName} main.cpp ServiceImpl.cpp)
-
此时代码结构如下。
-
命令行编译执行。
bashmkdir build cd build cmake .. make -j12 ./Service
-
执行结果。
In ServiceImpl, m_a = 3. In Test, m_a = 4. In ~ServiceImpl, m_a = 4.
-
可以看到
Service
指针在不感知ServiceImpl
具体实现的情况下,仅通过调用接口实现了和ServiceImpl
实例相同的功能。