我是依赖注入的新手,并且已经在StackOverflow和其他地方做了一些阅读。在实践中,我无法正确使用它。
为了解释这个问题,这里是一个我不确定如何使用DI的基本情况: 假设我有一些将在几个不同的类中使用的对象。但是,为了使这个对象可用,它需要在启动时没有的某些参数。
我可以看到使用DI执行此操作的一种可想到的方法是创建此对象的空白实例,使用必要参数初始化它的方法,以及是否初始化它的标志。
对我而言,这感觉就像一个黑客,因为对象不应该存在,我只是传递一个容器,等待负责的代码初始化它。这是它的意图,还是我错过了这一点?
答案 0 :(得分:2)
当开始使用DI时,这确实是一个非常艰难的事情,这也是一个不容易解释的事情。
您的想法是创建一个稍后将通过方法初始化的“空白”对象可能是次优解决方案是正确的 - 对象应该能够随时进行其工作; Initialize()
方法是Mark Seemann在他的书“ .NET中的依赖注入”中称之为“时间耦合”的方法。这是一种反模式,它使得使用该对象的代码依赖于该对象的内部工作,从而打破了封装。
问题在于所需信息何时可用,“初始化它的负责代码”是什么,以及从何处获取信息 - 以及它如何访问对象以初始化它。理想情况下,这个初始化代码本身会被注入到您的对象中,每当访问对象的方法/属性时,它都会从该其他依赖项请求初始化。
此外,如果IsInitialized
标志返回false会发生什么?这仍然是一个有效的计划状态吗?
通常,作为依赖注入对象图中的对象,我应该知道创建时的所有“配置”数据,或者知道可以将其提供给我的人(有人是另一个作为依赖项注入的对象)。
如果你能提供一些关于对象需要什么样的参数以及它们需要来自哪里的更多细节,这可能会有所帮助。
修改强>
你在评论中描述的内容几乎就是我第一次遇到这类问题;那时我在某处发布了一个问题,我当时发布了这个问题。
重要的是建立单独的类(通常,可能存在异常,但这些是经验问题),以这种方式假设您需要的所有类都存在。当程序运行时,需要有其他类,以确保假设不会失败。
Setter注射是我通常不必避免所述时间耦合的东西;根据马克·西曼(Mark Seemann)的说法,通常只有当你已经有一个很好的默认值而你刚刚通过setter覆盖时才能使用setter注入。但是,在这种情况下,如果没有该依赖关系,该对象将无法正常运行。
这可能不是最优雅的方式(我通常可以在相当封闭的仅限代码的环境中应用DI,而不必担心用户界面),但它可以工作(有点 - 它编译,但仍然是伪代码):
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),“Don”重复自己“(干)和德米特法则。最重要的是,练习单元测试,精确测试驱动开发(TDD)。这些事情确实带来了巨大的变化 - 但如果你自己接受了DI,你已经有了一个良好的开端。
答案 1 :(得分:0)
我同意GCATNM所说的内容,我想补充一点,每当我觉得有这样的对象时,我就会使用其中一种工厂模式变体(无论是抽象工厂,静态工厂等等)和我会向工厂注入该对象的配置信息的来源。因此,Marc Seemann也说过,我没有引用:工厂是依赖注入的好伴侣,你偶尔会需要它们。