【设计模式】如何用C++实现依赖倒置

【设计模式】如何用C++实现依赖倒置

一、什么是依赖倒置?

依赖倒置原则(Dependency Inversion Principle,DIP)是SOLID面向对象设计原则中的一项。它的核心思想是:

  1. 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。
  2. 抽象不应该依赖于细节,细节应该依赖于抽象。

这个原则的目的在于减少代码耦合性,使代码更具灵活性和扩展性。按照依赖倒置原则,我们让上层的逻辑不直接依赖底层实现,而是通过抽象(接口或抽象类)来控制两者之间的关系。

二、为什么使用依赖倒置?

在没有依赖倒置的设计中,高层模块(如业务逻辑)往往会直接依赖于低层模块(如数据访问、服务调用)。这种依赖会使得高层模块无法独立更改,一旦低层模块发生变动,所有依赖它的高层模块都需要跟着修改,导致代码维护难度和出错率增加。

通过依赖倒置可以实现以下特性:

  • 更易于扩展:可以随时替换低层模块的实现而不需要改动高层模块。
  • 增强测试性:可以方便地替换低层模块,便于Mock或打桩,提升了代码的可测试性。
  • 提高灵活性:可以根据需求切换不同的实现,方便扩展和维护。

三、实现步骤

  1. 定义抽象接口(基类) :定义一个抽象类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
  2. 实现接口的具体类 :定义一个实现类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;
    }
  3. 注入依赖 :通过构造函数依赖注入,将实现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接口的实现类都可以被使用。这就是依赖倒置带来的灵活性和扩展性。

四、完整实现

  1. 在以上三点的基础上继续编写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)
  2. 此时代码结构如下。

  3. 命令行编译执行。

    bash 复制代码
    mkdir build
    cd build
    cmake ..
    make -j12
    ./Service
  4. 执行结果。

    复制代码
    In ServiceImpl, m_a = 3.
    In Test, m_a = 4.
    In ~ServiceImpl, m_a = 4.
  5. 可以看到Service指针在不感知ServiceImpl具体实现的情况下,仅通过调用接口实现了和ServiceImpl实例相同的功能。

相关推荐
Brilliantwxx8 分钟前
【STM32】 I2C_EEPROM_test 工程(实战篇)
开发语言·stm32·单片机·嵌入式硬件·ecmascript
清水白石00813 分钟前
Python 异步编程深度解析:Cancellation 到底是异常还是控制信号?从 asyncio 取消机制到企业级事务设计最佳实践
开发语言·python
kolyle20 分钟前
万级 QPS 下的 Token 分发系统架构:从 0 到 1 跑通 AI 时代的“水电煤“
开发语言·人工智能·系统架构·token·qps·极智词元·大模型私有化部署
caoerzhong33 分钟前
JeeWMS 开源仓库管理系统 GPL-3.0 合规指南:Java WMS 二次开发前必须弄清的授权边界
java·开发语言·开源·vue
Patrick在香港33 分钟前
Python 审计香港开放数据目录:两个端点差 10 倍,只有 9.3% 的资源标了「最后修改时间」
开发语言·数据库·python·数据分析·api·数据治理·开放数据
Sylvia33.1 小时前
火星数据体育API|一站式接入足球篮球电竞等18+项目实时数据
java·开发语言·python·websocket·游戏
Data_Journal1 小时前
使用 AutoScraper 进行网页抓取:分步教程
大数据·开发语言·数据库·python·scrapy
yume_sibai2 小时前
12-Rust 性能优化指南(性能分析 + 内存优化 + SIMD + 并发优化 + 编译器优化 + 基准测试)
开发语言·性能优化·rust
萧瑟余晖2 小时前
Hibernate 核心原理与架构详解
开发语言·架构·hibernate
爱喝水的鱼丶2 小时前
SAP-ABAP:SAP MM 模块物料主数据批量导入与增强开发:字段扩展、校验逻辑与批量导入工具开发
开发语言·性能优化·sap·abap·增强·经验交流·mm