具有参数条件的简单工厂,使用 Unity 2.0
本文关键字:使用 Unity 工厂 简单 参数 条件 | 更新日期: 2023-09-27 17:56:15
假设我有一个简单的工厂(SimpleProductFactory
),它使用条件参数来确定如何创建这样的Product
:
public static class SimpleProductFactory
{
public static Product MakeProduct(Condition condition)
{
Product product;
switch(condition)
{
case Condition.caseA:
product = new ProductA();
// Other product setup code
break;
case Condition.caseA2:
product = new ProductA();
// Yet other product setup code
break;
case Condition.caseB:
product = new ProductB();
// Other product setup code
break;
}
return product;
}
}
此工厂由某些客户端使用,该客户端处理包含如下条件的运行时数据:
public class SomeClient
{
// ...
public void HandleRuntimeData(RuntimeData runtimeData)
{
Product product = SimpleProductFactory.MakeProduct(runtimeData.Condition);
// use product...
}
// ...
}
public class RuntimeData
{
public Condition Condition { get; set; }
// ...
}
如何使用 Unity 2.0 实现相同的构造行为?
重要的部分是条件(Condition
)决定了如何创建和设置Product
,并且条件仅在运行时已知,并且每个MakeProduct(...)
调用都不同。("其他产品设置代码"处理一些委托内容,但也可以处理其他初始化,并且需要成为构造的一部分。
Product
(或IProduct接口)的容器注册应该如何完成?
我应该使用InjectionFactory
结构吗?我该怎么做?
// How do I do this?
container.RegisterType<Product>(???)
我需要做什么才能在客户端代码中提供条件?
朴素的客户端代码(来自之前的编辑)来突出显示最后一个问题,这解释了几个答案的措辞:
public class SomeClient
{
// ...
public void HandleRuntimeData(RuntimeData runtimeData)
{
// I would like to do something like this,
// where the runtimeData.Condition determines the product setup.
// (Note that using the container like this isn't DI...)
Product product = container.Resolve<Product>(runtimeData.Condition);
// use product...
}
// ...
}
(我在Stackoverflow上读了很多类似的问题,但无法满足我的需求。
不应使用容器做出这样的运行时决策。相反,请通过容器将工厂注入客户端。如果工厂需要容器中的依赖项,请在创建时将它们注入工厂。
将工厂更改为实际对象,而不仅仅是静态方法的容器,然后注入它。
不应以任何方式在类中注入或使用容器。这包括使用参数。这样做的原因是这样做会将代码绑定到容器。然后,如果您必须实现另一个容器甚至新版本,您将留下大量工作。
但是,对于您描述的 Unity(和其他一些注入框架)具有称为"自动工厂"的功能。它使用 .NET Func
这是如何使用它,首先注册您的服务。不要向ContainerControlledLifetimeManager
注册它,否则每次都会得到相同的实例。
unity.RegisterType<IOpenFileService, OpenFileService>();
然后为其注册一个自动工厂。
unity.RegisterType<Func<IOpenFileService>>();
然后可以将其注入到任何需要它的类中。
unity.RegisterType<ViewModelBase, OptionsFileLocationsViewModel>(
new InjectionConstructor(new ResolvedParameter<Func<IOpenFileService>>());
如果您现在解析OptionsFileLocationsViewModel
的实例,则不会注入IOpenFileService
的实例,而是注入一个函数,如果调用该函数将返回IOpenFileService
的实例。
private readonly Func<IOpenFileService> openFileServiceFactory;
private string SelectFile(string initialDirectory)
{
var openFileService = this.openFileServiceFactory();
if (Directory.Exists(initialDirectory))
{
openFileService.InitialDirectory = initialDirectory;
}
else
{
openFileService.InitialDirectory =
System.Environment.GetFolderPath(System.Environment.SpecialFolder.Desktop);
}
bool? result = openFileService.ShowDialog();
if (result.HasValue && result.Value)
{
return openFileService.FileName;
}
return null;
}
我希望我的这个简短解释能激励你解决你的问题。
为注册定义唯一的名称;
container.RegisterType<Product ,ProductA>("ProductA");
container.RegisterType<Product, ProductB>("ProductB");
或在配置文件中;
<register type="Product" mapTo="ProductA" name="ProductA" />
<register type="Product" mapTo="ProductB" name="ProductB" />
然后,您可以根据注册解析实例:
string productName = "ProductB";
Product product = container.Resolve<Product>(productName);
您也可以使用如下所示的类作为名称;
public class ProductTypes
{
public static string ProductA
{
get
{
return "ProductA";
}
}
public static string ProductB
{
get
{
return "ProductB";
}
}
}
然后;
container.RegisterType<Product,ProductA>(ProductTypes.ProductA);
container.RegisterType<Product,ProductB>(ProductTypes.ProductB);
并解决它;
Product product = null;
switch(condition)
{
case Condition.caseA:
product = container.Resolve<Product>(ProductTypes.ProductA);
// Other product setup code
break;
case Condition.caseA2:
product = container.Resolve<Product>(ProductTypes.ProductA2);
// Yet other product setup code
break;
case Condition.caseB:
product = container.Resolve<Product>(ProductTypes.ProductB);
// Other product setup code
break;
}
return product;
如@ChrisTavares所述,以及本答案中所述,解决方案是简单地将工厂注入客户端SomeClient
。此外,为了遵循依赖反转原则 (DIP),客户端应仅依赖于抽象工厂,例如工厂接口 IProductFactory
。
这实际上只是一个做普通依赖注入(DI)的问题。Unity 的使用只是作为 DI 促进器来处理(构造函数)依赖项的解析。只有混凝土工厂ProductFactory
需要注册到 unity 容器。产品的创建完全由工厂处理,在那里使用"new"关键字是完全可以的。
container.RegisterType<IProductFactory, ProductFactory>();
以下是解决方案的外观:
public interface IProductFactory
{
IProduct MakeProduct(Condition condition);
}
internal class ProductFactory : IProductFactory
{
public IProduct MakeProduct(Condition condition)
{
IProduct product;
switch (condition)
{
case Condition.CaseA:
product = new ProductA();
// Other product setup code
break;
case Condition.CaseA2:
product = new ProductA();
// Yet other product setup code
break;
case Condition.CaseB:
product = new ProductB();
// Other product setup code
break;
default:
throw new Exception(string.Format("Condition {0} ...", condition));
}
return product;
}
}
public class SomeClient
{
private readonly IProductFactory _productFactory;
public SomeClient(IProductFactory productFactory) // <-- The factory is injected here!
{
_productFactory = productFactory;
}
// ...
public void HandleRuntimeData(RuntimeData runtimeData)
{
IProduct product = _productFactory.MakeProduct(runtimeData.Condition);
// use product...
}
// ...
}
public class RuntimeData
{
public Condition Condition { get; set; }
// ...
}
public interface IProduct
{ //...
}
internal class ProductB : IProduct
{ //...
}
internal class ProductA : IProduct
{ //...
}
public enum Condition { CaseA, CaseA2, CaseB }