在开始使用 DI 时,这确实是一件很难理解的事情,而且也不容易解释。
您认为创建稍后将通过方法初始化的“空白”对象可能是次优解决方案的想法是正确的 - 对象应该能够随时完成其工作;Initialize()
方法就是 Mark Seemann 在他的 .NET 中的依赖注入一书中所说的“时间耦合” 。这是一种反模式,它使使用对象的代码依赖于该对象的内部工作,从而破坏了封装。
问题是什么时候需要的信息可用,“负责初始化它的代码”是什么,它从哪里获取信息 - 以及它如何访问对象来初始化它。理想情况下,这个初始化代码本身会被注入到您的对象中,并且每当您的对象的方法/属性被访问时,它都会从其他依赖项中请求初始化。
另外,如果IsInitialized
标志返回 false 会发生什么?那仍然是有效的程序状态吗?
一般来说,作为依赖注入对象图中的对象,我应该知道我在创建时的所有“配置”数据,或者知道可以将其提供给我的人(某人是作为依赖项注入的另一个对象)。
如果您可以提供有关对象需要什么样的参数以及它们需要来自何处的更多详细信息,这可能会有所帮助。
编辑
您在评论中所描述的几乎正是我第一次遇到此类问题;我当时在SO上的某个地方发布了一个问题。
重要的是构建单独的类(通常,可能会有例外,但那些是经验问题),您假设该类需要的所有内容都存在。当程序运行时,需要有其他类来确保假设不会失败。
Setter 注入是我通常尽量避免避免所说的时间耦合的东西。根据 Mark Seemann 的说法,setter 注入通常应该只在你已经有一个很好的默认值并且你只是通过 setter 覆盖时才使用。但是,在这种情况下,如果没有这种依赖关系,对象将无法正常运行。
这可能不是最优雅的方法(我通常可以在非常封闭的纯代码环境中应用 DI 而不必担心 UI),但它会工作(有点 - 它可以编译,但仍然是伪代码):
public class MainForm
{
private readonly IDataManager _dataManager;
private readonly IConnectionProvider _connectionProvider;
private readonly IConnectionReceiver _connectionReceiver;
public MainForm(IDataManager dataManager, IConnectionProvider connectionProvider, IConnectionReceiver connectionReceiver)
{
this._dataManager = dataManager;
this._connectionProvider = connectionProvider;
this._connectionReceiver = connectionReceiver;
}
public void btnConnect_Click()
{
IConnection connection = this._connectionProvider.GetConnection();
if (null != connection)
{
this._connectionReceiver.SetConnection(connection);
this.SetFormControlsEnabled(true);
}
}
private void SetFormControlsEnabled(bool doEnable)
{
}
}
public interface IConnectionProvider
{
IConnection GetConnection();
}
public interface IConnectionReceiver
{
void SetConnection(IConnection connection);
}
public interface IConnection
{
IConnectionWebService ConnectionWebService { get; }
}
public class ConnectionBridge : IConnection, IConnectionReceiver
{
private IConnection _connection;
#region IConnectionReceiver Members
public void SetConnection(IConnection connection)
{
this._connection = connection;
}
#endregion IConnectionReceiver Members
#region IConnection Members
public IConnectionWebService ConnectionWebService
{
get { return this._connection.ConnectionWebService; }
}
#endregion
}
public interface IConnectionWebService {}
public interface IDataManager { }
public class DataManager : IDataManager
{
public DataManager(IConnection connection)
{
}
}
所以,MainForm
是把这一切结合在一起的东西。它从禁用其控件开始,因为它知道它们需要一个工作IDataManager
并且(按照惯例)需要一个连接。单击“连接”按钮时,表单会询问其IConnectionProvider
对连接的依赖关系。它不关心连接来自哪里;连接提供者可能会显示另一种形式来询问凭据,或者可能只是从文件中读取它们。
然后表单知道必须将连接传递给IConnectionReceiver
实例,然后可以启用所有控件。这不是任何 DI 原则,这就是我们定义它的MainForm
方式。
另一方面,数据管理器从一开始就拥有它所需的一切——一个IConnection
实例。这不能做它最初应该做的事情,但是还有其他代码可以防止它引起问题。
ConnectionBridge
既是实际IConnection
实例的装饰器,也是将连接获取与连接消耗解耦的适配器。它通过使用接口隔离原则来做到这一点。
作为旁注,请注意,虽然依赖注入是一项重要的技术,但它只是编写所谓的“干净代码”应遵循的几个原则之一。最著名的是SOLID原则(DI 就是其中之一),但还有其他一些原则,例如命令-查询-分离 (CQS)、“不要重复自己”(DRY)和得墨忒耳定律。最重要的是,练习单元测试,准确地说是测试驱动开发 (TDD)。这些事情确实产生了巨大的影响——但如果你是自愿使用 DI,那么你已经有了一个良好的开端。