【问题标题】:MVVM IoC challenge: implement concrete class for this ViewModel factory interfaceMVVM IoC 挑战:为此 ViewModel 工厂接口实现具体类
【发布时间】:2015-08-11 15:16:27
【问题描述】:

我正在开发一个 Windows 应用商店应用程序,我想在其中使用 MVVM、Unity 和 IoC。我正在努力创建包装模型对象的 ViewModel。其他各种帖子都提出了类似的问题,但我相信这是一个略有不同的看法。

我正在尝试创建一个可以注入到我的 ViewModel 中的 ViewModel 工厂服务。该工厂的接口可能如下所示:

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

问题是我试图将模型对象传递给 ViewModel 构造函数并将服务作为附加参数注入。我也试图坚持工厂不应该引用 IoC 容器的原则,因此不能在工厂内使用 container.Resolve。

使问题复杂化的是,不同的 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,并确保我可以为任何类型创建视图模型,只要我可以访问被包装的模型对象。

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

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

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

仅供参考,在我弄清楚这一点之前(如果我这样做的话),我已经下注并正在容器外创建我的 ViewModel。基本上我在做“穷人的依赖注入”。需要创建 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 具体类之外。结果是 Register Release Resolve 模式和对 Composition Roots 的强调相结合,以保持代码库清洁并避免业务逻辑中对 IoC 容器的“隐藏”依赖。

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

【问题讨论】:

  • “工厂不应该引用 IoC 容器的原则” - 我有兴趣看到一些博客文章/证据,说明为什么这是一件好事。
  • 这绝对是一个纯粹主义者的论点,但我可以看到它的好处。我正在用一些使我了解这一原则的博客文章更新原始文章。当然,这不是证据,而只是支持一个立场的论据。
  • 好吧,我没有看到任何说您不应该在您的工厂中使用任何 IoC 容器的内容。工厂应该位于系统的“根”端(在组合根模式中),在这里引用 IoC 容器是完全可以的。 “应用程序”端应该通过接口与这些工厂通信,完全不知道容器。这确实在您提到的第三篇博客文章的末尾说明了。
  • 这值得考虑。我无法摆脱这样一种感觉,即我应该能够创建几乎任何我能想象到的抽象工厂,而无需为工厂提供对容器的引用以供以后使用。在这种情况下这样做可能是合理的,但在未来会有不可行的情况吗?但我会尝试你的建议,看看我能创造什么。我会回来报告的。
  • 你可以,但是你会失去使用 IoC 容器的所有好处,根本没有其他好处。特别是,在纯抽象工厂中,解决链式依赖关系是一个真正的痛苦。

标签: mvvm dependency-injection inversion-of-control unity-container


【解决方案1】:

基于 Patrice 的 cmets,我继续创建了注入容器的具体类的一个版本。以这种方式创建当然很容易,而且我可以接受这样的论点,即它可以被视为 Composition Root 的一部分。

这是界面的最终版本(支持包装 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;
    }
}

我还需要为我希望能够使用工厂创建的每个 ViewModel 以及 ViewModelFactory 本身注册一个工厂委托。在此示例中,StackListItemViewModel 是包装了名为 Stack 的 DomainObject 的 ViewModel:

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 的构造函数的原因。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-12-25
    • 2018-08-12
    • 1970-01-01
    • 2012-07-25
    • 1970-01-01
    • 1970-01-01
    • 2013-09-30
    • 1970-01-01
    相关资源
    最近更新 更多