我相信我了解 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 实例化旧类是不可能的,因为它们中的许多使用(通常是多个)构造函数来注入值而不是依赖项,并且必须一个一个地重构。调用者基本上隐式地控制对象的生命周期,这是在实现中假设的(它们处理内部对象状态的方式)。
我有哪些选择?在这种情况下,您可能会看到哪些其他类型的问题?在这种环境中尝试仅使用构造函数注入是否可行?