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


当前回答

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

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

以下是一些关于这方面的好文章:

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/

其他回答

我可以在上面的回答中看到很好的解释。然而,我想用简单的例子提供一些简单的解释。

依赖倒置原则允许程序员删除硬编码的依赖项,这样应用程序就变得松散耦合和可扩展。

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

没有依赖反转:

 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;
    }
}

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

其思想是,两段代码不再相互依赖,而是依赖于一些抽象的接口。然后你可以在没有另一块的情况下重复使用其中的任何一块。

最常见的实现方式是通过控制反转(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的依赖项,以及这些依赖项的依赖项,以及…好吧,你懂的。

依赖倒置的良好应用在应用程序的整个体系结构层面上提供了灵活性和稳定性。它将允许您的应用程序更安全、更稳定地发展。

传统分层建筑

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

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

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

// 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;
}

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

要获得依赖项反转,持久性接口必须定义在高级逻辑或域所在的模块或包中,而不是定义在低级模块中。

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

// Domain.dll
public interface IProductRepository;

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

在持久化层依赖于域之后,如果定义了依赖项,就可以进行反转。

// Persistence.dll
public class ProductDAO : IProductRepository{

}

(来源:xurxodev.com)

深化原则

重要的是要吸收好概念,深化目的和效益。如果我们机械地停留在学习典型案例存储库中,我们将无法确定在哪里可以应用依赖原则。

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

这通常允许最稳定的东西,不依赖于不太稳定的东西,更频繁地变化。

持久化类型的更改(访问相同数据库的数据库或技术)比设计用于与持久化通信的域逻辑或操作更容易。因此,这种依赖是相反的,因为如果发生这种更改,则更容易更改持久性。这样我们就不需要改变定义域了。域层是所有层中最稳定的,这就是为什么它不应该依赖于任何东西。

但不只是这个存储库示例。有许多应用此原则的场景,并且有基于此原则的架构。

体系结构

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

干净的建筑

在Clean体系结构中,域位于中心,如果您查看指示依赖关系的箭头方向,就可以清楚地看到哪些是最重要和最稳定的层。外层被认为是不稳定的工具,因此避免依赖它们。

(来源:8 thlight.com)

六角结构

这与六边形体系结构发生的情况相同,其中域也位于中心部分,端口是从多米诺骨牌向外通信的抽象。这里很明显,域是最稳定的,传统的依赖关系是颠倒的。

(来源:pragprog.com)

依赖倒置原则(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();
        }
    }

注意:类应该依赖于抽象,如接口或抽象类,而不是特定的细节(接口的实现)。

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

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

以下是一些关于这方面的好文章:

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/