依赖注入-处理多次注入的干净方式



我对DI很陌生,我有一些问题希望人们能帮我解决,我目前正在开发一个使用Caliburn Micro的WPF-MVVM系统,使用MEF容器。

此应用程序用于跟踪装运情况,并包含多个部分。我希望我能足够清楚地解释这一点,但如果不清楚,请指出。

我有几个从数据库(通过Web服务)返回的实体,例如shipmentscontainerspackages

对于这些实体中的每一个,我都有一个封装Web服务实体的模型和一个管理器,管理器负责通过Web服务进行标准CRUD操作,并存储模式的ObservableCollection,然后将这些管理器注入到需要访问这些列表的viewmodels的构造函数中。

因此,我有发货shipmentManager>shipmentListViewModel,这样做是为了允许多个viewmodels使用相同的装运列表

然而,我已经开始遇到一些问题,其中一些viewmodels包含6+管理器的构造函数,以及一些仅用于传递到新构建的dialog viewmodels的情况。

我希望有人能为这个问题提出一个干净的解决方案,我想到的是一个单独的类,它将成为所有管理器容器,然后我可以简单地注入该集装箱类,并使用它来获得所需的Manager。然而,我看到有人建议不要使用这种方法,但没有明确说明原因。

另外,还有一个问题,我的模型实现了IEditableObject,因为我的经理负责维护模型列表,并保存对这些模型的更改,所以在EndEdit中发布管理者发现的事件会是一个问题吗?

编辑:按要求编码:

引导程序创建并导出所需的类:

        protected override void Configure()
    {
        container = new CompositionContainer(new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()));
        CompositionBatch batch = new CompositionBatch();
        IEventAggregator eventAggregator = new EventAggregator();
        batch.AddExportedValue<IWindowManager>(new WindowManager());
        batch.AddExportedValue<IEventAggregator>(eventAggregator);
        batch.AddExportedValue<IManager<ShipmentContainer>>(new ContainerManager());
        batch.AddExportedValue<IManager<Item>>(new ItemManager());
        batch.AddExportedValue<IManager<OrderedItem>>(new OrderedItemManager());
        batch.AddExportedValue<IManager<Package>>(new PackageManager());
        batch.AddExportedValue<IManager<Proforma>>(new ProformaManager(eventAggregator));
        batch.AddExportedValue<IManager<Project>>(new ProjectManager());
        batch.AddExportedValue<IManager<Shipment>>(new ShipmentManager(eventAggregator));
        batch.AddExportedValue<IManager<PackingItem>>(new PackingListManager(eventAggregator));
        batch.AddExportedValue(container);
        container.Compose(batch);
    }

ContentViewModel处理了菜单点击,这允许打开几个诊断日志,构造函数包含大量DI:

    public LBLContentViewModel(IWindowManager windowManager, IManager<Project> pManager, IEventAggregator eventManager, IManager<Item> iManager, IManager<PackingItem> plManager, IManager<Shipment> sManager)
        {
          ...
        }

对话框显示如下:

 public void OpenProject()
    {
        ProjectSearchViewModel viewModel = new ProjectSearchViewModel(_eventAggregator, _projectManager);
        this._windowManager.ShowDialog(viewModel);
    }

希望这是你想看到charleh的代码,如果不是,请告诉我,我会尽力提供所需的内容。

对此有两条评论。也许这不是你想要的答案,但也许它能敲响警钟。

1.-我不会使用Manager对象。经理不是一个好词,它可以意味着任何事情,它可能会负责很多事情,绝对不是一件好事,因为它会打破单一责任原则(SRP),这是SOLID原则之一。

2.-同样,一个具有多个责任的Manager可能不是一个好方法,一个有6个依赖项的类让我觉得这个类做得太多了。

很明显,您可以使用依赖注入来解决这个问题,而忘记每次创建新对象的痛苦。然而,我认为这只是一个小问题的补丁,而不是主要问题的补丁。

我的建议是分析类及其许多依赖项,并尝试将其拆分,以便应用一些面向对象的原则创建响应性较低的单元。

有时,我们的类会不断增长,看起来任何东西都无法拆分,尤其是使用ViewModels或Controllers。但是,一个视图可以有多个控件和多个ViewModels。。也许这就是我们要走的路。

顺便说一下,这个问题也有帮助!

编辑后:

我会按照我们在评论中所说的去做:

请先注册对话框。。

batch.AddExportedValue<ProjectSearchViewModel>(new ProjectSearchViewModel(eventAggregator,projectManager));

在主视图模型中,您可以有:

 public LBLContentViewModel(ProjectSearchViewModel projectSearchViewModel, OtherDependencies ...)
 {
      ...
      _projectSearchViewModel = projectSearchViewModel;
  }

打开对话框:

public void OpenProject()
{
    this._windowManager.ShowDialog(_projectSearchViewModel);
}

通过这种方式,您可以从MainViewModel中删除依赖项,并将它们移动到它们真正属于的地方,即对话框。

在我看来,MEF是一个强大的库,可以在大型应用程序中使用,并且可以在不耦合它们的情况下使用许多程序集。它也可以用作依赖注入引擎,但我认为它不是为这个目的而设计的。

看看这篇关于它的好文章。我建议添加一个IoC库,比如Unity或Autofac,以更有效地进行依赖注入。

我在这里做了一些假设,但:

是否有什么东西阻止您让对话框指定它们的依赖项,并让容器解析这些依赖项?

听起来你的经理类是单身生活(如果我错了,请纠正我);如果是这样的话,导致显示对话框的虚拟机就不需要依赖于他们并不真正感兴趣的管理者:对话框本身应该在实例化时解决这些依赖关系(我再次假设你的对话框是瞬态的)

SRP指出,对象应具有单一责任;虽然这听起来像是让一个只负责包含更多类的类成为一个单一的责任,但实际上你只是在创建另一个容器,IoC容器已经在做这件事了。

你能发布一些示例代码或澄清你是如何设置IoC的吗?什么是单例,什么是瞬态?大多数IoC容器也有工厂选项(如Castle Windsors TypedFactory),可以让您对瞬态实例化进行更多控制,因此这可能是一个选项(取决于您的复杂性)

编辑:看到您的代码后,解决方案就足够简单了。。。

如果您还没有这样做,请在MEF中导出您的ViewModels

容器也应该能够将所有VM解析为组件。不要担心出口的数量。我不确定MEF是否支持基于约定的注册(快速搜索表明它支持,但我不确定支持的程度)

一旦导出了虚拟机,就可以让MEF实例化并在需要虚拟机时满足依赖关系

当您需要VM的实例时,您可以使用CM中提供的静态IoC类(它只是从容器中解析)

var vm = IoC.Get<ProjectSearchViewModel>()

现在在对话框中显示此VM

_windowManager.ShowDialog(vm);

您还可以做得更好,将ProjectSearchViewModel的依赖项移到主VM的构造函数中,这样它就更清晰了(但无论哪种方式都可以)

由于依赖关系现在在实例化时得到满足,因此对话框可以指定它所依赖的内容,而不需要父窗口知道

然后LBLContentViewModel构造函数变得不那么臃肿:

 public LBLContentViewModel(IWindowManager windowManager, IEventAggregator eventManager)
 {
     ...
 }

并且ProjectSearchViewModel正确地指定了它的依赖

public ProjectSearchViewModel(IEventAggregator eventAggregator, IManager<Project> projectManager)
{
    ...
}

这是IoC的Inversion部分,现在组件和子组件指定了它们的需求和容器提供的内容。以前是另一种方式,由组件决定子组件需要什么。。。这将是痛苦的前进!

我相信最好的答案已经在你的问题中了:

我希望有人能为这个问题提出一个干净的解决方案,我正在考虑一个单独的类,它将成为所有类的容器管理器,然后我可以简单地注入该容器类并使用然而,我看到有人建议反对这种方法,但没有明确说明原因。

在我看来,这是最好的答案。如果你发现你不断地将同一组东西注入到你的一个构造函数中,那么很可能这组对象形成了某种逻辑分组,如果组合成一个类,这是有意义的。

还可以看看@margabit在回答中关于分析类及其依赖关系的内容。如果你的设计采用不同的结构,可能一开始就避免了这种情况,但并非总是可以回头改变一切。

我建议使用任何DI(依赖注入)组件。它非常干净和有力。

一些流行的DI组件:-Ninject-Unity-温莎城堡-Autofac-结构映射

否则,您可以定义一个属性,该属性的类型是您有两个或多个类的接口类型。然后动态创建适当的对象,并将其设置为属性。您可以将类名和完全限定的类名映射存储在配置文件中,该文件将在动态创建适当的对象时使用。

最新更新