【问题标题】:DI composition root: how does it ensure compile-time resolution checkingDI 组合根:它如何确保编译时分辨率检查
【发布时间】:2015-10-27 09:58:17
【问题描述】:

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

Service Locator 存在问题的基本思想是它可能在运行时失败:

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 不仅必须在启动时解决 所有 依赖关系,还要实际实例化整个对象图,否则我们仍然不知道所有必要的依赖关系都已注册:

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();
}

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

(附加信息)

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

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

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

简而言之,Service Locator 的问题在于它隐藏了类的依赖关系,从而导致运行时错误而不是编译时错误,并且使代码更难维护,因为不清楚何时会出现引入重大更改。

【问题讨论】:

  • 这在现实世界的应用程序中有多可行? 相当可行blog.ploeh.dk/2011/03/04/Composeobjectgraphswithconfidence 我一直都在这样做;这不是一个抽象的练习,而是对我(和其他人)几十年来如何构建应用程序的描述。
  • Here is another reason 为什么服务定位器是反模式。它限制了对象的可组合性。
  • 我们也可以对服务定位器进行编译时或运行时分析。就确保配置服务而言,DI 容器和 SL 注册表之间没有太大区别。
  • @MarkSeemann:我猜一直在使用它:-)。我知道我必须改变我的心态才能了解如何使 DI 适用于我的应用程序类型,但这是我的观点:我不确定如何。例如,在接受来自数百个不同类型/协议的设备的连接的服务器应用程序中,我必须有一种方法来管理单个设备“管道”的生命周期,从传入端口,通过各种解析器(根据设备类型实例化/protocol),发送给最终消费者。这是应用程序对象图的一个相当“动态”的部分,我看不出如何“根”它?
  • 如果不能在启动时组成一个完整的Services对象图,那一定是因为需要一些运行时的值;至少,我想不出任何其他原因。运行时值是输入,您必须假设所有输入都是邪恶的。在邪恶的输入,事情会失败。如果网络出现故障,事情可能会失败。等等……

标签: c# design-patterns dependency-injection inversion-of-control service-locator


【解决方案1】:

但这意味着Composition Root不仅要在启动时解决所有依赖关系,还要实际实例化整个对象图

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

这在实际应用中的可行性如何?

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

这真的意味着你不应该在构造函数之外的任何地方实例化任何东西吗?

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

【讨论】:

  • +1 谢谢。关键是(我在上面对@MarkSeeman 发表了类似的评论),如果我有一些对象图在很大程度上取决于其上下文的地方怎么办? IE。在接受来自数百个不同类型/协议的设备的连接的服务器应用程序中,我必须有一种方法来管理单个设备“管道”的生命周期,从传入端口,通过各种解析器(根据设备类型/协议实例化) , 给最终消费者。我几乎在任何地方都使用构造函数注入,但在某些地方我会在运行时动态创建更小的对象图。
  • @LousyCoder,你不能在运行时使用抽象工厂来构造对象图吗?
猜你喜欢
  • 1970-01-01
  • 2018-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多