Dependency injection 旧系统中的DI容器和自定义作用域状态

Dependency injection 旧系统中的DI容器和自定义作用域状态,dependency-injection,inversion-of-control,ioc-container,state,legacy-code,Dependency Injection,Inversion Of Control,Ioc Container,State,Legacy Code,我相信我已经理解了DI/IoC容器的基本概念,已经编写了几个使用它们的应用程序,并阅读了大量的堆栈溢出答案以及Mark Seeman的书。还有一些情况我遇到了麻烦,特别是当涉及到将DI容器集成到一个大型的现有体系结构时,在这个体系结构中,DI原则并没有真正被使用(想想这个大泥球) 我知道理想的情况是每个操作有一个单一的组合根/对象图,但在遗留系统中,如果不进行重大重构,这可能是不可能的(只有代码的新部分和一些经过选择重构的旧部分可以通过构造函数注入依赖项,系统的其余部分可以使用容器作为服务定位器

我相信我已经理解了DI/IoC容器的基本概念,已经编写了几个使用它们的应用程序,并阅读了大量的堆栈溢出答案以及Mark Seeman的书。还有一些情况我遇到了麻烦,特别是当涉及到将DI容器集成到一个大型的现有体系结构时,在这个体系结构中,DI原则并没有真正被使用(想想这个大泥球)

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

有了(潜在的错误,我可能想得太多了,或者假设这种混合体系结构是个好主意是完全错误的)假设,实际问题是:

假设我们有一个线程池,执行数据库(或任何外部位置)中定义的各种类型的计划作业。每种单独类型的计划作业都实现为继承公共基类的类。当作业启动时,它会收到有关应将日志消息写入哪些目标以及应使用的配置的信息。可能只需将值作为方法参数传递给任何cla即可处理配置ss需要它们,但如果作业实现超过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...
    }
}
Class JobXImplementation:JobBase{
//通过构造函数注入
iLogger工厂(iLogger工厂);;
JobXExtraLogic_jobXExtras;
public void运行(JobConfig configurationFromDatabase)
{
ILog log=\u loggerFactory.Create(configurationFromDatabase.targets);
//如果调用链中没有遗留部分,我会将日志注册为子容器的实例,并解析调用链的下一部分,每个请求ILog的人都会获得正确的日志目标
//做事
_DoStuff(数据库配置,日志);
}
}
类JobXExtraLogic{
public void DoStuff(JobConfig configurationFromDatabase,ILog log){
//调用遗留子系统
var old=新的OldClass(log,configurationFromDatabase.SomeRandomSetting);
老掉牙的东西;
}
}
类旧类{
公共空间{
//哀怨的东西
var old=新的另一个oldClass();
old.DoMoreOldStuff();
}
}
另一类{
public void DoMoreOldStuff(){
//调用一个新的子系统
var newsystemmentrypoint=DIContainerAsServiceLocator.Resolve();
newSystemEntryPoint.DoNewStuff();
}
}
类NewSubsystemPoint:InewSubsystemPoint{
公共无效DoNewStuff(){
//想要记录一些东西。。。
}
}
我相信你现在已经明白了

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


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

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

像和这样的书都讨论了如何区分对象和类,以使DI在您描述的代码库中可行

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


我不认为从构造函数注入转移到setter注入是有意义的,这可能会为构造函数注入提供一个垫脚石。添加属性通常比更改对象的构造函数更具侵略性。

关于日志记录,我的第一个想法是: