【C++设计模式】依赖倒转原则

2023年8月30日,周三上午


目录


概述

依赖倒转原则(Dependency Inversion Principle,DIP)是面向对象设计中的一个基本原则。

含义

高层模块不应该依赖低层模块,两者都应该依赖其抽象。

也就是说:

  • 高层模块不应该直接依赖低层模块,两者之间应使用抽象来解耦。
  • 具体实现应该依赖抽象,而不应该依赖细节。
  • 抽象不应该依赖细节,细节应该依赖抽象。

举个简单的例子

  • 高层模块:用户模块
  • 低层模块:数据库模块
  • 抽象:接口或抽象基类

传统做法

这违反了依赖倒转原则,因为高层用户模块直接依赖了低层数据库模块。

cpp 复制代码
用户模块 -> 直接依赖数据库模块

使用依赖倒转原则

  • 定义一个数据库操作的接口或抽象基类
  • 数据库模块实现这个接口/基类
  • 用户模块只依赖接口/基类,通过接口/基类与数据库模块解耦
cpp 复制代码
         依赖                 实现
用户模块 -----> 接口/抽象基类<-----数据库模块

这样一来,用户模块与数据库模块的依赖关系就通过抽象进行了解耦。如果需要替换数据库,只需要修改数据库模块的实现,不影响用户模块。

总之,依赖倒转原则通过抽象层解耦高低层模块的依赖关系,提高了模块的独立性、可扩展性和可维护性。

代码说明

这里用一个简单的代码例子来说明依赖倒转原则。

cpp 复制代码
            依赖                   实现
UserModule -----> 抽象类IDataBase<-----MysqlDatabase

首先定义一个数据库操作的接口:

cpp 复制代码
// 抽象接口
class IDatabase {
public:
  virtual void Insert(const string& data) = 0;
  virtual void Select() = 0;
};

然后实现这个接口的具体数据库类:

cpp 复制代码
// 具体实现
class MysqlDatabase : public IDatabase{
public:
  void Insert(const string& data) override {
  // 具体插入逻辑
  }

  void Select() override {
  // 具体查询逻辑 
  }
};

用户模块只依赖接口,不依赖具体实现:

cpp 复制代码
// 用户模块
class UserModule {
private:
  IDatabase* db;

public:
  UserModule(IDatabase* db) : db(db) {}

  void Run() {
  // ...
  db->Insert("some data");
  db->Select();
  }
};

在主函数中:

cpp 复制代码
int main() {

  MysqlDatabase mysql;
  UserModule user(&mysql);

  user.Run();

  return 0;
}

在这个程序里:

  • 用户模块只依赖抽象接口IDatabase,不依赖具体的MysqlDatabase类。
  • MysqlDatabase实现了IDatabase接口。
  • 通过接口解耦了用户模块和数据库模块的依赖关系。

如果需要替换数据库,只需要修改MysqlDatabase实现,而不影响用户模块。这就是依赖倒转原则的实现。

再举一个具体的例子

在windows平台上用这套

cpp 复制代码
                依赖                实现
DrawingProgram -----> 抽象类IShape<-----RectangleOnWindows

在Linux平台上用这套

cpp 复制代码
                依赖                实现
DrawingProgram -----> 抽象类IShape<-----RectangleOnLinux
cpp 复制代码
#include<iostream>

class IShape {
public:
  virtual void draw() = 0;
};

//在windows平台上画矩形
class RectangleOnWindows : public IShape {
public:
  void draw() override {
  std::cout << "在Windows上画矩形" << std::endl;
  std::cout << "先画左边和右边,再画上边和下边" << std::endl;
  }  
};

//在Linux平台上画矩形
class RectangleOnLinux : public IShape {
public:
  void draw() override {
  std::cout << "在Linux上画矩形" << std::endl;
  std::cout << "先画上边和左边,再画下边和右边" << std::endl;
  }  
};

class DrawingProgram {
private:
  IShape* shape;

public:
  DrawingProgram(IShape* shape) {
  this->shape = shape;
  }

  void run() {
  shape->draw();
  }
};
int main() {

//在Windows平台上用这一套
  RectangleOnWindows ROW;
  DrawingProgram program(&ROW);
  program.run();
  
//在Linux平台上用这一套
//  RectangleOnLinux ROL;
//  DrawingProgram program(&ROL);
//  program.run();

  return 0;
}

这这个程序中:

  • 绘图程序只依赖形状接口,不依赖具体形状类。
  • 形状类实现了形状接口。
  • 通过接口解耦了绘图程序和形状类的依赖关系。

如果需要添加新的形状,只需要实现形状接口,不影响绘图程序。这就是一个完整的依赖倒转原则示例。

以生活为例

电脑中的主板就是最好的一个依赖倒转原则例子,

在主板上有非常多的硬件接口,用来安装内存、硬盘、电源等等,

这些硬件接口就相当于抽象类,

正是因为有了接口,才能在一块主板上安装不同品牌、不同厂商生产的内存条、硬盘、电源等等。

如果主板上没有这些硬件接口,而是直接让主板与某个品牌的内存条连接,

那么当这个内存条坏了,你就只能买这个品牌的内存条,用其他品牌的没用,

因为这个主板是针对这个品牌的内存条设计的,没办法做到抽象,也就只能用这个品牌的。

相关推荐
星空寻流年8 小时前
设计模式第一章(建造者模式)
java·设计模式·建造者模式
蒋星熠9 小时前
Flutter跨平台工程实践与原理透视:从渲染引擎到高质产物
开发语言·python·算法·flutter·设计模式·性能优化·硬件工程
至此流年莫相忘12 小时前
设计模式:策略模式
设计模式·策略模式
ytadpole13 小时前
揭秘设计模式:命令模式-告别混乱,打造优雅可扩展的代码
java·设计模式
努力也学不会java16 小时前
【设计模式】 外观模式
设计模式·外观模式
deepwater_zone17 小时前
设计模式(策略,观察者,单例,工厂方法)
设计模式
宁静致远202121 小时前
【C++设计模式】第三篇:观察者模式(别名:发布-订阅模式、模型-视图模式、源-监听器模式)
c++·观察者模式·设计模式
User_芊芊君子1 天前
【Java】设计模式——单例、工厂、代理模式
java·设计模式·代理模式
YA3331 天前
java设计模式二、工厂
java·开发语言·设计模式
烛阴2 天前
【TS 设计模式完全指南】从零到一:掌握TypeScript建造者模式,让你的对象构建链式优雅
javascript·设计模式·typescript