DI容器和遗留系统中的自定义范围状态

时间:2012-08-14 20:40:03

标签: dependency-injection inversion-of-control ioc-container state legacy-code

我相信我理解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实例化旧类是不可能的,因为它们中的许多使用(通常是多个)构造函数来注入值而不是依赖项,并且必须逐个重构。调用者基本上隐式地控制对象的生命周期,这在实现中假定(它们处理内部对象状态的方式)。

我有什么选择?在这种情况下你可能会看到哪些其他类型的问题?试图在这种环境中只使用构造函数注入甚至可行吗?

1 个答案:

答案 0 :(得分:1)

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

Working Effectively with Legacy CodeDependency Injection in .NET之类的书籍都讨论了如何挑逗对象和类,以使DI在您所描述的代码库中可行。

让系统受到考验是我的首要任务。我会选择一个功能区域,一个很少依赖其他功能区域。

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