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

发布于:2024-05-02 ⋅ 阅读:(28) ⋅ 点赞:(0)

设计模式-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)过度抽象:过度抽象可能会使代码难以理解和维护。

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