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

8

我知道接口不能定义构造函数。如何最好地强制所有实现接口的类在统一协议下接收其依赖项。我知道可以通过属性将依赖项注入对象,但是通过构造函数传递它们对我来说更有意义。那么如何进行依赖注入呢?

5个回答

7

我知道你说过想要一份稳定的合同,但是不提供稳定接口的好处在于,你的依赖关系可能会随着不同实现而变化,这将降低耦合度:

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
这正是我想说的,但是解释得好多了。点赞并删除我的评论。 - Rob Fonseca-Ensor

3

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

类似于:

void Configure(dependency1 value, etc.);

当然,使用框架进行此类型的初始化和依赖注入有很多选择。但是有很多选项可供选择。

Scott Hanselman在这里列出了一个好的列表。


2
您需要做的是让所有接口实现都继承一个带有构造函数的类,该构造函数需要注入所需的状态。由于子类需要在其构造函数中执行基础调用,因此您的约束条件会自动得到维护。
起初,这可能看起来像一种奇怪的模式,但我们在企业解决方案中经常使用它,因此我保证它是合理的 :-)

0

我们都知道这可以通过许多不同的方法实现,但是有意义的东西肯定更受欢迎。我定义了一些仅设置属性,然后对象负责保存对传递给它的内容的引用:

public interface IBlogRepository
{
    ISession Session { set; }
}

class BlogRepository : IBlogRepository
{
   private ISession m_session;

   ISession Session
   {
      set { m_session = value; }
   }
}

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


僵守只有一种定义依赖的方式,是放弃设计选择。其他选择并不差,它们只是不同而已。 - Merlyn Morgan-Graham
使用这种设计,您需要派生类来定义属性,但不要求类的用户填写依赖项。如果用户实例化您的类,没有填写这些属性并调用方法,则无法抛出异常。这样做将违反接口定义的隐式契约。强制要求填写依赖项的唯一两种方法是构造函数和方法参数。 - Merlyn Morgan-Graham

0
界面不负责依赖关系。只有实现知道它需要满足的合同。
可能会有一个实现使用数据库,另一个使用文件系统来持久化数据。
界面应该声明哪个依赖关系是必需的?数据库管理器还是文件系统管理器?

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