假设我有一个使用条件参数的简单工厂(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
(或IP产品接口)的容器注册?
我应该使用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上已经阅读了很多类似的问题,但是根据我的需要,它们无法满足它们及其答案。)
答案 0 :(得分:6)
您不应该使用容器来做出像这样的运行时决定。而是通过容器将工厂注入客户端。如果工厂需要容器中的附属物,请在创建时将它们注入工厂。
将工厂更改为实际对象,而不仅仅是静态方法的容器,并注入。
答案 1 :(得分:6)
您不应以任何方式在课程中注入或使用容器。这包括使用参数。这样做的原因是,这样做会将代码绑定到容器。如果您必须实现另一个容器甚至是新版本,那么您将需要做很多工作。
然而,对于您描述Unity(以及其他一些注入框架)的情况,该功能称为“自动工厂”。它使用.NET Func&lt; TResult&gt;代表。这是一个.NET功能,因此它不会将您的类绑定到Unity。
这是如何使用它,首先注册您的服务。不要用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;
}
我希望对我的这个简短解释会激励你解决你的问题。
答案 2 :(得分:3)
您可以为注册定义唯一的名称;
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;
答案 3 :(得分:2)
正如@ChrisTavares所描述的那样,正如所描述的那样in this answer,解决方案就是将工厂注入客户端SomeClient
。
此外,为了遵循Dependency Inversion Principle (DIP),客户端应仅依赖于抽象工厂,例如工厂界面IProductFactory
。
这实际上只是做一个简单的依赖注入(DI)的问题。使用Unity仅仅是作为DI辅助器来处理(构造函数)依赖关系的解析。只需要在统一容器中注册具体工厂ProductFactory
。产品的创建完全由工厂处理,在那里使用'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 }