【问题标题】:Dependency Injection - Clean way to handle multiple injections依赖注入 - 处理多次注入的干净方法
【发布时间】:2013-09-06 23:06:27
【问题描述】:

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

此应用程序用于跟踪货物,并包含几个部分。我希望我能解释得足够清楚,但如果不清楚,请指出。

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

对于这些实体中的每一个,我都有一个包装 web 服务实体的 model,以及一个 ma​​nager,manager 还负责通过 web 服务进行标准 CRUD 操作由于存储了 modelObservableCollection,这些管理器随后被注入到需要访问这些列表的 viewmodels 的构造函数中。

所以,我有 shipment > shipmentManager > shipmentListViewModel,这样做是为了允许多个 viewmodels 使用相同的 出货量

但是,我开始遇到一些问题,即一些 viewmodels 的构造函数包含 6 个以上 ma​​nagers,而有些情况仅用于传递给新构造的 dialog viewmodels

我希望有人可以为这个问题提出一个干净的解决方案,我正在考虑一个单一的类,它将成为所有 经理容器,并且然后我可以简单地注入那个 container 类并使用它来获得所需的 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 处理了菜单点击,允许打开几个diaglogs,构造函数包含大量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 的代码,如果不是,请告诉我,我会尽力提供所需的。

【问题讨论】:

  • 好的 - 所以你需要得到一个 ProjectSearchViewModel 的实例 - 为什么不直接使用 IoC.Get&lt;ProjectSearchViewModel&gt;() (你的虚拟机也应该被导出) - 然后将你的 IEventAggregatorIManager&lt;Project&gt; 移动到ProjectSearchViewModel 的构造函数。你在这里做的不是 IoC,因为依赖的控制在于一个臃肿的类
  • 我认为这就是 margabit 的建议,我现在打算这样做,我有点担心引导程序中的导出数量,但我认为这不是问题?
  • 很多 IoC 容器允许您根据约定批量注册依赖项 - 不确定 MEF 允许您做什么,但是显式地一一导出所有对象是正常的(批量注册是为了制作东西而发明的在这些注册课程中少一点罗嗦,仅此而已)
  • 太好了,再次感谢您的帮助,charleh。我有点担心显示该代码,因为我觉得我做错了什么,谢天谢地似乎没有。
  • 一个例子——在温莎你会使用_container.Register(Classes.FromThisAssembly().InSameNamespaceAs&lt;SomeClass&gt;())而不是明确地注册所有东西。 MEF 似乎支持约定,但我必须阅读一下它到底支持什么

标签: c# wpf mvvm dependency-injection caliburn.micro


【解决方案1】:

对此有两个cmets。可能不是您正在寻找的答案,但也许它可以敲响警钟。

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

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

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

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

有时我们的类会不断增长,看起来好像任何东西都无法拆分,特别是对于 ViewModel 或 Controller。但是,一个 View 可以有多个控件和多个 ViewModel。也许这就是要走的路。

顺便说一句,this question can be helpful too!

编辑后:

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

先注册Dialog..

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

在主 ViewModel 中,您可以:

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

打开对话框:

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

通过这种方式,您可以从 MainViewModel 中移除 Dependencies 并将它们移动到它们真正属于的位置,即 Dialog。

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

Take a look at this great post about it。我建议添加一个 IoC 库,例如 Unity 或 Autofac 来更有效地进行依赖注入。

【讨论】:

  • 感谢您的回复,我最近才想到“SOLID”的想法,不断的工作让我知道我的大学教育实际上是多么缺乏!但是,我“认为”我的经理只是在做事,他们管理 ObservableCollections > CRUD 操作。除非我要将管理器拆分为其基本功能(保存、更新等),否则我看不到我是如何破坏 SRP 的(但是我可能是错的)此外,接收 6 次注入的视图模型仅仅是因为它创建了需要那些通过的对话框.
  • 尝试隔离 Dialogs ViewModel 怎么样,这样您就可以将它们注入您的主 ViewModel 并且注入器会解析 Dialog 依赖项?
  • 我已经考虑过这样做,但这不是将问题转移到引导程序中,而必须在其中创建和导出所有对话框视图模型吗?
  • 引导程序只会将依赖项注入到请求的对象中。这与主 ViewModel 现在正在执行的操作相同。但是如果 ViewModel 负责创建 Dialogs,那么每次您需要在 Dialog 中创建一个新对象时,主 ViewModel 都需要首先获取它。这意味着您的主 ViewModel 充满了甚至不属于它的依赖项。我认为 DI 在这种情况下可以使事情变得更干净
  • 啊,我明白你的意思了,我想将未使用的对象排除在视图模型之外肯定是要走的路。抱歉,如果我在制作过程中尝试学习一整套新概念的吸收速度有点慢,有时会有点不知所措哈哈。再次感谢。
【解决方案2】:

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

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

听起来你的经理班会是单身生活(如果我错了,请纠正我);如果是这种情况,导致显示对话框的 VM 不需要依赖于他们并不真正感兴趣的管理器:对话框本身应该在实例化时解决这些依赖关系(我再次假设您的对话框将是短暂的)

SRP 声明对象应该有一个单一的职责;虽然听起来创建一个只负责包含更多类的类是一项单一职责,但实际上您只是在创建另一个容器,这是 IoC 容器已经在做的事情。

您能否发布一些示例代码或阐明您的 IoC 设置 - 什么是单例,什么是瞬态?大多数 IoC 容器还具有工厂选项(例如 Castle Windsors TypedFactory),可以让您更好地控制瞬态实例化,因此这可能是一个选项(取决于您的复杂性)

编辑:好的,看到您的代码后,解决方案很简单......

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

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

导出 VM 后,您可以让 MEF 在需要 VM 时实例化并满足依赖关系

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

var vm = IoC.Get<ProjectSearchViewModel>()

现在在对话框中显示此虚拟机

_windowManager.ShowDialog(vm);

您还可以做得更好,将 ProjectSearchViewModel 的依赖关系移到主 VM 的构造函数中,这样会更清晰(但任何一种方式都可以)

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

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

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

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

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

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

【讨论】:

  • 我现在将尝试发布一些代码,希望我能解决您的问题,我必须承认必须查看瞬态在这方面的含义,但是我应该能够展示您的需求。
  • 瞬态仅仅意味着不止一个实例——每次需要服务时,容器都会提供一个新实例(大多数容器都有额外的选项来满足可变的运行时间依赖性)
【解决方案3】:

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

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

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

还采取@margabit 在他的回答中所说的关于分析类及其依赖项的内容。与您的设计不同的结构可能一开始就避免了这种情况,但并不总是可以返回并更改所有内容。

【讨论】:

    【解决方案4】:

    我建议使用任何 DI(依赖注入)组件。它非常干净且功能强大。

    一些流行的 DI 组件: - 忍者 - 团结 - 城堡.温莎 - Autofac - 结构图

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

    【讨论】:

    • 我不太确定你在这里的建议是什么,我已经在使用 DI 将管理器注入到视图模型中,我正在寻找一种方法来减少这些视图模型中所需的注入次数.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-08-22
    • 1970-01-01
    • 1970-01-01
    • 2013-01-30
    • 2020-12-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多