将DI容器使用保留在Silverlight和MVVM中的组合根目录中

时间:2010-02-03 08:48:21

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

我不太清楚如何设计,所以我在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容器,只有它们和具有这种依赖关系的组合根。

1 个答案:

答案 0 :(得分:7)

为了尽可能接近您的示例代码,您可以以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;
    }
}

这分离了视图的创建和ViewModel的数量,符合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原则。