为什么要使用依赖注入容器?

4

我已经完成了Karl Shiffet的 'InTheBox' WPF培训课程 ,发现它是学习WPF的极好资源。其中提到的一个问题是依赖注入和Unity容器的使用。以下是引起我的一些疑问的代码部分:

public partial class App : Application {

    protected override void OnStartup(StartupEventArgs e) {

        IUnityContainer container = new UnityContainer();

        container.RegisterType<IDialogService, ModalDialogService>(
            new ContainerControlledLifetimeManager());

        container.RegisterType<IEventRepository, EventRepository>(
            new ContainerControlledLifetimeManager());

        MainWindow window = container.Resolve<MainWindow>();

        window.DataContext = container.Resolve<MainWindowViewModel>();

        window.Show();
    }
}

依赖项被注册到UnityContainer中,然后由UnityContainer注入到MainWindowViewModel中。我的问题是为什么要使用容器?为什么不只使用以下代码,就可以实现依赖项注入的相同效果:
protected override void OnStartup(StartupEventArgs e)
{
    IDialogService dialogService = new ModalDialogService();
    IEventRepository eventRepository = new EventRepository();

    MainWindow window = new MainWindow();
    window.DataContext = 
        new MainWindowViewModel(eventRepository, dialogService);
    window.Show();
}

我仍然从组合根将依赖项注入构造函数,因此在这种情况下,我没有看到使用UnityContainer的任何好处。

我显然很感激它有存在的理由,但是有人能解释一下在这种情况下它是否有所增加吗?还有,是否还有其他场景可以毫不费力地使用容器?

2个回答

5

在这种简单的情况下使用DI容器并没有太多作用。当事情变得更加复杂时,使用它会更有意义,并且可以最大程度地减少依赖性变化的影响。

例如,假设您现在有一个ILoggingService,所有依赖项都要使用它。使用像Unity这样的DI容器时,您只需要添加一行代码。

    protected override void OnStartup(StartupEventArgs e)
    {
        IUnityContainer container = new UnityContainer();
        container.RegisterType<IDialogService, ModalDialogService>();
        container.RegisterType<IEventRepository, EventRepository>();
        container.RegisterType<ILoggingService, LoggingService>(); // added

        MainWindow window = container.Resolve<MainWindow>();
        window.DataContext = container.Resolve<MainWindowViewModel>();
        window.Show();
    }

自己动手实现时,您需要添加一行代码,并修改3行代码。
    protected override void OnStartup(StartupEventArgs e)
    {
        ILoggingService loggingService = new LoggingService(); // added
        IDialogService dialogService = new ModalDialogService(loggingService); // modified
        IEventRepository eventRepository = new EventRepository(loggingService); // modified

        MainWindow window = new MainWindow();
        window.DataContext = new MainWindowViewModel(eventRepository, dialogService, loggingService); // modified
        window.Show();
    }

当使用更高级的容器进行类型注册时,你可能不需要在组合根中更改任何代码。以下是使用AutoFac的示例。

    protected override void OnStartup(StartupEventArgs e)
    {
        var builder = new ContainerBuilder();
        var assembly = Assembly.GetExecutingAssembly();
        builder.RegisterAssemblyTypes(assembly)
               .AsSelf()
               .AsImplementedInterfaces();
        var container = builder.Build();

        MainWindow window = container.Resolve<MainWindow>();
        window.DataContext = container.Resolve<MainWindowViewModel>();
        window.Show();
    }

感谢提供额外的示例,展示了容器如何在引入新依赖项时不需要进行任何代码更改。 - andrew_scfc

2
你的第二个例子是使用了Mark Seemann所说的“穷人版DI”。它仍然是DI,但你需要自己实现。
当你开始管理许多不同类型和特性的注入时,IoC容器就会发挥作用,例如生命周期管理和按约定注册类型等功能,这些都可以大大节省劳动力。
对于具有最小依赖关系管理的较小任务,如你所建议的,它们可能过于复杂。
如果你想了解更多信息,我强烈推荐Seemann的书和博客。在我看来,他比任何人都更好地解释了这个主题。

感谢您抽出时间回答,当我们考虑扩展依赖注入方法时,我会查阅Seemann的书籍。 - andrew_scfc
我也推荐这本书。虽然我还没有读完,但我已经阅读的部分很有帮助。 - Tim B

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接