依赖注入(DI)是否依赖于接口?

3
这对大多数人来说可能很明显,但我只是想确认依赖注入(DI)依赖于使用接口。
更具体地说,在构造函数中将某个接口用作参数或在属性中定义某个接口(也称为Setter)的情况下,DI框架可以提供一个具体类的实例来满足该类中该接口的需求。 (如果这种描述不清楚,很抱歉。因为术语/概念对我来说仍然有些陌生,所以我有些困难来描述这一点)
我询问的原因是我目前有一个类有某种依赖关系。不是对象依赖,而是URL。该类如下[C#]:
using System.Web.Services.Protocols;
public partial class SomeLibraryService : SoapHttpClientProtocol 
{
        public SomeLibraryService() 
        {
            this.Url = "http://MyDomainName.com:8080/library-service/jse";
        }
}

SoapHttpClientProtocol类有一个名为Url的公共属性(它是一个普通的“字符串”),这里的构造函数将其初始化为硬编码值。
我是否可以使用DI框架在构建时注入不同的值?我认为不行,因为this.Url不是任何类型的接口;它是一个字符串。
顺便说一下,上面的代码是根据我正在使用的代码中的注释“由wsdl自动生成的”。因此,我不想特别更改此代码,尽管我也不打算重新生成它。所以也许更改此代码是可以的。
我可以看到自己制作一个接受字符串参数并以此方式初始化this.Url的替代构造函数,但我不确定这是否是保持松散耦合关注点分离(SoC)的正确方法。
对于这种情况有什么建议吗?
4个回答

8

DI实际上意味着一个类不会构造它的外部依赖项,也不会管理这些依赖项的生命周期。依赖项可以通过构造函数或方法参数进行注入。接口或抽象类型通常用于澄清消费者对其依赖性所期望的契约,但在某些情况下也可以注入简单类型。

例如,库中的一个类可能会在内部调用HttpContext.Current,这会对代码将托管在其中的应用程序作出任意假设。DI版本的库方法将通过参数注入HttpContext实例等。


5

没有必要使用接口 -- 你可以使用具体类型或抽象基类。但是,许多 DI 的优点(例如能够更改依赖项的实现)是在使用接口时才出现的。

Castle Windsor(我最熟悉的 DI 框架)允许将对象映射到 IoC 容器中的接口或名称,这在你的情况下都可以工作。


我会给你打上绿色勾号,但David和Peter的回答也很好。感谢你澄清接口不是必需的。 - Pretzel
“使用接口时可能会出现”这种说法是不正确或者误导性的。我认为要么删除它,要么解释一下如何使用抽象基类来防止更改依赖项的实现。 - Luke Puplett
@LukePuplett:如果您使用模拟框架来构建单元测试对象(这是依赖注入的核心理由之一),则需要使用接口。 - James Curran
@JamesCurran 不,就像手动子类化实现多态性一样,模拟也在同样的约束条件下工作;成员必须是抽象或虚拟的。正如David所说,DI是向要使用的类或系统提供依赖项的行为,即使是手动进行,而不需要像Unity这样的自动DI容器。关于你的问题,我不使用模拟库。我编写非常好的伪造对象,并且只测试公共API的行为,这使我可以经常轻松地进行重构。模拟会混淆测试并将测试与实现耦合在一起。必须模拟自己的代码是设计缺陷的标志。 - Luke Puplett

3
依赖注入是一种组织代码的方式。也许您的困惑来自于没有一个官方的方法来实现它。它可以通过使用“常规”的c#代码或使用类似Castle Windsor的框架来实现。有时(经常?)这涉及使用接口。无论如何实现,DI的大局目标通常是使您的代码更易于测试和以后更易于修改。
如果您通过构造函数注入示例中的URL,则可以将其视为“手动”DI。维基百科上的DI 文章 有更多关于手动与框架DI的示例。

是的,我同意。一些混淆来自于没有标准化的做法。DI通过《Pro ASP.net MVC Framework》(由Sanderson编写)向我展示,并且每个示例都使用接口,因此我的结论是它只适用于接口,但从逻辑上讲,在我的脑海中并不合理,所以我不得不询问。Sanderson在他的第一本书中谈到了Castle Windsor,但现在在他的第二版中使用Ninject(刚刚出版)。是的,我开始意识到DI对单元测试有很大帮助。 - Pretzel

0
我想重点讨论在.NET应用程序中使用接口。在.NET中,多态性可以通过虚拟或抽象方法或接口来实现。
在所有情况下,都有一个没有任何实现或可以被覆盖的实现的方法签名。
函数(甚至属性)的“合同”是定义的,但方法的逻辑实现可以在运行时由实例化和传入方法或构造函数的子类或设置在属性上(“注入”的行为)来确定。
官方的.NET类型设计指南提倡使用抽象基类而不是接口,因为它们在发货后具有更好的演变选项,可以包括方便的重载,并且能够更好地自我记录和向实现者传达正确的使用方法。
然而,必须注意不要添加任何逻辑。过去的诱惑已经让人们付出了代价,所以许多人使用接口-许多其他人使用接口只是因为周围的程序员这样做。
值得一提的是,虽然DI本身很少被过度使用,但使用框架执行注入通常会被过度使用,从而增加了复杂性,可能会发生连锁反应,即使它们从未被“切换”,容器中仍需要越来越多的类型。

IoC框架应该谨慎使用,通常只在需要在运行时根据环境或配置替换对象时使用。这通常意味着在应用程序中切换主要组件“接缝”,例如用于抽象数据层的存储库对象。

对我来说,IoC框架的真正力量在于在您无法控制创建的地方切换实现。例如,在ASP.NET MVC中,控制器类的创建由ASP.NET框架执行,因此注入任何内容都是不可能的。 ASP.NET框架具有一些钩子,IoC框架可以使用这些钩子“介入”创建过程并执行其魔术。

Luke


请参阅此文章中的二进制破坏更改:https://learn.microsoft.com/en-us/dotnet/standard/library-guidance/breaking-changes,该文章建议使用抽象基类而不是接口。 - Luke Puplett

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接