什么是依赖倒置原则?为什么它很重要?
当前回答
依赖倒置原则(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();
}
}
注意:类应该依赖于抽象,如接口或抽象类,而不是特定的细节(接口的实现)。
其他回答
基本上它说:
类应该依赖于抽象(例如接口,抽象类),而不是特定的细节(实现)。
如果我们可以假定公司的“高级”员工是通过执行他们的计划来获得报酬的,并且这些计划是由许多“低级”员工的计划的综合执行来交付的,那么我们可以说,如果高级员工的计划描述以任何方式与任何低级员工的具体计划耦合在一起,那么这通常是一个糟糕的计划。
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.
“高级”在依赖倒置原则中意味着“更重要”。
查看这个文档:依赖倒置原则。
它基本上是说:
高级模块不应该依赖于低级模块。两者都应该依赖于抽象。 抽象永远不应该依赖于细节。细节应该依赖于抽象。
至于为什么它很重要,简而言之:更改是有风险的,通过依赖于概念而不是实现,您减少了在调用站点更改的需求。
DIP有效地减少了不同代码段之间的耦合。其思想是,尽管有许多实现方法,比如日志记录工具,但使用它的方式应该在时间上相对稳定。如果您可以提取一个表示日志记录概念的接口,那么这个接口在时间上应该比它的实现稳定得多,并且调用站点在维护或扩展日志记录机制时受更改的影响应该小得多。
通过使实现依赖于接口,您可以在运行时选择哪个实现更适合您的特定环境。根据具体情况,这可能也很有趣。
依赖倒置:依赖抽象,而不是具体。
控制反转:主要与抽象,以及主要如何成为系统的粘合剂。
以下是一些关于这方面的好文章:
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/
除了一大堆不错的答案之外,我还想举一个我自己的小例子来说明好的和坏的做法。是的,我不是那种会扔石头的人!
比如说,你想要一个小程序通过控制台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的低级实现都不会打扰它。