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