设计模式-03 设计模式-依赖倒转原则案例分析

2024-05-01 18:28

本文主要是介绍设计模式-03 设计模式-依赖倒转原则案例分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


设计模式-03 设计模式-依赖倒转原则案例分析

目录

设计模式-02 设计模式-依赖倒转原则案例分析

1.定义

2.内涵

3.案例对比

4.注意事项

5.最佳实践

6.总结


1.定义

依赖倒转原则(Dependency Inversion Principle,简称DIP)高层级的模块不能依赖底层级模块的,两种层级的模块应该依赖抽象,抽象层不能依赖具体实现层,具体实现应该依赖抽象

通俗来说,DIP 意味着:

客户端代码(高层模块)不应该直接依赖于具体实现(低层模块)。
相反,客户端代码应该依赖于一个抽象,而抽象再依赖于具体实现。

+--------------+|  客户端代码  |+--------------+|V+--------------+|    抽象类    |+--------------+|V+--------------+| 具体实现类 |+--------------+

说明:

  • 客户端代码(高层模块)依赖于抽象类,而不是具体的实现类。
  • 抽象类定义了客户端代码所需的行为,而具体实现类提供了这些行为的具体实现。
  • 客户端代码通过依赖注入接收其依赖项(抽象类)。
  • 通过使用 DIP,客户端代码与具体实现类之间的耦合最小化,从而提高了代码的灵活性、可测试性和可维护性。

                   

2.内涵

DIP 的定义出发点是:

软件的灵活性:当依赖关系是固定的时,很难在不影响其他模块的情况下修改软件。
软件的可测试性:当客户端代码直接依赖于具体实现时,很难对客户端代码进行单元测试。

3.案例对比

错误的设计,经常是依赖与具体的实现。而不是依赖与接口层。以下就具体案例进行的举例说明。


bad 设计

#include <iostream>
#include <cstdio>
#include <vector>
#include <fstream>
#include <tuple>
#include <string>using namespace std;enum class Relationship{parent,child,sibling,
};struct Person{string name;
};struct Relationships  // 底层模块
{vector<tuple<Person, Relationship, Person>> relations;void add_parent_add_child(const Person& parent, const Person& child){relations.push_back({parent, Relationship::parent, child});relations.push_back({child, Relationship::child, parent});}};struct Research  // 上层模块,依赖与底层的具体实现,而不是抽象接口,违反了依赖倒转原则
{Research(Relationships& relationships){auto& relations = relationships.relations;for(auto& [first, rel, second]: relations){if(first.name == "Hohn"  && rel == Relationship::parent){cout<<"Hohn has child :"<< second.name<< endl;}}}
};int main() {Person parent{"Hohn"};Person child1{"Dhris"},child2{"Watt"};Relationships relationships;relationships.add_parent_add_child(parent,child1);relationships.add_parent_add_child(parent,child2);Research _(relationships);return 0;
}

好的设计

#include <iostream>
#include <cstdio>
#include <vector>
#include <fstream>
#include <tuple>
#include <string>using namespace std;enum class Relationship{parent,child,sibling,
};struct Person{string name;
};struct RelationshipBrowser{virtual vector<Person> find_all_children_of(const string& name) = 0;
};struct Relationships :RelationshipBrowser // 底层模块
{vector<tuple<Person, Relationship, Person>> relations;void add_parent_add_child(const Person& parent, const Person& child){relations.push_back({parent, Relationship::parent, child});relations.push_back({child, Relationship::child, parent});}vector<Person> find_all_children_of(const string &name) override {vector<Person> result;for(auto&& [first,rel,second] : relations) {if(first.name == name && rel == Relationship::parent){result.push_back(second);}}return result;}};struct Research  // 上层模块不在依赖 Relationships ,而是依赖接口:RelationshipBrowser
{Research(RelationshipBrowser& browser){for(auto& child: browser.find_all_children_of("Hohn")){cout<<"Honh has child:"<< child.name<<endl;}}
//    Research(Relationships& relationships){
//        auto& relations = relationships.relations;
//        for(auto& [first, rel, second]: relations){
//            if(first.name == "Hohn"  && rel == Relationship::parent){
//                cout<<"Hohn has child :"<< second.name<< endl;
//            }
//        }
//    }
};int main() {Person parent{"Hohn"};Person child1{"Dhris"},child2{"Watt"};Relationships relationships;relationships.add_parent_add_child(parent,child1);relationships.add_parent_add_child(parent,child2);Research _(relationships);return 0;
}

两种设计思路输出结果一样,但是好的设计遵循了依赖倒转原则,代码更灵活

Honh has child:Dhris
Honh has child:Watt
4.注意事项


在具体开发中,应用依赖倒转原则(DIP)时需要注意以下几点:

  • 明确接口和实现之间的关系:确保接口定义了客户端代码所需的行为,而实现提供了这些行为的具体实现。
  • 使用抽象类或接口来定义抽象:避免使用具体类作为抽象,因为这会违反 DIP。
  • 通过依赖注入传递依赖项:使用依赖注入框架或手动将依赖项传递给客户端代码,而不是让客户端代码直接创建依赖项。
  • 避免循环依赖:确保依赖关系是单向的,即高层模块不依赖于低层模块,低层模块也不依赖于高层模块。
  • 考虑性能影响:在某些情况下,使用 DIP 可能会有轻微的性能开销,因此在应用 DIP 时需要权衡利弊。

5.最佳实践

DIP 有以下优点:

  • 提高代码的灵活性:通过使用抽象,我们可以轻松地更改具体实现,而无需修改客户端代码。
  • 提高代码的可测试性:通过使用抽象,我们可以对客户端代码进行单元测试,而无需依赖于具体实现。
  • 减少耦合:DIP 减少了客户端代码与具体实现之间的耦合,从而提高了代码的可维护性和可重用性。

6.总结

DIP 并不是万能的,在某些情况下可能会有一些局限性:
(1)轻微的性能开销:使用 DIP 可能会有轻微的性能开销,因为需要额外的开销来管理依赖项。
(2)过度抽象:过度抽象可能会使代码难以理解和维护。

过度抽象就会使得系统复杂化,不便于后续优化。

这篇关于设计模式-03 设计模式-依赖倒转原则案例分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/952365

相关文章

IDEA下"File is read-only"可能原因分析及"找不到或无法加载主类"的问题

《IDEA下Fileisread-only可能原因分析及找不到或无法加载主类的问题》:本文主要介绍IDEA下Fileisread-only可能原因分析及找不到或无法加载主类的问题,具有很好的参... 目录1.File is read-only”可能原因2.“找不到或无法加载主类”问题的解决总结1.File

Spring Boot 整合 Redis 实现数据缓存案例详解

《SpringBoot整合Redis实现数据缓存案例详解》Springboot缓存,默认使用的是ConcurrentMap的方式来实现的,然而我们在项目中并不会这么使用,本文介绍SpringB... 目录1.添加 Maven 依赖2.配置Redis属性3.创建 redisCacheManager4.使用Sp

springboot项目redis缓存异常实战案例详解(提供解决方案)

《springboot项目redis缓存异常实战案例详解(提供解决方案)》redis基本上是高并发场景上会用到的一个高性能的key-value数据库,属于nosql类型,一般用作于缓存,一般是结合数据... 目录缓存异常实践案例缓存穿透问题缓存击穿问题(其中也解决了穿透问题)完整代码缓存异常实践案例Red

Dubbo之SPI机制的实现原理和优势分析

《Dubbo之SPI机制的实现原理和优势分析》:本文主要介绍Dubbo之SPI机制的实现原理和优势,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Dubbo中SPI机制的实现原理和优势JDK 中的 SPI 机制解析Dubbo 中的 SPI 机制解析总结Dubbo中

C#继承之里氏替换原则分析

《C#继承之里氏替换原则分析》:本文主要介绍C#继承之里氏替换原则,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#里氏替换原则一.概念二.语法表现三.类型检查与转换总结C#里氏替换原则一.概念里氏替换原则是面向对象设计的基本原则之一:核心思想:所有引py

Nginx使用Keepalived部署web集群(高可用高性能负载均衡)实战案例

《Nginx使用Keepalived部署web集群(高可用高性能负载均衡)实战案例》本文介绍Nginx+Keepalived实现Web集群高可用负载均衡的部署与测试,涵盖架构设计、环境配置、健康检查、... 目录前言一、架构设计二、环境准备三、案例部署配置 前端 Keepalived配置 前端 Nginx

基于Go语言实现Base62编码的三种方式以及对比分析

《基于Go语言实现Base62编码的三种方式以及对比分析》Base62编码是一种在字符编码中使用62个字符的编码方式,在计算机科学中,,Go语言是一种静态类型、编译型语言,它由Google开发并开源,... 目录一、标准库现状与解决方案1. 标准库对比表2. 解决方案完整实现代码(含边界处理)二、关键实现细

PostgreSQL 序列(Sequence) 与 Oracle 序列对比差异分析

《PostgreSQL序列(Sequence)与Oracle序列对比差异分析》PostgreSQL和Oracle都提供了序列(Sequence)功能,但在实现细节和使用方式上存在一些重要差异,... 目录PostgreSQL 序列(Sequence) 与 oracle 序列对比一 基本语法对比1.1 创建序

MySQL 复合查询案例详解

《MySQL复合查询案例详解》:本文主要介绍MySQL复合查询案例详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录基本查询回顾多表笛卡尔积子查询与where子查询多行子查询多列子查询子查询与from总结合并查询(不太重要)union基本查询回顾查询

Maven 依赖发布与仓库治理的过程解析

《Maven依赖发布与仓库治理的过程解析》:本文主要介绍Maven依赖发布与仓库治理的过程解析,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下... 目录Maven 依赖发布与仓库治理引言第一章:distributionManagement配置的工程化实践1