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


当前回答

查看这个文档:依赖倒置原则。

它基本上是说:

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

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

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

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

其他回答

除了一大堆不错的答案之外,我还想举一个我自己的小例子来说明好的和坏的做法。是的,我不是那种会扔石头的人!

比如说,你想要一个小程序通过控制台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接口很满意;任何IReadable和IWriteable的低级实现都不会打扰它。

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

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

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

没有依赖反转:

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

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

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

软件中的对象不应该进入一个层次结构,其中一些对象是顶级对象,依赖于低级对象。底层对象的变化会波及到顶层对象,这使得软件非常容易发生变化。

你希望你的“顶级”对象非常稳定,不容易改变,因此你需要颠倒依赖关系。

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

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

控制反转(IoC)是一种设计模式,在这种模式下,对象通过外部框架获得其依赖项,而不是向框架请求其依赖项。

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

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

使用IoC的类似代码:

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

国际奥委会的好处是:

您不依赖于中心 框架,所以这可以改变如果 想要的。 因为对象是被创建的 以注射方式使用为佳 接口,很容易创建单元 替换依赖项的测试 模拟版本。 解耦代码。