依赖注入- MVVM IoC挑战:实现这个ViewModel工厂接口的具体类



我正在制作一个Windows Store应用程序,我想使用MVVM, Unity和IoC。我正在努力创建包装模型对象的ViewModels。其他许多帖子也问过类似的问题,但我相信这是一个略有不同的看法。

我试图创建一个ViewModel工厂服务,我可以注入到我的ViewModels。此工厂的接口可能如下所示:

public interface IViewModelFactoryService {
    TViewModel Create<TViewModel, TModel>(TModel domainObject) 
        where TViewModel : ViewModelBase 
        where TModel : DomainObject;
}

问题是,我试图传递一个模型对象到ViewModel构造函数和注入服务作为额外的参数。我也在努力坚持工厂不应该有对IoC容器的引用的原则,所以使用容器。不能在工厂内解析。

使问题复杂化的是,不同的viewmodel当然需要不同的服务。我相信解决方案可能涉及使用InjectionFactory(一个Unity对象,它可以让你为注册类型配置一个工厂),但我似乎不能让所有的部分都适合得很好。

下面是这个工厂需要创建的ViewModel构造函数:

FooViewModel(Foo model)
BarViewModel(Bar model, IViewModelFactoryService factory)
BazViewModel(Baz model, IViewModelFactoryService factory, IRepository repository)
下面是一个使用Unity的InjectionFactory为两个ViewModel类创建工厂的例子:
container.RegisterType<Func<Bar, BarViewModel>>(new InjectionFactory(
    c => new Func<Bar, BarViewModel>(
        bar => new BarViewModel(bar, 
            c.Resolve<IViewModelFactoryService>()))))
container.RegisterType<Func<Baz, BazViewModel>>(new InjectionFactory(
    c => new Func<Baz, BazViewModel>(
        baz => new BazViewModel(baz, 
            c.Resolve<IViewModelFactoryService>(), 
            c.Resolve<IRepository>()))))

一旦这些工厂注册到容器中,可以使用以下代码:

barFactory = container.Resolve<Func<Bar, BarViewModel>>();
barViewModel = barFactory(myBar);
bazFactory = container.Resolve<Func<Baz, BazViewModel>>();
bazViewModel = bazFactory(myBaz);

当然,我的最终目标是做到以下几点:

viewModelFactory = container.Resolve<IViewModelFactory>();
barViewModel = viewModelFactory.Create<BarViewModel, Bar>(myBar);
bazViewModel = viewModelFactory.Create<BazViewModel, Baz>(myBaz);

如果我能做到这一点,我就可以在任何我想要的地方注入IViewModelFactoryService,并且只要我能够访问被包装的模型对象,我就可以为任何类型创建视图模型。

我认为一定有某种方法可以使用各个ViewModels的工厂来创建我所描述的IViewModelFactoryService的具体实现,但是我不能以正确的方式将这些部分组合在一起。

我可以为每个ViewModel的每个工厂创建一个具体IViewModelFactoryService类的构造函数参数,但这显然是不可取的。我可以在具体的IViewModelFactoryService类上使用属性注入做类似的事情,但随后我将为每个ViewModel定义单独的属性。这两种可能的途径都是不可取的,因为每次创建一个新的ViewModel时,我都必须修改具体的IViewModelFactoryService类。

也许DynamicObject可以在这里使用一些。到目前为止,我一直避免在WinRT中使用它,因为它似乎无法从XAML绑定到DynamicObject的动态属性。但是对于IViewModelFactoryService具体类来说,这不是问题。

仅供参考,直到我弄清楚这一点(如果我这样做),我已经下注,并在容器外创建我的ViewModels。基本上我在做"穷人的依赖注入"。需要创建ViewModel的ViewModel被注入新ViewModel可能需要的所有参数。例如,如果BarViewModel需要创建一个BazViewModel:

class BarViewModel {
    IRepository _repository;
    Bar _bar;
    BarViewModel(Bar bar, IRepository repository) {
        _bar = bar;
        _repository = repository;
    }
    void DoSomethingWithBaz(Baz baz) {
        var bazViewModel = new BazViewModel(baz, _repository);
        // do something with bazViewModel
    }
当然,这样做的缺点是Bar本身不应该依赖于IRepository。它只会被注入,因为它在构造BazViewModel时需要使用它。抽象工厂模型将消除Bar对IRepository的依赖。另一个缺点是容器不再管理BazViewModel,我必须自己进行注入。

这里有一些博客文章,它们使我倾向于将容器从IViewModelFactoryService具体类中保留出来。其结果是注册释放解析模式和强调组合根的结合,以保持代码库的干净,并避免在业务逻辑中对IoC容器的"隐藏"依赖。

  • 注册解析释放模式
  • 使用反转控制容器
  • 初始合成后从容器中拉出

当然,如果将容器注入到具体工厂类中可以解决整个问题,使我不再感到头痛,那么牺牲一点纯度也许是合理的。但感觉就像放弃寻找优雅的解决方案。

根据Patrice的评论,我继续创建了一个注入容器的具体类的版本。这种方式当然很容易创建,我可以接受这个论点,即它可以被视为组合根的一部分。

这是接口的最终版本(以支持包装DomainObject和不包装DomainObject的viewmodel)。

public interface IViewModelFactory {
    TViewModel Create<TViewModel, TModel>(TModel domainObject) where TViewModel : ViewModelBase where TModel : DomainObject;
    TViewModel Create<TViewModel>() where TViewModel : ViewModelBase;
}

下面是ViewModelFactory的实现:

public class ViewModelFactory : IViewModelFactory, IDisposable {
    IUnityContainer _container;
    public ViewModelFactory(IUnityContainer container) {
        if (null == container) throw new ArgumentNullException("container");
        _container = container;
    }
    public TViewModel Create<TViewModel, TModel>(TModel domainObject)
        where TViewModel : GalaSoft.MvvmLight.ViewModelBase
        where TModel : DomainObject {
        return _container.Resolve<Func<TModel, TViewModel>>()(domainObject);
    }

    public TViewModel Create<TViewModel>() where TViewModel : GalaSoft.MvvmLight.ViewModelBase {
        return _container.Resolve<TViewModel>();
    }
    public void Dispose() {
        _container = null;
    }
}

我还需要为我希望能够用工厂创建的每个ViewModels注册一个工厂委托,加上ViewModelFactory本身。在这个例子中,stacklisttitemviewmodel是一个ViewModel,它包装了一个名为Stack的DomainObject:

container.RegisterType<Func<Stack, StackListItemViewModel>>(
    new InjectionFactory(c => new Func<Stack, StackListItemViewModel>(
        stack => new StackListItemViewModel(stack, container.Resolve<IRepository>()))));
container.RegisterType<IViewModelFactory, ViewModelFactory>(new ContainerControlledLifetimeManager(), new InjectionConstructor(container));

注意,我不想将容器本身的实例注册到容器中。如果我这样做,对于那些决定开始使用容器作为ServiceLocator的人来说,这就变成了一个滑坡。这就是为什么我使用一个InjectionConstructor将容器传递给ViewModelFactory的构造函数。

最新更新