什么是依赖倒置原则以及为什么它很重要?

发布于 2024-07-05 07:01:42 字数 26 浏览 8 评论 0原文

什么是依赖倒置原则以及为什么它很重要?

What is the dependency inversion principle and why is it important?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

情深缘浅 2024-07-12 07:01:43

当我们设计软件应用程序时,我们可以考虑低级类,即实现基本和主要操作(磁盘访问、网络协议等)的类,以及高级类,即封装复杂逻辑(业务流,...)的类。

最后一个依赖于低级别的课程。 实现这种结构的一种自然方法是编写低级类,一旦我们有了它们就可以编写复杂的高级类。 由于高级类是根据其他类定义的,这似乎是合乎逻辑的方法。 但这不是一个灵活的设计。 如果我们需要替换低级别的类会发生什么?

依赖倒置原则指出:

  • 高层模块不应依赖于低层模块。 两者都应该依赖于抽象。
  • 抽象不应该依赖于细节。 细节应该取决于抽象。

该原则旨在“反转”软件中的高级模块应依赖于较低级别模块的传统观念。 这里高层模块拥有由低层模块实现的抽象(例如,决定接口的方法)。 从而使较低级别的模块依赖于较高级别的模块。

When we design software applications we can consider the low level classes the classes which implement basic and primary operations (disk access, network protocols,...) and high level classes the classes which encapsulate complex logic (business flows, ...).

The last ones rely on the low level classes. A natural way of implementing such structures would be to write low level classes and once we have them to write the complex high level classes. Since high level classes are defined in terms of others this seems the logical way to do it. But this is not a flexible design. What happens if we need to replace a low level class?

The Dependency Inversion Principle states that:

  • High level modules should not depend upon low level modules. Both should depend upon abstractions.
  • Abstractions should not depend upon details. Details should depend upon abstractions.

This principle seeks to "invert" the conventional notion that high level modules in software should depend upon the lower level modules. Here high level modules own the abstraction (for example, deciding the methods of the interface) which are implemented by lower level modules. Thus making lower level modules dependent on higher level modules.

情未る 2024-07-12 07:01:43

基本上它说:

类应该依赖于抽象(例如接口、抽象类),而不是具体的细节(实现)。

Basically it says:

Class should depend on abstractions (e.g interface, abstract classes), not specific details (implementations).

谁与争疯 2024-07-12 07:01:43

对我来说,官方文章中描述的依赖倒置原则确实是提高本质上可重用性较差的模块的可重用性的错误尝试,以及解决 C++ 语言中的问题的方法。

C++ 中的问题是头文件通常包含私有字段和方法的声明。 因此,如果高级 C++ 模块包含低级模块的头文件,它将取决于该模块的实际实现细节。 显然,这不是一件好事。 但对于当今常用的更现代的语言来说,这不是问题。

高级模块本质上比低级模块可重用性差,因为前者通常比后者更特定于应用程序/上下文。 例如,实现 UI 屏幕的组件是最高级别的,并且也非常(完全?)特定于应用程序。 尝试在不同的应用程序中重用此类组件会适得其反,并且只会导致过度设计。

因此,只有当组件 A 真正可在不同应用程序或上下文中重用时,才能在组件 A 的同一级别创建依赖于组件 B(不依赖于 A)的单独抽象。 如果情况并非如此,那么应用 DIP 将是糟糕的设计。

To me, the Dependency Inversion Principle, as described in the official article, is really a misguided attempt to increase the reusability of modules that are inherently less reusable, as well as a way to workaround an issue in the C++ language.

The issue in C++ is that header files typically contain declarations of private fields and methods. Therefore, if a high-level C++ module includes the header file for a low-level module, it will depend on actual implementation details of that module. And that, obviously, is not a good thing. But this is not an issue in the more modern languages commonly used today.

High-level modules are inherently less reusable than low-level modules because the former are normally more application/context specific than the latter. For example, a component that implements an UI screen is of the highest-level and also very (completely?) specific to the application. Trying to reuse such a component in a different application is counter-productive, and can only lead to over-engineering.

So, the creation of a separate abstraction at the same level of a component A that depends on a component B (which does not depend on A) can be done only if component A will really be useful for reuse in different applications or contexts. If that's not the case, then applying DIP would be bad design.

娇柔作态 2024-07-12 07:01:43

表述依赖倒置原则的一种更清晰的方法是:

封装复杂业务逻辑的模块不应该直接依赖于封装业务逻辑的其他模块。 相反,它们应该仅依赖于简单数据的接口。

即,不要像人们通常那样实现您的类 Logic

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

您应该执行以下操作:

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

DataDataFromDependency 应该与以下内容位于同一模块中: 逻辑,而不是依赖

为什么要这样做?

  1. 两个业务逻辑模块现已解耦。 当Dependency发生变化时,您不需要更改Logic
  2. 理解Logic 的作用是一个简单得多的任务:它只对看起来像 ADT 的东西进行操作。
  3. 现在可以更轻松地测试逻辑。 您现在可以使用虚假数据直接实例化 Data 并将其传入。无需模拟或复杂的测试脚手架。

A much clearer way to state the Dependency Inversion Principle is:

Your modules which encapsulate complex business logic should not depend directly on other modules which encapsulate business logic. Instead, they should depend only on interfaces to simple data.

I.e., instead of implementing your class Logic as people usually do:

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

you should do something like:

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

Data and DataFromDependency should live in the same module as Logic, not with Dependency.

Why do this?

  1. The two business logic modules are now decoupled. When Dependency changes, you don't need to change Logic.
  2. Understanding what Logic does is a much simpler task: it operates only on what looks like an ADT.
  3. Logic can now be more easily tested. You can now directly instantiate Data with fake data and pass it in. No need for mocks or complex test scaffolding.
缱绻入梦 2024-07-12 07:01:43

其他人已经在这里给出了很好的答案和很好的例子。

DIP 之所以重要,是因为它确保了 OO 原则“松散耦合设计”。

软件中的对象不应进入层次结构,其中某些对象是顶级对象,依赖于低级对象。 然后,低级对象的更改将波及到顶级对象,这使得软件对于更改非常脆弱。

您希望您的“顶级”对象非常稳定并且不易发生变化,因此您需要反转依赖关系。

Good answers and good examples are already given by others here.

The reason DIP is important is because it ensures the OO-principle "loosely coupled design".

The objects in your software should NOT get into a hierarchy where some objects are the top-level ones, dependent on low-level objects. Changes in low-level objects will then ripple-through to your top-level objects which makes the software very fragile for change.

You want your 'top-level' objects to be very stable and not fragile for change, therefore you need to invert the dependencies.

一影成城 2024-07-12 07:01:43

控制反转 (IoC) 是一种设计模式,其中对象由外部传递其依赖项框架,而不是询问框架的依赖关系。

使用传统查找的伪代码示例:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

使用 IoC 的类似代码:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

IoC 的好处是:

  • 您不依赖于中央
    框架,所以这可以改变,如果
    想要的。
  • 既然对象被创建了
    通过注射,最好使用
    接口,很容易创建单元
    将依赖项替换为的测试
    模拟版本。
  • 解耦代码。

Inversion of control (IoC) is a design pattern where an object gets handed its dependency by an outside framework, rather than asking a framework for its dependency.

Pseudocode example using traditional lookup:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

Similar code using IoC:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

The benefits of IoC are:

  • You have no dependency on a central
    framework, so this can be changed if
    desired.
  • Since objects are created
    by injection, preferably using
    interfaces, it's easy to create unit
    tests that replace dependencies with
    mock versions.
  • Decoupling off code.
巴黎夜雨 2024-07-12 07:01:43

除了一系列普遍好的答案之外,我想添加一个我自己的小样本来展示好的做法与坏的做法。 是的,我不是一个会扔石头的人!

假设您需要一个小程序通过控制台 I/O将字符串转换为 Base64 格式。 这是一种简单的方法:

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-level I/O functionality.
         * Hence, you'll have to fiddle with BadEncoder whenever you want to change
         * the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
         * problems with I/O shouldn't break the encoder!
         */
        BadEncoder.Run();            
    }
}

public static class BadEncoder
{
    public static void Run()
    {
        Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
    }
}    

DIP 基本上表示高级组件不应依赖于低级实现,其中“级别”是根据 Robert C. Martin(“清洁架构”)与 I/O 的距离。 但如何摆脱这种困境呢? 只需让中央编码器仅依赖于接口,而不关心它们是如何实现的:

class Program
{
    static void Main(string[] args)
    {           
        /* Demo of the Dependency Inversion Principle (= "High-level functionality
         * should not depend upon low-level implementations"): 
         * You can easily implement new I/O methods like
         * ConsoleReader, ConsoleWriter without ever touching the high-level
         * Encoder class!!!
         */            
        GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter());        }
}

public static class GoodEncoder
{
    public static void Run(IReadable input, IWriteable output)
    {
        output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
    }
}

public interface IReadable
{
    string ReadInput();
}

public interface IWriteable
{
    void WriteOutput(string txt);
}

public class ConsoleReader : IReadable
{
    public string ReadInput()
    {
        return Console.ReadLine();
    }
}

public class ConsoleWriter : IWriteable
{
    public void WriteOutput(string txt)
    {
        Console.WriteLine(txt);
    }
}

请注意,您不需要触摸 GoodEncoder 来更改 I/O 模式 - 该类对它知道的 I/O 接口; IReadableIWriteable 的任何低级实现都不会打扰它。

Adding to the flurry of generally good answers, I'd like to add a tiny sample of my own to demonstrate good vs. bad practice. And yes, I'm not one to throw stones!

Say, you want a little program to convert a string into base64 format via console I/O. Here's the naive approach:

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-level I/O functionality.
         * Hence, you'll have to fiddle with BadEncoder whenever you want to change
         * the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
         * problems with I/O shouldn't break the encoder!
         */
        BadEncoder.Run();            
    }
}

public static class BadEncoder
{
    public static void Run()
    {
        Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
    }
}    

The DIP basically says that high-level components shouldn't be dependent on low-level implementation, where "level" is the distance from I/O according to Robert C. Martin ("Clean Architecture"). But how do you get out of this predicament? Simply by making the central Encoder dependent only on interfaces without bothering how those are implemented:

class Program
{
    static void Main(string[] args)
    {           
        /* Demo of the Dependency Inversion Principle (= "High-level functionality
         * should not depend upon low-level implementations"): 
         * You can easily implement new I/O methods like
         * ConsoleReader, ConsoleWriter without ever touching the high-level
         * Encoder class!!!
         */            
        GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter());        }
}

public static class GoodEncoder
{
    public static void Run(IReadable input, IWriteable output)
    {
        output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
    }
}

public interface IReadable
{
    string ReadInput();
}

public interface IWriteable
{
    void WriteOutput(string txt);
}

public class ConsoleReader : IReadable
{
    public string ReadInput()
    {
        return Console.ReadLine();
    }
}

public class ConsoleWriter : IWriteable
{
    public void WriteOutput(string txt)
    {
        Console.WriteLine(txt);
    }
}

Note that you don't need to touch GoodEncoder in order to change the I/O mode — that class is happy with the I/O interfaces it knows; any low-level implementation of IReadable and IWriteable won't ever bother it.

和我恋爱吧 2024-07-12 07:01:43

依赖倒置原则 (DIP) 指出

i) 高层模块不应依赖于低层模块。 两者都应该依赖于抽象。

ii) 抽象永远不应该依赖于细节。 细节应该取决于抽象。

示例:

    public interface ICustomer
    {
        string GetCustomerNameById(int id);
    }

    public class Customer : ICustomer
    {
        //ctor
        public Customer(){}

        public string GetCustomerNameById(int id)
        {
            return "Dummy Customer Name";
        }
    }

    public class CustomerFactory
    {
        public static ICustomer GetCustomerData()
        {
            return new Customer();
        }
    }

    public class CustomerBLL
    {
        ICustomer _customer;
        public CustomerBLL()
        {
            _customer = CustomerFactory.GetCustomerData();
        }

        public string GetCustomerNameById(int id)
        {
            return _customer.GetCustomerNameById(id);
        }
    }

    public class Program
    {
        static void Main()
        {
            CustomerBLL customerBLL = new CustomerBLL();
            int customerId = 25;
            string customerName = customerBLL.GetCustomerNameById(customerId);


            Console.WriteLine(customerName);
            Console.ReadKey();
        }
    }

注意:类应该依赖于接口或抽象类等抽象概念,而不是具体细节(接口的实现)。

Dependency Inversion Principle (DIP) says that

i) High level modules should not depend upon low-level modules. Both should depend upon abstractions.

ii) Abstractions should never depend upon details. Details should depend upon abstractions.

Example:

    public interface ICustomer
    {
        string GetCustomerNameById(int id);
    }

    public class Customer : ICustomer
    {
        //ctor
        public Customer(){}

        public string GetCustomerNameById(int id)
        {
            return "Dummy Customer Name";
        }
    }

    public class CustomerFactory
    {
        public static ICustomer GetCustomerData()
        {
            return new Customer();
        }
    }

    public class CustomerBLL
    {
        ICustomer _customer;
        public CustomerBLL()
        {
            _customer = CustomerFactory.GetCustomerData();
        }

        public string GetCustomerNameById(int id)
        {
            return _customer.GetCustomerNameById(id);
        }
    }

    public class Program
    {
        static void Main()
        {
            CustomerBLL customerBLL = new CustomerBLL();
            int customerId = 25;
            string customerName = customerBLL.GetCustomerNameById(customerId);


            Console.WriteLine(customerName);
            Console.ReadKey();
        }
    }

Note: Class should depend on abstractions like interface or abstract classes, not specific details (implementation of interface).

四叶草在未来唯美盛开 2024-07-12 07:01:43

依赖倒置:依赖于抽象,而不是具体。

控制反转:主要与抽象,以及主要如何成为系统的粘合剂。

DIP 和 IoC

这些是一些讨论此问题的好帖子:

https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt -avoid-it/

https: //coderstower.com/2019/04/02/main-and-abstraction-the-de Coupled-peers/

https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/

Dependency inversion: Depend on abstractions, not on concretions.

Inversion of control: Main vs Abstraction, and how the Main is the glue of the systems.

DIP and IoC

These are some good posts talking about this:

https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/

https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/

https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/

め可乐爱微笑 2024-07-12 07:01:43

依赖倒置原则(DIP)

它是SOLID的一部分[关于]< /a> 是 OOD 的一部分,由 Uncle Bob 引入。 它是关于类(层...)之间的松散耦合。 类不应该依赖于具体实现,类应该依赖于抽象/接口

问题:

//A -> B
class A {
  B b

  func foo() {
     b = B();
  }
}

解决方案:

//A -> IB <|- B
//client[A -> IB] <|- B is the Inversion 
class A {
  IB ib // An abstraction between High level module A and low level module B

  func foo() {
     ib = B()
  }
}

现在A不依赖于B(一对一),现在A依赖于B实现的接口IB,这意味着A取决于IB的多个实现(一对多)

[DIP vs DI vs IoC]

Dependency Inversion Principle(DIP)

It is a part of SOLID[About] which is a part of OOD and was introduced by Uncle Bob. It is about loose coupling between classes(layers...). Class should not be depended on concrete realization, class should be depended on abstraction/interface

Problem:

//A -> B
class A {
  B b

  func foo() {
     b = B();
  }
}

Solution:

//A -> IB <|- B
//client[A -> IB] <|- B is the Inversion 
class A {
  IB ib // An abstraction between High level module A and low level module B

  func foo() {
     ib = B()
  }
}

Now A is not depended on B(one to one), now A is depended on interface IB which is implemented by B, it means that A depends on multiple realization of IB(one to many)

[DIP vs DI vs IoC]

白馒头 2024-07-12 07:01:43

依赖倒置的目的是制作可重用的软件。

这个想法是,它们不是依赖于彼此的两段代码,而是依赖于一些抽象的接口。 然后您可以重复使用其中一个而不需要另一个。

最常见的实现方式是通过控制反转 (IoC) 容器,例如 Java 中的 Spring。 在这个模型中,对象的属性是通过 XML 配置来设置的,而不是对象出去寻找它们的依赖关系。

想象一下这个伪代码...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

MyClass 直接依赖于 Service 类和 ServiceLocator 类。 如果您想在另一个应用程序中使用它,则需要这两者。 现在想象一下...

public class MyClass
{
  public IService myService;
}

现在,MyClass 依赖于一个接口,即 IService 接口。 我们会让 IoC 容器实际设置该变量的值。

因此,现在 MyClass 可以轻松地在其他项目中重用,而无需带来其他两个类的依赖关系。

更好的是,您不必拖动 MyService 的依赖项以及这些依赖项的依赖项,并且......好吧,您明白了。

The point of dependency inversion is to make reusable software.

The idea is that instead of two pieces of code relying on each other, they rely on some abstracted interface. Then you can reuse either piece without the other.

The way this is most commonly achieved is through an inversion of control (IoC) container like Spring in Java. In this model, properties of objects are set up through an XML configuration instead of the objects going out and finding their dependency.

Imagine this pseudocode...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

MyClass directly depends on both the Service class and the ServiceLocator class. It needs both of those if you want to use it in another application. Now imagine this...

public class MyClass
{
  public IService myService;
}

Now, MyClass relies on a single interface, the IService interface. We'd let the IoC container actually set the value of that variable.

So now, MyClass can easily be reused in other projects, without bringing the dependency of those other two classes along with it.

Even better, you don't have to drag the dependencies of MyService, and the dependencies of those dependencies, and the... well, you get the idea.

孤单情人 2024-07-12 07:01:43

如果我们可以假定公司的“高级”员工因执行其计划而获得报酬,并且这些计划是通过许多“低级”员工计划的总体执行来交付的,那么我们可以说如果高级员工的计划描述以任何方式与任何较低级别员工的具体计划相结合,那么这通常是一个糟糕的计划。

如果一位高层管理人员有一个“改善交货时间”的计划,并指示航运公司的员工每天早上必须喝咖啡并做伸展运动,那么该计划是高度耦合且内聚性较低的。 但是,如果该计划没有提及任何特定员工,而实际上只是要求“可以执行工作的实体已准备好工作”,那么该计划是松散耦合的并且更具凝聚力:计划不会重叠并且可以轻松替换。 承包商或机器人可以轻松替换员工,而高层的计划保持不变。

依赖倒置原则中的“高级”意味着“更重要”。

If we can take it as a given that a "high level" employee at a corporation is paid for the execution of their plans, and that these plans are delivered by the aggregate execution of many "low level" employee's plans, then we could say it is generally a terrible plan if the high level employee's plan description in any way is coupled to the specific plan of any lower level employee.

If a high level executive has a plan to "improve delivery time", and indicates that an employee in the shipping line must have coffee and do stretches each morning, then that plan is highly coupled and has low cohesion. But if the plan makes no mention of any specific employee, and in fact simply requires "an entity that can perform work is prepared to work", then the plan is loosely coupled and more cohesive: the plans do not overlap and can easily be substituted. Contractors, or robots, can easily replace the employees and the high level's plan remains unchanged.

"High level" in the dependency inversion principle means "more important".

清醇 2024-07-12 07:01:43

我可以看到上面的答案已经给出了很好的解释。 不过我想用简单的例子提供一些简单的解释。

依赖倒置原则允许程序员删除硬编码的依赖关系,以便应用程序变得松散耦合和可扩展。

如何实现这一点:通过抽象

< strong>没有依赖倒置:

 class Student {
    private Address address;

    public Student() {
        this.address = new Address();
    }
}
class Address{
    private String perminentAddress;
    private String currentAdrress;

    public Address() {
    }
} 

在上面的代码片段中,地址对象是硬编码的。 相反,如果我们可以使用依赖倒置并通过传递构造函数或 setter 方法来注入地址对象。 让我们看看。

依赖反转:

class Student{
    private Address address;

    public Student(Address address) {
        this.address = address;
    }
    //or
    public void setAddress(Address address) {
        this.address = address;
    }
}

I can see good explanation has been given in above answers. However i wants to provide some easy explanation with simple example.

Dependency Inversion Principle allows the programmer to remove the hardcoded dependencies so that the application becomes loosely coupled and extendable.

How to achieve this : through abstraction

Without dependency inversion:

 class Student {
    private Address address;

    public Student() {
        this.address = new Address();
    }
}
class Address{
    private String perminentAddress;
    private String currentAdrress;

    public Address() {
    }
} 

In above code snippet, address object is hard-coded. Instead if we can use dependency inversion and inject the address object by passing through constructor or setter method. Let's see.

With dependency inversion:

class Student{
    private Address address;

    public Student(Address address) {
        this.address = address;
    }
    //or
    public void setAddress(Address address) {
        this.address = address;
    }
}
怎樣才叫好 2024-07-12 07:01:42

它是什么?

《敏捷软件开发、原则、模式和实践》和《C# 中的敏捷原则、模式和实践》一书是充分理解依赖倒置原则背后的原始目标和动机的最佳资源。 《依赖倒置原则》一文也是一个很好的资源,但由于它是最终进入前面提到的书籍的草稿的浓缩版本,因此它遗漏了一些关于依赖倒置概念的重要讨论。包和接口所有权是区分这一原则与《设计模式》(Gamma 等人)一书中“针对接口编程,而不是实现”的更一般建议的关键。

总结一下,依赖倒置原则主要是反转从“较高级别”组件到“较低级别”组件的传统依赖方向,使得“较低级别”组件依赖于接口<由“更高级别”组件拥有。 (注意:这里的“更高级别”组件是指需要外部依赖/服务的组件,不一定是其在分层架构中的概念位置。)这样做时,耦合并没有减少,而是减少了。从理论上价值较低的组件转移到理论上价值较高的组件。

这是通过设计组件来实现的,这些组件的外部依赖性以接口的形式表达,而组件的使用者必须为其提供实现。 换句话说,定义的接口表达了组件需要什么,而不是如何使用组件(例如“INeedSomething”,而不是“IDoSomething”)。

依赖倒置原则没有指的是通过使用接口(例如MyService → [ILogger ⇐ Logger])抽象依赖关系的简单实践。 虽然这将组件与依赖关系的具体实现细节解耦,但它并没有反转消费者和依赖关系之间的关系(例如 [MyService → IMyServiceLogger] ⇐ Logger。

为什么它很重要?

可以提炼出依赖关系倒置原则的重要性归结为能够重用软件组件的单一目标,这些组件的部分功能(日志记录、验证等)依赖于外部依赖项。

在重用的总体目标中,我们可以描述重用的两种子类型:

  1. 在具有子依赖实现的多个应用程序中使用软件组件(例如,您开发了一个 DI 容器并希望提供日志记录,但不希望将您的容器耦合到特定记录器,这样每个使用您容器的人都必须也可以使用您选择的日志库)。

  2. 在不断发展的环境中使用软件组件(例如,您开发的业务逻辑组件在实现细节不断发展的应用程序的多个版本中保持不变)。

对于跨多个应用程序重用组件的第一种情况(例如使用基础设施库),目标是为您的消费者提供核心基础设施需求,而不将您的消费者耦合到您自己的库的子依赖项,因为耦合到此类依赖项需要您的消费者也需要相同的依赖关系。 当您的库的使用者选择使用不同的库来满足相同的基础设施需求(例如 NLog 与 log4net),或者如果他们选择使用所需库的更高版本(该版本不向后兼容该版本)时,这可能会出现问题您的图书馆需要。

对于重用业务逻辑组件(即“更高级别的组件”)的第二种情况,目标是将应用程序的核心域实现与实现细节不断变化的需求(即更改/升级持久性库、消息传递库)隔离开来。 、加密策略等)。 理想情况下,更改应用程序的实现细节不应破坏封装应用程序业务逻辑的组件。

注意:有些人可能反对将第二种情况描述为实际重用,理由是在单个不断发展的应用程序中使用的组件(例如业务逻辑组件)仅代表单次使用。 然而,这里的想法是,对应用程序实现细节的每次更改都会呈现一个新的上下文,从而呈现一个不同的用例,尽管最终目标可以区分为隔离与可移植性。

虽然在第二种情况下遵循依赖倒置原则可以带来一些好处,但应该注意的是,它应用于 Java 和 C# 等现代语言的价值大大降低,甚至可能到了无关紧要的程度。 如前所述,DIP 涉及将实现细节完全分离到单独的包中。 然而,在不断发展的应用程序的情况下,简单地利用根据业务领域定义的接口将防止由于实现细节组件的需求变化而需要修改更高级别的组件,即使实现细节最终驻留在同一个包中。 原则的这一部分反映了在原则编纂时与所考虑的语言(即 C++)相关的​​方面,这些方面与较新的语言无关。 也就是说,依赖倒置原则的重要性主要在于可重用软件组件/库的开发。

有关此原则的详细讨论,因为它涉及接口的简单使用、依赖注入和分离接口模式,可以在 此处。 此外,可以找到该原理如何与动态类型语言(例如 JavaScript)相关的讨论 这里

What Is It?

The books Agile Software Development, Principles, Patterns, and Practices and Agile Principles, Patterns, and Practices in C# are the best resources for fully understanding the original goals and motivations behind the Dependency Inversion Principle. The article "The Dependency Inversion Principle" is also a good resource, but due to the fact that it is a condensed version of a draft which eventually made its way into the previously mentioned books, it leaves out some important discussion on the concept of a package and interface ownership which are key to distinguishing this principle from the more general advise to "program to an interface, not an implementation" found within the book Design Patterns (Gamma, et. al).

To provide a summary, the Dependency Inversion Principle is primarily about reversing the conventional direction of dependencies from "higher level" components to "lower level" components such that "lower level" components are dependent upon the interfaces owned by the "higher level" components. (Note: "higher level" component here refers to the component requiring external dependencies/services, not necessarily its conceptual position within a layered architecture.) In doing so, coupling isn't reduced so much as it is shifted from components that are theoretically less valuable to components which are theoretically more valuable.

This is achieved by designing components whose external dependencies are expressed in terms of an interface for which an implementation must be provided by the consumer of the component. In other words, the defined interfaces express what is needed by the component, not how you use the component (e.g. "INeedSomething", not "IDoSomething").

What the Dependency Inversion Principle does not refer to is the simple practice of abstracting dependencies through the use of interfaces (e.g. MyService → [ILogger ⇐ Logger]). While this decouples a component from the specific implementation detail of the dependency, it does not invert the relationship between the consumer and dependency (e.g. [MyService → IMyServiceLogger] ⇐ Logger.

Why Is It Important?

The importance of the Dependency Inversion Principle can be distilled down to a singular goal of being able to reuse software components which rely upon external dependencies for a portion of their functionality (logging, validation, etc.)

Within this general goal of reuse, we can delineate two sub-types of reuse:

  1. Using a software component within multiple applications with sub-dependency implementations (e.g. You've developed a DI container and want to provide logging, but don't want to couple your container to a specific logger such that everyone that uses your container has to also use your chosen logging library).

  2. Using software components within an evolving context (e.g. You've developed business-logic components which remain the same across multiple versions of an application where the implementation details are evolving).

With the first case of reusing components across multiple applications, such as with an infrastructure library, the goal is to provide a core infrastructure need to your consumers without coupling your consumers to sub-dependencies of your own library since coupling to such dependencies requires your consumers to require the same dependencies as well. This can be problematic when consumers of your library choose to use a different library for the same infrastructure needs (e.g. NLog vs. log4net), or if they choose to use a later version of the required library which isn't backward compatible with the version required by your library.

With the second case of reusing business-logic components (i.e. "higher-level components"), the goal is to isolate the core domain implementation of your application from the changing needs of your implementation details (i.e. changing/upgrading persistence libraries, messaging libraries, encryption strategies, etc.). Ideally, changing the implementation details of an application shouldn't break the components encapsulating the application's business logic.

Note: Some may object to describing this second case as actual reuse, reasoning that components such as business-logic components used within a single evolving application represents only a single use. The idea here, however, is that each change to the application's implementation details renders a new context and therefore a different use case, though the ultimate goals could be distinguished as isolation vs. portability.

While following the Dependency Inversion Principle in this second case can offer some benefit, it should be noted that its value as applied to modern languages such as Java and C# is much reduced, perhaps to the point of being irrelevant. As discussed earlier, the DIP involves separating implementation details into separate packages completely. In the case of an evolving application, however, simply utilizing interfaces defined in terms of the business domain will guard against needing to modify higher-level components due to changing needs of implementation detail components, even if the implementation details ultimately reside within the same package. This portion of the principle reflects aspects that were pertinent to the language in view when the principle was codified (i.e. C++) which aren't relevant to newer languages. That said, the importance of the Dependency Inversion Principle primarily lies with the development of reusable software components/libraries.

A longer discussion of this principle as it relates to the simple use of interfaces, Dependency Injection, and the Separated Interface pattern can be found here. Additionally, a discussion of how the principle relates to dynamically-typed languages such as JavaScript can be found here.

始终不够爱げ你 2024-07-12 07:01:42

良好的依赖倒置应用可以为应用程序的整个架构提供灵活性和稳定性。 它将让您的应用程序更加安全和稳定地发展。

传统的分层架构

传统的分层架构 UI 依赖于业务层,而业务层又依赖于数据访问层。

< /a>

你必须了解层、包或库。 让我们看看代码是怎样的。

我们将为数据访问层提供一个库或包。

// DataAccessLayer.dll
public class ProductDAO {

}

另一个库或包层业务逻辑依赖于数据访问层。

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

具有依赖倒置的分层架构

依赖倒置表示以下内容:

高级模块不应依赖于低级模块。 两者都应该依赖于抽象。

抽象不应依赖于细节。 细节应取决于抽象。

什么是高层模块和低层模块? 考虑诸如库或包之类的模块,高级模块将是那些传统上具有依赖性的模块以及它们所依赖的低级模块。

换句话说,模块高级别将是调用操作的位置,而低级别将是执行操作的位置。

从这个原则得出的一个合理的结论是,具体之间不应该存在依赖关系,但必须存在对抽象的依赖关系。 但根据我们采取的方法,我们可能会误用投资依赖依赖性,而是一种抽象。

想象一下,我们按如下方式调整代码:

我们将有一个用于定义抽象的数据访问层的库或包。

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

另一个库或包层业务逻辑依赖于数据访问层。

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

尽管我们依赖于业务和数据访问之间的抽象依赖性,但它仍然保持不变。

< /a>

为了实现依赖倒置,持久化接口必须定义在高级逻辑或域所在的模块或包中,而不是低级模块中。

首先定义什么是领域层,其通信的抽象是定义持久性。

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

在持久层依赖于域之后,如果定义了依赖关系,现在就开始反转。

// Persistence.dll
public class ProductDAO : IProductRepository{

}

< /a>
(来源:
xurxodev.com)< /sub>

深化原理

很好地理解概念、深化目的和好处很重要。 如果我们机械地停留在典型案例库中,我们将无法确定在哪里可以应用依赖原则。

但为什么我们要反转依赖关系呢? 除了具体例子之外,主要目标是什么?

这种情况通常允许最稳定的事物(不依赖于不太稳定的事物)更频繁地更改。

持久性类型更容易更改,无论是数据库还是访问同一数据库的技术与设计用于持久性通信的域逻辑或操作相比。 因此,依赖性被逆转,因为如果发生这种变化,更容易改变持久性。 这样我们就不必更改域。 领域层是最稳定的,这就是为什么它不应该依赖于任何东西。

但不仅仅是这个存储库示例。 这个原则适用的场景有很多,也有基于这个原则的架构。

架构

在某些架构中,依赖倒置是其定义的关键。 在所有领域中,它是最重要的,它是抽象,它将指示域与定义的其余包或库之间的通信协议。

架构

干净架构域中的干净 位于中心,如果你沿着指示依赖关系的箭头方向看,很清楚什么是最重要和最稳定的层。 外层被认为是不稳定的工具,因此请避免依赖它们。

< /a>
(来源:
8thlight.com

六边形架构

它的发生方式与六边形相同架构,其中域也位于中心部分,端口是多米诺骨牌向外通信的抽象。 在这里,很明显,领域是最稳定的,传统的依赖关系是相反的。

< /a>
(来源:
pragprog.com

Dependency inversion well applied gives flexibility and stability at the level of the entire architecture of your application. It will allow your application to evolve more securely and stable.

Traditional layered architecture

Traditionally a layered architecture UI depended on the business layer and this in turn depended on the data access layer.

You have to understand layer, package, or library. Let's see how the code would be.

We would have a library or package for the data access layer.

// DataAccessLayer.dll
public class ProductDAO {

}

And another library or package layer business logic that depends on the data access layer.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

Layered architecture with dependency inversion

The dependency inversion indicates the following:

High-level modules should not depend on low-level modules. Both should depend on abstractions.

Abstractions should not depend on details. Details should depend on abstractions.

What are the high-level modules and low level? Thinking modules such as libraries or packages, high-level module would be those that traditionally have dependencies and low level on which they depend.

In other words, module high level would be where the action is invoked and low level where the action is performed.

A reasonable conclusion to draw from this principle is that there should be no dependence between concretions, but there must be a dependence on an abstraction. But according to the approach we take we can be misapplying investment depend dependency, but an abstraction.

Imagine that we adapt our code as follows:

We would have a library or package for the data access layer which define the abstraction.

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

And another library or package layer business logic that depends on the data access layer.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

Although we are depending on an abstraction dependency between business and data access remains the same.

To get dependency inversion, the persistence interface must be defined in the module or package where this high level logic or domain is and not in the low-level module.

First define what the domain layer is and the abstraction of its communication is defined persistence.

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

After the persistence layer depends on the domain, getting to invert now if a dependency is defined.

// Persistence.dll
public class ProductDAO : IProductRepository{

}


(source: xurxodev.com)

Deepening the principle

It is important to assimilate the concept well, deepening the purpose and benefits. If we stay in mechanically and learn the typical case repository, we will not be able to identify where we can apply the principle of dependence.

But why do we invert a dependency? What is the main objective beyond specific examples?

Such commonly allows the most stable things, that are not dependent on less stable things, to change more frequently.

It is easier for the persistence type to be changed, either the database or technology to access the same database than the domain logic or actions designed to communicate with persistence. Because of this, the dependence is reversed because as it is easier to change the persistence if this change occurs. In this way we will not have to change the domain. The domain layer is the most stable of all, which is why it should not depend on anything.

But there is not just this repository example. There are many scenarios where this principle applies and there are architectures based on this principle.

Architectures

There are architectures where dependency inversion is key to its definition. In all the domains it is the most important and it is abstractions that will indicate the communication protocol between the domain and the rest of the packages or libraries are defined.

Clean Architecture

In Clean architecture the domain is located in the center and if you look in the direction of the arrows indicating dependency, it is clear what are the most important and stable layers. The outer layers are considered unstable tools so avoid depending on them.


(source: 8thlight.com)

Hexagonal Architecture

It happens the same way with the hexagonal architecture, where the domain is also located in the central part and ports are abstractions of communication from the domino outward. Here again it is evident that the domain is the most stable and traditional dependence is inverted.


(source: pragprog.com)

迎风吟唱 2024-07-12 07:01:42

查看此文档:依赖倒置原则

它基本上是说:

  • 高级模块不应该依赖于低级模块。 两者都应该依赖于抽象。
  • 抽象永远不应该依赖于细节。 细节应该取决于抽象。

至于为什么它很重要,简而言之:更改是有风险的,通过依赖概念而不是实现,您可以减少调用站点的更改需求。

DIP 有效地减少了不同代码段之间的耦合。 这个想法是,虽然有很多实现(例如,日志记录工具)的方法,但是您使用它的方式应该在时间上相对稳定。 如果您可以提取一个代表日志记录概念的接口,那么该接口在时间上应该比其实现更加稳定,并且调用站点受您在维护或扩展该日志记录机制时所做的更改的影响应该小得多。

通过使实现依赖于接口,您可以在运行时选择更适合您的特定环境的实现。 根据具体情况,这可能也很有趣。

Check this document out: The Dependency Inversion Principle.

It basically says:

  • High level modules should not depend upon low-level modules. Both should depend upon abstractions.
  • Abstractions should never depend upon details. Details should depend upon abstractions.

As to why it is important, in short: changes are risky, and by depending on a concept instead of on an implementation, you reduce the need for change at call sites.

Effectively, the DIP reduces coupling between different pieces of code. The idea is that although there are many ways of implementing, say, a logging facility, the way you would use it should be relatively stable in time. If you can extract an interface that represents the concept of logging, this interface should be much more stable in time than its implementation, and call sites should be much less affected by changes you could make while maintaining or extending that logging mechanism.

By also making the implementation depend on an interface, you get the possibility to choose at run-time which implementation is better suited for your particular environment. Depending on the cases, this may be interesting too.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文