依赖倒置原则是否意味着我必须为每个模块创建一个接口?

mic*_*rus 5 architecture dependency-injection solid-principles dependency-inversion

如果我希望我的代码遵循SOLID原则,特别是依赖性反转原则,这是否意味着我必须为每个模块创建一个接口(抽象),即使它只有一个实现?

在我看来,并根据这些帖子:

http://josdejong.com/blog/2015/01/06/code-reuse/

http://blog.ploeh.dk/2010/12/02/Interfacesarenotabstractions/

为每个模块创建"抽象"是代码混乱并违反了YAGNI原则.

我的经验法则是:不要使用依赖注入,或者为模块创建接口,除非它有多个实现(第二个实现可以是用于数据库/服务器/文件模块的单元测试的模拟类).

有人可以为我清楚这一点吗?SOLID是否意味着我必须注入每个模块并对其进行抽象?如果是的话,是不是只是在很多时候我们根本不会使用它?

Ste*_*ven 8

依赖倒置原则指出:

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

换句话说,每个依赖的模块(除了应用程序中的入口点模块之外的所有内容)都应该被抽象出来.否则,高级模块将不得不直接依赖于低级模块,从而导致违反DIP.

抽象实现的数量与DIP无关,因为它的目标是使模块能够抵抗变化.如果没有抽象,就不可能轻易地更改实现或添加横切关注点而无需更改或重新编译高级组件.

但是,如果您发现自己只使用一个实现定义了许多抽象,那么您违反了重用抽象原则,正如Mark Seemann在您引用的文章中已经说过的那样:

只有一个给定接口的实现是代码气味.

但是,这意味着你不应该根本不定义接口,而是需要仔细研究你的设计并发现与行为相关的类.这些相关的类通常可以放在相同的通用抽象(通用接口)之后,这不仅允许抽象重用,而且还可以应用横切关注子项.

以下是一些可以放在同一个通用抽象背后的功能建议:

  • ICommandHandler <TCommand>用于代表用户更改系统的类(用例).
  • IQueryHandler <TQuery,TResult>作为查询数据库(或文件系统,Web服务,无论如何)和返回数据的类的抽象.
  • IValidator <T>用于将报告验证错误检回给用户的类
  • ISecurityValidator <T>用于验证是否允许用户执行某个操作的类.
  • IAuthorizationFilter <T>用于允许基于用户的权限和角色应用基于行的安全性的类.
  • IEventHandler <T>用于响应已发生的特定业务事件的类.

这些只是抽象的几个例子.它在很大程度上取决于应用程序和设计您将获得的通用抽象.

我编写的应用程序使用这些通用抽象,这些应用程序只有一个接口只有一个实现.系统中大约90%到98%的模块实现了这些通用抽象中的一种(取决于应用程序的大小;应用程序越大,百分比越高).

这些通用抽象使得在DI库中的一行代码中注册所有实现变得非常容易(或者至少,如果您使用的是.NET),但更重要的是,正如我之前所说的那样,应用横切关注变得非常简单.例如,无需对应用程序进行全面更改,您可以在数据库事务中运行用例,或应用死锁重试机制.或者,您可以应用查询缓存,而无需在整个应用程序中进行全面更改.