【发布时间】:2011-05-06 03:40:18
【问题描述】:
我知道接口不能定义构造函数。强制所有实现接口的类在统一合同中接收它们的依赖项的最佳实践是什么。我知道整数可以通过属性将依赖项注入对象,但通过构造函数传递它们对我来说更有意义。那么如何DI呢?
【问题讨论】:
标签: c# interface dependency-injection constructor
我知道接口不能定义构造函数。强制所有实现接口的类在统一合同中接收它们的依赖项的最佳实践是什么。我知道整数可以通过属性将依赖项注入对象,但通过构造函数传递它们对我来说更有意义。那么如何DI呢?
【问题讨论】:
标签: c# interface dependency-injection constructor
一种选择是在接口上创建一个用于初始化的方法。此方法可以接受所有必需的依赖项。
类似:
void Configure(dependency1 value, etc.);
当然,有很多选项可以使用框架进行这种类型的初始化和 DI。不过有很多选项可供选择。
Scott Hanselman 有一个很好的列表here。
【讨论】:
你需要做的是让你的所有接口实现子类化一个类,构造函数采用需要注入的任何状态。由于子类需要执行基本调用,因此在它们的构造函数中,您的约束会自动保持。
起初这似乎是一个奇怪的模式,但我们一直在我们的企业解决方案中使用它,所以我保证它是理智的 :-)
【讨论】:
我知道你说过你想要一份稳定的合同。但是不提供稳定接口的一个好处是,你的依赖可能会随着不同的实现而大不相同,这会减少耦合:
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();
}
各种框架也支持抽象工厂。
【讨论】:
我们都知道这可以通过许多不同的方法实现,但有意义的事情肯定更受欢迎。我定义了一些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,但对我来说,从现在开始。
【讨论】:
接口不负责依赖。只有实现知道,它需要什么来履行合同。
可能有一种实现使用数据库,另一种使用文件系统来持久化数据。
接口应该声明哪个依赖项是必需的?数据库管理器还是文件系统管理器?
【讨论】: