1

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

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

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

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

所以,我有shipping > shippingManager > shippingListViewModel,这样做是为了允许对同一个货件列表进行多项工作viewmodels

但是,我已经开始遇到一些问题,其中一些viewmodels具有包含 6 个以上管理器的构造函数,而有些情况仅用于传递新构造dialog viewmodels的 .

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

另外,另一个问题是,我的模型实现了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 的代码,如果不是,请告诉我,我会尽力提供所需的。

4

4 回答 4

3

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

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

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

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

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

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

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

编辑后:

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

先注册Dialog..

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

在主 ViewModel 中,您可以拥有:

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

打开对话框:

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

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

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

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

于 2013-09-03T16:34:29.963 回答
2

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

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

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

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

于 2013-09-03T17:02:01.097 回答
2

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

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

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

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

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

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

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

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

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

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

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)
{
    ...
}

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

于 2013-09-04T09:40:50.900 回答
-2

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

一些流行的 DI 组件: - Ninject - Unity - Castle.Windsor - Autofac - StructureMap

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

于 2013-09-03T16:19:59.503 回答