【问题标题】:Keeping the DI-container usage in the composition root in Silverlight and MVVM在 Silverlight 和 MVVM 的组合根中保持 DI 容器的使用
【发布时间】:2011-01-12 12:54:15
【问题描述】:

我不太清楚如何设计,所以我在 Silverlight + MVVM 应用程序的组合根中保留了对 DI 容器的引用。

我有以下简单的使用场景:有一个主视图(可能是项目列表)和一个用于打开单个项目的编辑视图的操作。因此,当用户执行操作(例如单击某个按钮)时,主视图必须创建并显示编辑视图。

为此,我有以下代码:

public interface IView
{
   IViewModel ViewModel {get; set;}
}

然后,对于我需要能够创建的每个视图,我都有一个抽象工厂,就像这样

public interface ISomeViewFactory
{
   IView CreateView();
}

然后这个工厂被声明为“父”视图模型的依赖,像这样:

public class SomeParentViewModel
{
   public SomeParentViewModel(ISomeViewFactory viewFactory)
   {
       // store it
   }

   private void OnSomeUserAction()
   {
      IView view = viewFactory.CreateView();
      dialogService.ShowDialog(view);
   }       
} 

所以在此之前一切都很好,看不到 DI 容器 :)。现在是 ISomeViewFactory 的实现:

public class SomeViewFactory : ISomeViewFactory
{
    public IView CreateView()
    {
        IView view = new SomeView();
        view.ViewModel = ????   
    }
}

“???”部分是我的问题,因为视图的视图模型需要从 DI 容器中解析,以便注入其依赖项。我不知道的是如何在不依赖组合根以外的任何地方的 DI 容器的情况下做到这一点。

一种可能的解决方案是依赖注入到工厂的视图模型,如下所示:

public class SomeViewFactory : ISomeViewFactory
{
    public SomeViewFactory(ISomeViewModel viewModel)
    { 
       // store it
    }

    public IView CreateView()
    {
        IView view = new SomeView();
        view.ViewModel = viewModel;
    }
}

虽然这可行,但它有一个问题,因为整个对象图是“静态”连接的(即“父”视图模型将获得 SomeViewFactory 的实例,它将获得 SomeViewModel 的实例,并且这些将存在只要“父”视图模型存在),注入的视图模型实现是有状态的,如果用户打开子视图两次,第二次视图模型将是同一个实例并具有之前的状态。我想我可以使用“初始化”方法或类似的方法来解决这个问题,但它闻起来不太对。

另一种解决方案可能是包装 DI 容器并让工厂依赖于包装器,但它仍然是一个“伪装”的 DI 容器 :)

ps:我目前的解决方案是工厂知道 DI 容器,只有它们和组合根具有这种依赖关系。

【问题讨论】:

    标签: c# silverlight mvvm dependency-injection inversion-of-control


    【解决方案1】:

    为了尽可能接近您的示例代码,您可以以 IViewPopulator 的形式引入另一个级别的间接:

    public interface IViewPopulator
    {
        void Populate(IView view);
    }
    

    您现在可以像这样实现您的 SomeViewFactory:

    public class SomeViewFactory : ISomeViewFactory
    {
        private readonly IViewPopulator populator;
    
        public SomeViewFactory(IViewPopulator populator)
        {
            if (populator == null)
            {
                throw new ArgumentNullException("populator");
            }
    
            this.populator = populator;
        }
    
        public IView CreateView()
        {
            IView view = new SomeView();
            this.populator.Populate(view);
            return view;
        }
    }
    

    这将视图的创建和视图模型的填充分开,遵循Single Responsibility Principle。一定程度上也是Service Aggregation的一个例子。

    您现在可以将 IViewPopulator 实现为具有正常依赖关系的具体类型:

    public class SomeViewPopulator : IViewPopulator
    {
        private readonly IDependency dep;
    
        public SomeViewPopulator(IDependency dep)
        {
            if (dep == null)
            {
                throw new ArgumentNullException("dep");
            }
    
            this.dep = dep;
        }
    
        public void Populate(IView view)
        {
            var vm = // Perhaps use this.dep to create an instance of IViewModel...
            view.ViewModel = vm;
        }
    }
    

    您可能还有其他方法可以对 IView 和 IViewModel 之间的关系进行建模,但以上代表了一种可能的解决方案。

    关键是不断提取抽象,直到每个抽象都有明确的职责。这个练习根本不是为了让代码与容器无关,而是最终要遵守 SOLID 原则。

    【讨论】:

    • 非常感谢。尽管您的实现非常清晰,但我实际上所做的只是在我的工厂中声明视图模型的依赖项,因为它们确实非常基本,而且我觉得拥有像您的视图填充器这样的东西对我来说太过分了。尽管如此,从 SOLID 的角度来看,尤其是当事情变得复杂时,像你所做的那样分离责任肯定是很有意义的。好东西:)
    猜你喜欢
    • 2011-01-14
    • 2012-11-30
    • 1970-01-01
    • 2011-06-29
    • 2018-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多