依赖注入的最佳实践

9
这是一个关于如何最好地进行DI(依赖注入)的问题,因此它不与任何特定的DI/IoC框架相关,因为嘛,框架应该基于模式和实践而选择,而不是相反,对吧?
我正在做一个项目,其中repository必须被注入到services中,一个service可能需要多个repositories,我很想知道以下方法之间的优缺点:
  1. Inject repositories in service constructor

    public class SomeService : ISomeService
    {
        private IRepository1 repository1;
        private IRepository2 repository2;
    
        public SomeService(IRepository1 repository1, IRepository2 repository2)
        {
              this.repository1 = repository1;
              this.repository2 = repository2;
        }
    
        public void DoThis()
        {
              //Do something with repository1
        }
        public void DoThat()
        {
              //Do something with both repository1 and repository2
        }
    }
    
  2. Inject a custom context class that include everything any service may need but lazy instantiated (the IServiceContext will be a protected field in BaseService)

    public class SomeService : BaseService, ISomeService
    {
        public SomeService(IServiceContext serviceContext)
        {
              this.serviceContext= serviceContext;
        }
    
        public void DoThis()
        {
              //Do something with serviceContext.repository1
        }
        public void DoThat()
        {
              //Do something with both serviceContext.repository1 and serviceContext.repository2
        }
    }
    
  3. Inject into methods that need them only

    public class SomeService : ISomeService
    {
        public void DoThis(IRepository1 repository1)
        {
              //Do something with repository1
        }
        public void DoThat(IRepository1 repository1, IRepository2 repository2)
        {
              //Do something with both repository1 and repository2
        }
    }
    

希望您能提供一些指导,此外在评估类似这样的备选方案时应考虑哪些方面?

3个回答

8
首选的依赖注入方式是构造函数注入方法注入不太理想,因为这会很快导致必须从一个服务传递许多依赖项到另一个服务,并且它将导致实现细节(依赖项)泄漏到API(您的方法)中。
选项1和2都采用了构造函数注入,这很好。如果你发现自己不得不在构造函数中注入太多的依赖项,那么就有些问题了。要么你违反了单一责任原则,要么你缺少某种聚合服务,这就是选项2所做的事情。
在您的情况下,您的IServiceContext 聚合服务将多个存储库组合在一起。一个类后面有多个存储库的情况听起来像是工作单元。只需向IServiceContext添加一个Commit方法,您就一定会拥有一个工作单元。想想看:您是否想将IUnitOfWork注入到您的服务中?

注意:我同意Phil的观点,你的“IServiceContext”绝对不应该包含“任何服务可能需要的一切”。请不要这样做。 - Steven

3
第一种选择从DI的角度来看似乎是最自然的。服务类需要两个仓储才能执行其功能,因此在构建实例时将它们设置为必需的在语义上(和实际上)是有意义的。
第二种选择听起来有点像服务定位器,这通常被认为是一种反模式(参见http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx)。简而言之,它创建了隐式依赖关系,而显式依赖关系总是首选。

第二个选项不是服务定位器,除非这个 IServiceContext 有某种 GetInstance<T>() 方法(这确实很糟糕),但从问题中看起来并没有。事实上,他的 IServiceContext 是一个聚合服务 - Steven
"注入一个自定义上下文类,其中包含任何服务可能需要的一切。" 他可能没有调用 container.GetInstance<T>(),但他所做的具有相同的效果(不显式地列出依赖项)。 - Phil Sandler
1
此外,依我之见,他没有提供足够的信息来明确表示他应该重构为聚合服务。 - Phil Sandler
好观点。我错过了那个。包括“任何服务可能需要的一切”确实是一件坏事,开始感觉像一个服务定位器。+1 - Steven

1

我会使用构造函数注入或属性注入。除非该上下文还有其他用途,否则我不会传递包含依赖项的上下文。

对于必需的依赖项,我更喜欢使用构造函数注入,因为如果缺少某些内容,对象创建就会变得非常容易失败。我从这里学到了这一点。如果您要验证是否满足依赖关系,则必须使用构造函数注入进行验证,因为无法确定哪个setter是最后一个被触发的。


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