DI 组合根:它如何确保编译时分辨率检查

DI composition root: how does it ensure compile-time resolution checking

我读了几篇我的 Mark Seeman 关于依赖注入的文章,特别是避免服务定位器模式的原因:

服务定位器存在问题的基本思想是它可能在运行时失败:

public class OrderProcessor : IOrderProcessor
{
    public void Process(Order order)
    {
        var validator = Locator.Resolve<IOrderValidator>();
        if (validator.Validate(order))
        {
            var shipper = Locator.Resolve<IOrderShipper>();
            shipper.Ship(order);
        }
    }
}

var orderProcessor = new OrderProcessor();

// following line fails at compile time if you 
// forget to register all necessary services - and
// you don't have a way of knowing which services it needs

orderProcessor.Process(someOrder);

但这意味着 Composition Root 不仅必须在启动时解析 all 依赖关系,而且实际上实例化整个对象图,否则我们仍然无法知道所有已注册必要的依赖项:

private static void Main(string[] args)
{
    var container = new WindsorContainer();
    container.Kernel.Resolver.AddSubResolver(
        new CollectionResolver(container.Kernel));

    // Register
    container.Register(
        Component.For<IParser>()
            .ImplementedBy<WineInformationParser>(),
        Component.For<IParser>()
            .ImplementedBy<HelpParser>(),
        Component.For<IParseService>()
            .ImplementedBy<CoalescingParserSelector>(),
        Component.For<IWineRepository>()
            .ImplementedBy<SqlWineRepository>(),
        Component.For<IMessageWriter>()
            .ImplementedBy<ConsoleMessageWriter>());

    // Everything must be resolved AND instantiated here
    var ps = container.Resolve<IParseService>();
    ps.Parse(args).CreateCommand().Execute();

    // Release
    container.Release(ps);
    container.Dispose();
}

这在实际应用中的可行性如何?这是否真的意味着您不应该在构造函数之外的任何地方实例化任何东西?

(附加信息)

假设您有一项服务应该处理来自某种类型的多个测量设备(不同的连接类型、协议或同一协议的不同版本)的传入连接。每当您获得新连接时,服务都应该从输入端口构造一个 "pipeline",通过 fifo 缓冲区,到许多特定于该设备类型的解析器,以多个消费者结束各种解析消息。

提前组合这些对象图在应用程序启动时似乎是不可能的。即使可以延迟,我仍然看不出如何早期(-er)指示对象图构造将失败

这似乎是 main problem with service locators,我不知道如何避免它:

In short, the problem with Service Locator is that it hides a class' dependencies, causing run-time errors instead of compile-time errors, as well as making the code more difficult to maintain because it becomes unclear when you would be introducing a breaking change.

But this means that the Composition Root must not only resolve all dependencies at startup, but actually instantiate the entire object graph

如果您应用 Pure DI(即应用依赖注入模式,但没有 DI 容器),您将获得开箱即用的编译时支持。对于 DI 容器,您必须在运行时进行这些检查,但这并不意味着您必须在启动期间进行这些检查,尽管我认为这是首选。因此,如果在启动时检查容器的配置不会导致性能问题,那么您应该这样做。否则,您可以将此验证步骤移至单元测试。

How feasible is this in a real world application?

这是完全可行的。我构建的大型应用程序通常在容器中注册了数百到一千多个服务,并且我始终验证(和 diagnose)我的容器配置,这可以防止许多常见的配置错误,这些错误很容易犯,而且很难追踪.

Does this really mean you are not supposed to instantiate anything anywhere outside the constructor?

负责创建服务的是您的组合根;这本身并不意味着所有服务都应在启动期间创建,因为您可以将部分对象图的创建延迟到运行时。然而,我更喜欢的工作方式是使所有注册服务成为单例(一个实例用于应用程序期间)。这使得在应用程序启动期间创建所有服务变得非常容易(并且便宜),并迫使您进入更严格的模型,在该模型中 SOLID 违规和其他 DI 不良做法会更快弹出。