设计模式-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

相关文章

Python通用唯一标识符模块uuid使用案例详解

《Python通用唯一标识符模块uuid使用案例详解》Pythonuuid模块用于生成128位全局唯一标识符,支持UUID1-5版本,适用于分布式系统、数据库主键等场景,需注意隐私、碰撞概率及存储优... 目录简介核心功能1. UUID版本2. UUID属性3. 命名空间使用场景1. 生成唯一标识符2. 数

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

PostgreSQL的扩展dict_int应用案例解析

《PostgreSQL的扩展dict_int应用案例解析》dict_int扩展为PostgreSQL提供了专业的整数文本处理能力,特别适合需要精确处理数字内容的搜索场景,本文给大家介绍PostgreS... 目录PostgreSQL的扩展dict_int一、扩展概述二、核心功能三、安装与启用四、字典配置方法

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java设计模式---迭代器模式(Iterator)解读

《Java设计模式---迭代器模式(Iterator)解读》:本文主要介绍Java设计模式---迭代器模式(Iterator),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录1、迭代器(Iterator)1.1、结构1.2、常用方法1.3、本质1、解耦集合与遍历逻辑2、统一

Python中re模块结合正则表达式的实际应用案例

《Python中re模块结合正则表达式的实际应用案例》Python中的re模块是用于处理正则表达式的强大工具,正则表达式是一种用来匹配字符串的模式,它可以在文本中搜索和匹配特定的字符串模式,这篇文章主... 目录前言re模块常用函数一、查看文本中是否包含 A 或 B 字符串二、替换多个关键词为统一格式三、提

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

Python get()函数用法案例详解

《Pythonget()函数用法案例详解》在Python中,get()是字典(dict)类型的内置方法,用于安全地获取字典中指定键对应的值,它的核心作用是避免因访问不存在的键而引发KeyError错... 目录简介基本语法一、用法二、案例:安全访问未知键三、案例:配置参数默认值简介python是一种高级编

解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题

《解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题》:本文主要介绍解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4... 目录未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘打开pom.XM

IDEA Maven提示:未解析的依赖项的问题及解决

《IDEAMaven提示:未解析的依赖项的问题及解决》:本文主要介绍IDEAMaven提示:未解析的依赖项的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录IDEA Maven提示:未解析的依编程赖项例如总结IDEA Maven提示:未解析的依赖项例如