我们需要依赖注入的接口吗?
Posted
技术标签:
【中文标题】我们需要依赖注入的接口吗?【英文标题】:Do we need interfaces for dependency injection? 【发布时间】:2017-03-28 20:25:25 【问题描述】:我有一个 ASP.NET Core 应用程序。该应用程序有几个帮助类可以做一些工作。每个类都有不同的签名方法。我在网上看到很多 .net 核心示例,它们为每个类创建接口,然后使用 DI 框架注册类型。例如
public interface IStorage
Task Download(string file);
public class Storage
public Task Download(string file)
public interface IOcr
Task Process();
public class Ocr:IOcr
public Task Process()
基本上每个接口只有一个类。然后我用 DI 将这些类型注册为
services.AddScoped<IStorage, Storage>();
services.AddScoped<IOcr,Ocr>();
但是我可以在没有接口的情况下注册类型,所以这里的接口看起来是多余的。例如
services.AddScoped<Storage>();
services.AddScoped<Ocr>();
那么我真的需要接口吗?
【问题讨论】:
临时锁,让人们在cmets冷静下来。 【参考方案1】:不,您不需要 接口进行依赖注入。但是依赖注入对它们更有用!
正如您所注意到的,您可以使用服务集合注册具体类型,ASP.NET Core 会将它们注入您的类中而不会出现问题。通过注入它们而不是简单地使用 new Storage()
创建实例所获得的好处是 service lifetime management(瞬态 vs. 作用域 vs. 单例)。
这很有用,但只是使用 DI 的一部分功能。正如@DavidG 所指出的,接口经常与 DI 配对的主要原因是测试。让你的消费者类依赖于接口(抽象)而不是其他具体的类,这使得它们更容易测试。
例如,您可以创建一个实现IStorage
的MockStorage
以在测试期间使用,而您的消费者类应该无法分辨出其中的区别。或者,您可以使用模拟框架轻松地动态创建模拟的IStorage
。用具体的类做同样的事情要困难得多。接口可以很容易地替换实现而不改变抽象。
【讨论】:
很好的答案。除了测试接口的好处之外,我想补充一点,接口还允许拦截依赖项,例如使用装饰器设计模式。例如,可以将其他行为添加到IStorage
,而无需更改Storage
实现本身。例如,您可能希望记录每个下载的文件及其大小,或阻止某些用户下载某些文件。【参考方案2】:
有效吗?是的。你应该这样做吗?没有。
依赖注入是依赖倒置原理的工具:https://en.wikipedia.org/wiki/Dependency_inversion_principle
或者正如SOLID中描述的那样
一个人应该“依赖于抽象,[而不是]具体化。”
您可以在各处注入具体的类,它就可以工作。但这不是 DI 旨在实现的目标。
【讨论】:
但是,如果您使用的是其他人创建的某些“组件”(没有接口),您可能别无选择,对吧? 在这种情况下,您可以为该组件创建一个接口。应该是一个 ctrl + 。离开 依赖注入不是依赖倒置的工具。这两个概念经常齐头并进(这是正确的),但你可以在没有另一个的情况下完成任何一个。【参考方案3】:我不会试图涵盖其他人已经提到的内容,使用带有 DI 的接口通常是最好的选择。但值得一提的是,有时使用对象继承可能会提供另一个有用的选项。比如:
public class Storage
public virtual Task Download(string file)
public class DiskStorage: Storage
public override Task Download(string file)
并像这样注册它:
services.AddScoped<Storage, DiskStorage>();
【讨论】:
但它只对抽象类有用,对吧?您的 Storage 类没有用abstract
关键字修饰。它包含虚拟方法 - 虚拟方法必须具有默认实现,而不是抽象方法。
不,好问题。您可以将 Storage 设为 Abstract 类,在这种情况下,您无需在该类中提供 Download
的实现。但是 Storage 可以是一个常规的 ol' 类,如我所示,它可以使用 virtual for 方法,可以被覆盖,如图所示。将方法声明为 virtual 向编译器表明允许派生类提供它自己的方法实现。您可以在这里了解更多信息:docs.microsoft.com/en-us/dotnet/csharp/language-reference/…【参考方案4】:
不,我们不需要接口。除了注入类或接口之外,您还可以注入委托。这相当于用一种方法注入接口。
例子:
public delegate int DoMathFunction(int value1, int value2);
public class DependsOnMathFunction
private readonly DoMathFunction _doMath;
public DependsOnAFunction(DoMathFunction doMath)
_doMath = doMath;
public int DoSomethingWithNumbers(int number1, int number2)
return _doMath(number1, number2);
您可以在不声明委托的情况下执行此操作,只需注入 Func<Something, Whatever>
,这也可以。我倾向于代表,因为它更容易设置 DI。您可能有两个具有相同签名但服务于不相关目的的委托。
这样做的一个好处是它可以引导代码实现接口隔离。有人可能会想向接口(及其实现)添加方法,因为它已经被注入到某个地方,所以很方便。
意思是
接口和实现承担了他们可能不应该承担的责任,因为这对当时的某人来说很方便。 依赖于接口的类的职责也会增加,但由于其依赖项的数量没有增加,因此更难识别。 其他类最终取决于臃肿、隔离程度较低的接口。我看到过这样的情况,单个依赖项最终会发展成真正应该是两个或三个完全独立的类,所有这些都是因为添加到现有接口和类而不是注入新的东西很方便。这反过来又帮助一些类达到了 2,500 行的长度。
你不能阻止某人做他们不应该做的事。你不能阻止某人仅仅让一个班级依赖于 10 个不同的代表。但它可以设置一种模式,引导未来朝着正确的方向发展,并为不断增长的接口和类提供一些阻力。
(这并不意味着不使用接口。这意味着您有选择。)
【讨论】:
以上是关于我们需要依赖注入的接口吗?的主要内容,如果未能解决你的问题,请参考以下文章