9

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

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

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

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

为每个模块创建一个“抽象”是一种代码混乱,违反了 YAGNI 原则。

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

有人可以帮我解决这个问题吗?SOLID 是否意味着我必须注入每个模块并对其进行抽象?如果是的话,这不是很多我们根本不会在大多数时候使用的杂物吗?

4

1 回答 1

11

依赖倒置原则指出:

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

换句话说,所依赖的每个模块(即除了应用程序中的入口点模块之外的所有模块)都应该被抽象。否则高层模块将不得不直接依赖低层模块,导致违反 DIP。

抽象的实现数量与 DIP 无关,因为它的目标是使模块能够抵抗更改。如果没有抽象,就不可能在不更改或重新编译高级组件的情况下轻松更改实现或添加横切关注点。

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

只有一个给定接口的实现是一种代码异味。

然而,这并不是说您根本不应该定义接口,而是您需要仔细检查您的设计并找出与行为相关的类。这些相关的类通常可以放在同一个通用抽象(通用接口)后面,这不仅允许抽象重用,而且使应用横切关注点成为孩子们的游戏。

以下是一些功能建议,您可以将它们放在相同的通用抽象后面:

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

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

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

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

于 2015-02-28T08:42:31.927 回答