2

我是依赖注入的新手,并且在 StackOverflow 和其他地方都在阅读它。在实践中,我无法正确使用它。

为了解释这个问题,这里有一个我不确定如何使用 DI 的基本情况:假设我有一些对象将在几个不同的类中使用。但是,为了使该对象可用,它需要某些我在启动时没有的参数。

我可以看到使用 DI 执行此操作的一种可能的方法是创建此对象的空白实例、使用必要参数对其进行初始化的方法以及是否已初始化的标志。

对我来说,这感觉像是 hack,因为对象不应该真的存在,我只是传递一个容器,等待负责的代码初始化它。这是它的意思,还是我错过了重点?

4

2 回答 2

2

在开始使用 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,那么你已经有了一个良好的开端。

于 2013-03-14T18:48:22.427 回答
0

我同意 GCATNM 所说的,我想补充一点,每当我觉得有这样的对象时,我就会去使用工厂模式变体之一(无论是抽象工厂、静态工厂等),然后我会注入该对象的配置信息源的工厂。所以正如 Marc Seemann 所说,我没有引用:工厂是依赖注入的一个很好的伴侣,你偶尔会需要它们。

于 2013-03-14T23:04:37.727 回答