2

我相信我了解 DI / IoC 容器的基本概念,并使用它们编写了几个应用程序并阅读了很多堆栈溢出答案以及 Mark Seeman 的书。在某些情况下我仍然遇到了麻烦,尤其是在将 DI 容器集成到尚未真正使用 DI 原则的大型现有架构中时(想想大泥球)。

我知道理想的情况是每个操作都有一个组合根/对象图,但在遗留系统中,如果没有重大重构,这可能是不可能的(只有代码的新部分和一些选择重构的旧部分可能具有通过构造函数注入的依赖项和系统的其余部分使用容器作为服务定位器与新部件进行交互)。这实际上意味着一个操作深处的堆栈跟踪可能包括多个对象图,其中在新子系统(单个对象图直到退出旧段)和传统子系统(服务定位器调用在某个点以在DI 容器)。

有了(可能有问题,我可能想多了,或者完全错误地假设这种混合架构是一个好主意)假设,这是实际的问题:

假设我们有一个线程池执行在数据库(或任何外部位置)中定义的各种类型的预定作业。每种单独类型的计划作业都实现为继承公共基类的类。当作业启动时,它会收到有关应将其日志消息写入哪些目标以及应使用的配置的信息。配置可以通过将值作为方法参数传递给任何需要它们的类来处理,但是如果作业实现大于 10-20 个类,它似乎不太方便。

日志记录是更大的问题。作业调用的子系统可能还需要将内容写入日志,通常在示例中,这仅通过在构造函数中请求 ILog 实例来完成。但是在这种情况下,当我们直到运行时才知道细节/实现时,它是如何工作的呢?自从:

  • 由于调用链中的(非 DI 容器控制)遗留系统段(-> 可能存在多个单独的对象图),子容器不能用于注入特定子范围的自定义记录器
  • 手动属性注入基本上需要更新完整的调用链(包括所有遗留子系统)

帮助更好地理解问题的简化示例:

Class JobXImplementation : JobBase {
    // through constructor injection
    ILoggerFactory _loggerFactory;
    JobXExtraLogic _jobXExtras;

    public void Run(JobConfig configurationFromDatabase)
    {
        ILog log = _loggerFactory.Create(configurationFromDatabase.targets);
        // if there were no legacy parts in the call chain, I would register log as instance to a child container and Resolve next part of the call chain and everyone requesting ILog would get the correct logging targets
        // do stuff
        _jobXExtras.DoStuff(configurationFromDatabase, log);
    }
}

Class JobXExtraLogic {
    public void DoStuff(JobConfig configurationFromDatabase, ILog log) {
        // call to legacy sub-system
        var old = new OldClass(log, configurationFromDatabase.SomeRandomSetting);
        old.DoOldStuff();
    }
}

Class OldClass {
    public void DoOldStuff() {
        // moar stuff 
        var old = new AnotherOldClass();
        old.DoMoreOldStuff();
    }
}

Class AnotherOldClass {
    public void DoMoreOldStuff() {
        // call to a new subsystem 
        var newSystemEntryPoint = DIContainerAsServiceLocator.Resolve<INewSubsystemEntryPoint>();
        newSystemEntryPoint.DoNewStuff();
    }
}

Class NewSubsystemEntryPoint : INewSubsystemEntryPoint {
    public void DoNewStuff() {
        // want to log something...
    }
}

我相信你现在已经明白了。

通过 DI 实例化旧类是不可能的,因为它们中的许多使用(通常是多个)构造函数来注入值而不是依赖项,并且必须一个一个地重构。调用者基本上隐式地控制对象的生命周期,这是在实现中假设的(它们处理内部对象状态的方式)。

我有哪些选择?在这种情况下,您可能会看到哪些其他类型的问题?在这种环境中尝试仅使用构造函数注入是否可行?

4

1 回答 1

1

好问题。一般来说,我会说当只有一部分代码对 DI 友好时,IoC 容器会失去很多有效性。

有效地使用遗留代码.NET 中的依赖注入这样的书都谈到了梳理对象和类以使 DI 在您描述的代码库中可行的方法。

让系统接受测试将是我的首要任务。我会选择一个功能区域作为开始,一个对其他功能区域几乎没有依赖的功能区域。

我认为从构造函数注入转移到有意义的 setter 注入没有问题,它可能为您提供构造函数注入的垫脚石。添加属性通常比更改对象的构造函数更具侵入性。

于 2012-08-14T20:51:23.163 回答