14.5 StyletIoC 工厂
问题
构造函数/参数注入一切都很好,只要你只需要某物的一个实例。当您开始需要自己创建实例时(想想您的 ShellViewModel 想要显示对话框,并且需要为它们创建 ViewModels),即
class ShellViewModel
{
// ...
public void ShowDialog()
{
// 我们不想直接创建 DialogViewModel,因为它有自己的依赖项需要注入
var dialogVm = new DialogViewModel();
this.windowManager.ShowDialog(dialogVm);
}
}
诱人的解决方案是让您的 ShellViewModel(或其他)了解您的 IoC 容器,因此它可以调用this.container.Get<DialogViewModel>()
,如下所示:
class ShellViewModel
{
// ...
public ShellViewModel(IContainer container)
{
this.container = container;
}
public void ShowDialog()
{
var dialogVm = this.container.Get<DialogViewModel>();
this.windowManager.ShowDialog(dialogVm);
}
}
这被称为服务定位器模式,关于它是否实际上是一种反模式存在很多争论——本质上,它添加了一个不应该添加的依赖项(你的类需要知道你的(正确配置的)IoC容器)——同时隐藏您的类实际具有的依赖项。
解决方案
这个问题实际上在 1994 年被 GoF——抽象工厂模式解决了。由于您不能将 a 注入DialogViewModel
到您的 中ShellViewModel
,因此您可以注入一个可以创建的工厂DialogViewModels
,如下所示:
class ShellViewModel
{
// ...
public ShellViewModel(Func<DialogViewModel> dialogViewModelFactory, IWindowManager windowManager)
{
this.dialogViewModelFactory = dialogViewModelFactory;
this.windowManager = windowManager;
}
public void ShowDialog()
{
var dialogVm = this.dialogViewModelFactory();
this.windowManager.ShowDialog(dialogVm);
}
}
这非常有效:您可以在这里提供任何您喜欢的东西进行测试,而 IoC 容器可以提供适合正常运行的东西。
StyletIoC 支持两种类型的工厂:“Func-factories”——如上所示——和“抽象工厂”——你为工厂提供接口,StyletIoC 提供实现。两者都记录在下面各自的部分中。
功能工厂
这些是最容易使用的工厂类型。它们不需要您进行任何设置,而且非常灵活。然而,它们确实有一些限制:这些将在后面讨论,并由抽象工厂解决。
黄金法则是:如果你能执行container.Get<ISomeInterface>()
,你也能执行container.Get<Func<ISomeInterface>()
。构造函数和属性注入当然也是如此:如果您可以编写这样的构造函数:
class Foo
{
public Foo(IBar bar)
{
}
}
你也可以这样写:
class Foo
{
public Foo(Func<IBar> bar)
{
}
}
如果多次执行 Func<ISomeInterface>
工厂函数,效果就像多次调用 container.Get<ISomeInterface>()
一样:如果 ISomeInterface
被注册为单例,每次都会得到相同的实例。如果它被注册为瞬态,则每次都会得到不同的实例。
Func 工厂还有其他一些诀窍:如果您请求一个 Func<IEnumerable<ISomeInterface>>
(可以通过 container.Get<Func<IEnumerable<ISomeInterface>>>()
或构造函数/属性注入方式实现),则会得到一个工厂,当调用它时,将返回与 container.GetAll<ISomeInterface>()
(或者如果您已将 IEnumerable<ISomeInterface>
注入到构造函数/属性中)相同的东西。
类似地,如果您请求一个IEnumerable<Func<ISomeInterface>>
(通过container.GetAll<Func<ISomeInterface>())
,或者通过注入IEnumerable<Func<ISomeInterface>>
构造函数或属性),您将获得一组工厂,并且每个工厂都能够创建该实现的新实例ISomeInterface
。
Func 工厂确实有一个限制:无法获取需要键的内容。回想一下,您可以调用 container.Get<ISomeInterface>("magicKey")
,但是您不能像这样做 var factory = container.Get<Func<string, ISomeInterface>>(); factory("magicKey")
。这是一个有意的决定-一开始所有这些都有点不明显,并且当您开始涉及到工厂集合和集合的工厂时,有一些非常棘手的角落情况。如果您需要这种行为,请使用抽象工厂(下面会介绍)。
抽象工厂
抽象工厂类似于 func 工厂,但不是每个工厂都有类型Func<ISomeInterface>
,而是定义自己的工厂。
考虑这个例子:
interface IDialogViewModelFactory
{
DialogViewModel CreateDialogViewModel();
}
class ShellViewModel
{
// ...
public ShellViewModel(IDialogViewModelFactory dialogViewModelFactory)
{
this.dialogViewModelFactory = dialogViewModelFactory;
}
public void ShowDialog()
{
var dialogVm = this.dialogViewModelFactory.CreateDialogViewModel();
this.windowManager.ShowDialog(dialogVm);
}
}
在最基本的情况下,你可以提供一个简单的实现,IDialogViewModelFactory
它只是调用 IoC 容器以供生产使用,或者你可以提供一个模拟来进行测试。
但是编写该实现IDialogViewModelFactory
有点痛苦,而 StyletIoC 有一个解决方案。与其他一些 IoC 容器一样,StyletIoC 能够采用这样的接口,并为您生成一个实现(不同之处在于 StyletIoC 不需要任何依赖项来执行此操作)。
要利用这一点,请使用绑定语法builder.Bind<IFactoryInterfaceType>().ToAbstractFactory()
,例如:
public interface IDialogViewModelFactory
{
DialogViewModel CreateDialogViewModel();
}
// ...
builder.Bind<IDialogViewModelFactory>().ToAbstractFactory();
这样的工厂方法也可以创建类型的集合,例如:
public interface IVehicleFactory
{
IEnumerable<IVehicle> CreateVehicleCollection();
}
限制
编写工厂接口时需要牢记一些事项。这些在下面展开:
- 该接口必须是公共的,或者您必须
[assembly: InternalsVisibleTo(StyletIoC.FactoryAssemblyName)]
在您的AssemblyInfo.cs
. - 工厂中的每个方法都必须返回一个在 IoC 容器中注册的类型,或该类型的 IEnumerable,并且必须具有零参数或单个字符串参数。
StyletIoC 在不同的程序集中生成工厂接口的实现,因此您的接口必须是公共的,或者您必须已将此程序集标记为您的“朋友”(使用[assembly: InternalsVisibleTo(StyletIoC.FactoryAssemblyName)]
)。你可能确实希望你的工厂接口是公共的(这样你就可以在编写单元测试时手动提供一个实现。
StyletIoC 只知道如何为返回某些东西(即不是void
)、采用零参数或单个字符串参数(用作键,下面介绍)的方法生成方法实现。
在抽象工厂中使用键
当 StyletIoC 的实现创建一个类型的新实例时,您可以通过多种不同的方式指定要使用的键(请参阅StyletIoC 键)。第一个是使用[Inject(Key = "key")]
属性:
public interface IDialogViewModelFactory
{
[Inject(Key = "someKey")]
DialogViewModel CreateDialogViewModel();
}
您还可以创建采用单个字符串参数的方法,该参数将被使用:
public interface IDialogViewModelFactory
{
DialogViewModel CreateDialogViewModel(string key);
}
// ... then
this.dialogViewModel.CreateDialogViewModel("someKey");
在其内部运作中
在底层,StyletIoC 生成一个类型,其实现看起来很像这样:
public interface IFactoryInterface
{
TypeWithoutKey CreateTypeWithoutKey();
IEnumerable<ElementType> CreateCollection();
[Inject(Key = "key")]
TypeWithAttributeKey CreateTypeWithAttributeKey();
TypeWithKeyParameter CreateTypeWithKeyParameter(string key);
}
public class FactoryInterface : IFactoryInterface
{
private IContainer container;
public FactoryInterface(IContainer container)
{
this.container = container;
}
public TypeWithoutKey CreateTypeWithoutKey()
{
return this.container.GetTypeOrAll(typeof(TypeWithoutKey));
}
public IEnumerable<ElementType> CreateCollection()
{
return this.container.GetTypeOrAll(typeof(IEnumerable<ElementType>));
}
public TypeWithAttributeKey CreateTypeWithAttributeKey()
{
return this.container.GetTypeOrAll(typeof(TypeWithAttributeKey), "key");
}
public TypeWithKeyParameter CreateTypeWithKeyParameter(string key)
{
return this.container.GetTypeOrAll(typeof(TypeWithKeyParameter), key);
}
}
项目原地址:https://github.com/canton7/Stylet
当前文档原地址:https://github.com/canton7/Stylet/wiki/StyletIoC-Factories
上一节:WPF的MVVM框架Stylet开发文档 14.4 StyletIoC Keys
下一节:WPF的MVVM框架Stylet开发文档 14.6 StyletIoC 模块
上一篇:WPF的MVVM框架Stylet开发文档 13.验证模型基类ValidatingModelBase
下一篇:WPF的MVVM框架Stylet开发文档 15. 视图管理器 The ViewManager