依赖项反转原理是什么,为什么重要?


177

依赖项反转原理是什么,为什么重要?



3
这里使用维基百科的“高级别”和“低级别”术语可笑的数量很多。这些术语不可访问,并导致许多读者访问此页面。如果您要反驳维基百科,请在您的答案中定义这些术语以提供上下文!
8bitjunkie

Answers:


106

签出该文档:Dependency Inversion Principle

它基本上说:

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

简而言之,为何重要:变更是有风险的,并且通过依赖概念而非实施来减少呼叫站点的变更需求。

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

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


31
这个答案并没有说明为什么DIP很重要,甚至没有说明DIP是什么。我已经阅读了DIP的官方文档,并认为这确实是一个糟糕且不合理的“原则”,因为它基于一个错误的假设:高级模块是可重用的。
罗杰里奥2009年

3
考虑一些对象的依赖图。将DIP应用于对象。现在,任何对象都将独立于其他对象的实现。单元测试现在很简单。以后可以进行重构以便重用。设计变更的变更范围非常有限。设计问题不会层叠。另请参见AI模式“ Blackboard”以获取数据依赖性反转。总之,非常强大的工具可以使软件易于理解,可维护和可靠。在这种情况下,请忽略依赖项注入。这无关。
Tim Williscroft 09年

6
使用类B的类A并不意味着A“取决于” B的实现;它仅取决于B的公共接口。添加一个现在仅依赖于A和B的抽象,这意味着A将不再对B的公共接口具有编译时依赖性。无需这些额外的抽象就可以轻松实现单元之间的隔离。在Java和.NET中,有专门的模拟工具可以处理所有情况(静态方法,构造函数等)。应用DIP往往会使软件更复杂,更难以维护,并且不再具有可测试性。
罗杰里奥2009年

1
那么什么是控制反转?一种实现依赖倒置的方法?
user20358 2012年

2
@Rogerio,请参阅下面的Derek Greer回复,他对此进行了解释。AFAIK,DIP说,是A决定了A的需求,而不是B陈述了A的需求。因此,该接口是A的需求不应该由B中给出,但对于A.
ejaenv

144

《 C#中的敏捷软件开发,原理,模式和实践》以及《 C#中的敏捷原理,模式和实践》是充分理解依赖反转原理背后的原始目标和动机的最佳资源。“依赖倒置原则”一文也是一个很好的参考资料,但是由于它是草案的精简版,最终被引入到前面提到的书中,因此它省略了一些关于“概念”的重要讨论。包和接口所有权是区分此原理与更一般的建议的关键,建议在“设计模式”一书(Gamma等)中找到“对接口进行编程,而不是对实现进行编程”。

为了提供总之,依赖倒置原则主要是关于反转从“层次”组件“较低级”组件具有依赖性,使得“较低级”组件是依赖于接口的常规方向拥有的“更高级别”的部件。(注意:这里的“更高级别”组件是指需要外部依赖项/服务的组件,而不一定是其在分层体系结构中的概念位置。)这样做时,耦合并没有减少太多,因为它从理论上的组件转移了对理论上更有价值的组件而言,价值较小。

这是通过设计组件来实现的,这些组件的外部依赖性通过接口来表示,组件的使用者必须为其提供实现。换句话说,定义的接口表示组件所需的内容,而不是组件的使用方式(例如,“ IneedSomething”而不是“ IDoSomething”)。

依赖关系反转原理所指的不是通过使用接口(例如MyService→[ILogger→Logger])抽象依赖关系的简单实践。尽管这使组件与依赖项的特定实现细节脱钩,但它不会反转使用者和依赖项之间的关系(例如[MyService→IMyServiceLogger]⇐Logger。

依赖倒置原则的重要性可以归结为一个单一目标,即能够重用依赖于外部依赖的软件组件来实现其功能的一部分(记录,验证等)。

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

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

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

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

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

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

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

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


17
谢谢。我现在看到我的答案错了重点。MyService→[ ILogger⇐ 记录器][MyService→IMyServiceLogger]⇐记录器之间的区别是细微但重要的。
Patrick McElhaney,2009年

2
在同一条直线其很好的解释在这里:lostechies.com/derickbailey/2011/09/22/...
ejaenv

1
我希望人们如何停止将记录器用作依赖项注入的规范用例。特别是在与log4net相连的地方,这几乎是一种反模式。撇开,kickass解释!
卡斯珀·莱昂·尼尔森

4
@ Casper Leon Nielsen-DIP与DI无关。它们既不是同义词,也不是等效概念。
TSmith

4
@ VF1如摘要段落所述,依赖反转原则的重要性主要在于重用。如果John释放了一个对日志记录库具有外部依赖关系的库,而Sam希望使用John的库,则Sam会承担瞬态日志记录依赖关系。如果没有选择John的日志库,Sam将永远无法部署他的应用程序。但是,如果John遵循DIP,Sam可以自由提供适配器并使用他选择的任何日志记录库。DIP与便利无关,而是耦合。
Derek Greer

13

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

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

依赖倒置原则指出:

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

该原理试图“反转”软件中高级模块应依赖于较低级模块的传统观念。在这里,高级模块拥有由低级模块实现的抽象(例如,确定接口的方法)。因此,使较低级别的模块依赖于较高级别的模块。


11

良好应用的依赖关系反转可在应用程序整个体系结构级别提供灵活性和稳定性。这将使您的应用程序更安全,稳定地发展。

传统的分层架构

传统上,分层体系结构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

深化原则

重要的是要很好地吸收概念,加深目的和好处。如果我们坚持不懈地学习典型的案例库,我们将无法确定在哪里可以应用依赖原理。

但是为什么我们要反转依赖关系呢?除了特定示例,主要目标是什么?

这样通常可以使不依赖于不稳定程度较低的最稳定事物更频繁地更改。

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

但是,不仅有这个存储库示例。在许多情况下都可以应用此原理,并且有基于此原理的体系结构。

建筑学

在某些体系结构中,依赖关系反转是其定义的关键。在所有域中,它是最重要的,它是抽象,它指示域和其余包或库之间的通信协议已定义。

清洁建筑

在“ 干净的体系结构”中,域位于中心,如果您从指示依赖性的箭头方向看,很明显,最重要和最稳定的层是什么。外层被认为是不稳定的工具,因此请避免依赖它们。


(来源:8thlight.com

六角建筑

对于六角形架构,它的发生方式相同,该域也位于中央部分,端口是从多米诺骨牌向外通信的抽象。再次显然,该域是最稳定的,并且传统的依赖关系被倒置了。


我不确定这是什么意思,但是这句话是不连贯的:“但是根据我们采用的方法,我们可能会误用投资依赖的依赖,而是抽象的。” 也许您想修复?
Mark Amery

9

对我来说,依赖性反转原则,如官方文章中所述确实是一种错误的尝试,它试图提高本质上不可重用的模块的可重用性,并且是解决C ++语言问题的一种方法。

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

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

因此,只有在组件A对于在不同应用程序或上下文中重用确实有用的情况下,才能在依赖于组件B(不依赖于A)的组件A的同一级别上创建单独的抽象。如果不是这种情况,那么应用DIP将是错误的设计。


即使高级抽象仅在该应用程序的上下文中有用,当您想要用存根替换实际的实现以进行测试(或为通常在网络上可用的应用程序提供命令行界面)时,它也可能具有价值
Przemek Pokrywka

3
DIP在所有语言中都很重要,与C ++本身无关。即使您的高级代码永远不会离开您的应用程序,DIP也允许您在添加或更改低级代码时包含代码更改。这样既降低了维护成本,又降低了更改带来的意外后果。DIP是一个更高层次的概念。如果您不了解,则需要进行更多谷歌搜索。
德克·贝斯特

3
我认为您误解了我对C ++所说的话。这只是DIP 的动机。毫无疑问,它比这更笼统。请注意,有关DIP的官方文章清楚地表明了主要动机是通过使高级模块不受底层模块的更改的影响来支持重用高级模块。无需重复使用,那么这很可能会造成过度杀伤和过度设计。(您读过它吗?它还讨论了C ++问题。)
Rogério2013年

1
您不是忽略了DIP的反向应用吗?也就是说,更高级别的模块本质上实现了您的应用程序。您主要关心的不是重用它,而是减少对低级模块的实现的依赖,以便对其进行更新以适应未来的变化,从而使您的应用程序免受时间和新技术的破坏。并不是要更轻松地替换WindowsOS,而是要使WindowsOS减少对FAT / HDD实施细节的依赖,以便可以将更新的NTFS / SSD插入WindowsOS,而几乎没有影响。
kickNrod '16

1
UI屏幕绝对不是最高级别的模块,也不应该用于任何应用程序。最高级别的模块是包含业务规则的模块。最高级别的模块也不由其可重用性的潜力来定义。请检查这篇文章中Bob叔叔的简单解释:blog.cleancoder.com/uncle-bob/2016/01/04/…–
humbaba

8

基本上说:

类应取决于抽象(例如,接口,抽象类),而不取决于特定的细节(实现)。


可以这么简单吗?正如DerekGreer提到的,有很长的文章甚至书籍?我实际上是在寻找简单的答案,但如果这么简单,它就令人难以置信:D
Darius.V

1
@ darius-v并不是说“使用抽象”的另一个版本。关于谁拥有界面。委托人说,客户端(较高级别的组件)应该定义接口,而较低级别的组件应该实现它们。
boran

6

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

DIP的原因重要的是,因为它保证OO原则“松耦合的设计”。

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

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


6
对于Java或.NET代码,DIP如何做到这一点?在那些语言中,与C ++相反,对低级模块的实现的更改不需要在使用该低级模块的高级模块中进行更改。只有公共接口中的更改会不断出现,但是更高级别上定义的抽象也必须更改。
罗杰里奥2009年

5

陈述依赖倒置原则的更清晰方法是:

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

即,与其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
    }
}

Data并且DataFromDependency应与一起位于同一模块中Logic,而不是Dependency

为什么这样

  1. 现在,两个业务逻辑模块已解耦。当Dependency修改,你不需要改变Logic
  2. 理解Logic操作是一个简单得多的任务:它仅对看起来像ADT的对象起作用。
  3. Logic现在可以更容易地进行测试。您现在可以直接实例化Data虚假数据并将其传递。无需模拟或复杂的测试支架。

我认为这是不对的。如果DataFromDependency直接引用Dependency的与处于同一模块中Logic,则在编译时Logic模块仍直接依赖于Dependency模块。按照Bob叔叔对原理的解释,避免这就是DIP的重点。而是,要遵循DIP,Data应与放在同一模块中Logic,但DataFromDependency应与在同一模块中Dependency
Mark Amery

3

控制反转(IoC)是一种设计模式,在这种模式下,对象由外部框架处理其依赖关系,而不是向框架询问其依赖关系。

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

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

使用IoC的类似代码:

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

IoC的好处是:

  • 您无需依赖中央框架,因此可以根据需要进行更改。
  • 由于对象是通过注入(最好使用接口)创建的,因此很容易创建将依赖项替换为模拟版本的单元测试。
  • 解耦代码。

依赖倒置原理和控制倒置是同一件事吗?
彼得·莫滕森

3
的“反转”在DIP是一样控制反转。第一个关于编译时依赖性,而第二个关于运行时方法之间的控制流。
罗杰里奥(Rogério)2010年

我觉得IoC版本缺少某些内容。它是如何定义什么数据库对象的,它来自哪里。我试图了解这不仅会延迟在一个巨大的神依赖中指定所有依赖关系到最高级别的原因
Richard Tingle 2015年

1
正如@Rogério所说,DIP不是DI / IoC。IMO这个答案是错误的
zeraDev

1

依赖性反转的重点是使软件可重用。

这个想法是,它们依赖于一些抽象的接口,而不是两个相互依赖的代码。然后,您可以重用任何一块,而无需另一块。

最常见的实现方法是通过控制反转(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的依赖关系,这些依赖关系的依赖关系,以及……嗯,您就明白了。


2
这个答案确实与DIP无关,而是其他。两段代码可以依靠某个抽象的接口,而不能彼此依赖,并且仍然不遵循依赖倒置原则。
罗杰里奥2009年

1

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

如果高层管理人员有一个“改善交货时间”的计划,并指出船运公司的员工每天早上必须喝咖啡并且伸懒腰,那么该计划是高度耦合的,而且凝聚力很低。但是,如果该计划没有提及任何特定员工,而实际上仅要求“一个可以执行工作的实体准备工作”,那么该计划将变得松散耦合且更具凝聚力:这些计划不会重叠,并且很容易被替换。承包商或机器人可以轻松替换员工,高层的计划保持不变。

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


1
这是一个非常好的类比。在DIC中,高级组件在运行时仍然依赖低级组件,在这种情况下,高级计划的执行取决于低级计划。而且,就像在DIC中一样,低级计划在编译时依赖高级计划,在您的类比中,低级计划的形成取决于高级计划。
Mark Amery

0

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

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

如何实现:通过抽象

没有依赖反转:

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


-1

假设我们有两个类:EngineerProgrammer

类工程师依赖于Programmer类,如下所示:

class Engineer () {

    fun startWork(programmer: Programmer){
        programmer.work()
    }
}

class Programmer {

    fun work(){
        //TODO Do some work here!
    }
}

在这个例子中,类Engineer依赖于我们的Programmer类。如果我需要更改该Programmer怎么办?

显然,我也需要更改Engineer。(哇,这时OCP也违反了)

然后,我们该如何清理这个烂摊子?答案实际上是抽象。通过抽象,我们可以删除这两个类之间的依赖关系。例如,我可以Interface为Programmer类创建一个,从现在开始,每个想要使用的类Programmer都必须使用其Interface,然后通过更改Programmer类,我们不需要更改任何使用它的类,因为我们抽象用过的。

注:DependencyInjection可以帮助我们做的DIPSRP太。


-1

除了一连串的好答案之外,我还想添加一个自己的小样本,以展示良好与不良做法。是的,我不是丢石头的人!

假设您想要一个小程序通过控制台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永远不会打扰它。


我不认为这是依赖倒置。毕竟,您在这里没有反转任何依赖关系。您的读者和作家不依赖GoodEncoder您的第二个示例。要创建DIP示例,您需要引入一个概念,该概念是“拥有”您在此处提取的接口-尤其是将其与GoodEncoder放在同一包中,而将其实现保留在外部。
Mark Amery

-2

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

注意:类应取决于接口或抽象类之类的抽象,而不取决于特定的细节(接口的实现)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.