如何将依赖项注入实现接口的类中?

Posted

技术标签:

【中文标题】如何将依赖项注入实现接口的类中?【英文标题】:How to inject dependencies into classes that implement an interface? 【发布时间】:2011-05-06 03:40:18 【问题描述】:

我知道接口不能定义构造函数。强制所有实现接口的类在统一合同中接收它们的依赖项的最佳实践是什么。我知道整数可以通过属性将依赖项注入对象,但通过构造函数传递它们对我来说更有意义。那么如何DI呢?

【问题讨论】:

【参考方案1】:

一种选择是在接口上创建一个用于初始化的方法。此方法可以接受所有必需的依赖项。

类似:

void Configure(dependency1 value, etc.);

当然,有很多选项可以使用框架进行这种类型的初始化和 DI。不过有很多选项可供选择。

Scott Hanselman 有一个很好的列表here。

【讨论】:

【参考方案2】:

你需要做的是让你的所有接口实现子类化一个类,构造函数采用需要注入的任何状态。由于子类需要执行基本调用,因此在它们的构造函数中,您的约束会自动保持。

起初这似乎是一个奇怪的模式,但我们一直在我们的企业解决方案中使用它,所以我保证它是理智的 :-)

【讨论】:

【参考方案3】:

我知道你说过你想要一份稳定的合同。但是提供稳定接口的一个好处是,你的依赖可能会随着不同的实现而大不相同,这会减少耦合:

public interface IBlogRepository

    IEnumerable<Entry> GetEntries(int pageId, int pageCount);


class BlogDatabase : IBlogRepository

    public BlogDatabase(ISession session)
    
        this.session = session;
    

    public IEnumerable<Entry> GetEntries(int pageId, int pageCount)
    
        // Not that you should implement your queries this way...
        var query = session.CreateQuery("from BlogEntry");
        return query.Skip(pageId * pageCount).Take(pageCount);
    

    private ISession session;

正如您所说,您还可以将依赖项实现为属性(或参数),但这将硬编码您的依赖项,而不是使它们特定于实现。您将解耦您的特定会话实现,但您仍然必须依赖会话。

public interface IBlogRepository

    ISession Session  get; set; 
    IEnumerable<Entry> GetEntries(int pageId, int pageCount);
    IEnumerable<Entry> GetEntriesWithSession(ISession session,
        int pageId, int pageCount);


class BlogDatabase : IBlogRepository

    public ISession Session  Get; set; 

    public IEnumerable<Entry> GetEntries(int pageId, int pageCount)
    
        var query = Session.CreateQuery ...
    

    public IEnumerable<Entry> GetEntries(ISession session, int pageId, int pageCount)
    
        var query = session.CreateQuery ...
    


class BlogFile : IBlogRepository

    // ISession has to abstract a file handle.  We're still okay
    // ...


class BlogInMemory : IBlogRepository

    // ISession abstracts nothing.
    // Maybe a lock, at best, but the abstraction is still breaking down
    // ...

只有在您使用某种可以为您处理构建/提供依赖项的依赖注入框架时,构造函数注入才会起作用。即使没有框架,属性和参数注入也可以工作。

我相信这三个都是公认的做法。至少有几个流行的框架同时支持构造函数和属性注入。

这意味着由您决定什么对您的项目最有意义。权衡是一个易于跟踪的依赖图,而不是更强的耦合。决定当然也不必是全构造函数或全属性/参数。

另一个需要考虑的更高层次的抽象是抽象工厂类。如果你想对一组依赖进行分组,或者你需要在运行时构造它们的实例,你会这样做:

public interface IInstallationFactory

    IUser CreateRegisteredUser(Guid userSid);
    IPackage CreateKnownPackage(Guid id);
    IInstaller CreateInstaller();

各种框架也支持抽象工厂。

【讨论】:

这正是我所说的,但解释得更好。 +1 并删除我自己的。【参考方案4】:

我们都知道这可以通过许多不同的方法实现,但有意义的事情肯定更受欢迎。我定义了一些set-only 属性,然后该对象负责持有对传递给它的内容的引用:

public interface IBlogRepository

    ISession Session  set; 


class BlogRepository : IBlogRepository

   private ISession m_session;

   ISession Session
   
      set  m_session = value; 
   

这样每个实现接口的类都知道set-only 属性是依赖注入,因为set-only 属性很少使用。我不确定这个方法是否被称为good practice,但对我来说,从现在开始。

【讨论】:

固执地只使用一种定义依赖项的方法就是抛弃设计选择。其他的选择也不错,只是不同而已。 使用这种设计,您需要派生类来定义属性,但您不需要类的用户填写依赖项。如果用户实例化了你的类,没有填写这些属性,并调用了一个方法,你就不能抛出。这样做会违反接口定义的隐式契约。 要求填充依赖项的唯一两种方法是构造函数和方法参数。【参考方案5】:

接口不负责依赖。只有实现知道,它需要什么来履行合同。

可能有一种实现使用数据库,另一种使用文件系统来持久化数据。

接口应该声明哪个依赖项是必需的?数据库管理器还是文件系统管理器?

【讨论】:

以上是关于如何将依赖项注入实现接口的类中?的主要内容,如果未能解决你的问题,请参考以下文章

我应该如何将依赖项传递给我的类?

Angular 2 - 如何将依赖项注入到自定义类中,该类不是组件并且不作为服务公开

Spring 依赖注入的理解

运用Unity实现依赖注入[结合简单三层实例]

spring 依赖注入在使用 @WebListener 注释的类中不起作用

PHP中的服务容器与依赖注入的思想