具有参数条件的简单工厂,使用 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 2.0

不应使用容器做出这样的运行时决策。相反,请通过容器将工厂注入客户端。如果工厂需要容器中的依赖项,请在创建时将它们注入工厂。

将工厂更改为实际对象,而不仅仅是静态方法的容器,然后注入它。

不应以任何方式在类中注入或使用容器。这包括使用参数。这样做的原因是这样做会将代码绑定到容器。然后,如果您必须实现另一个容器甚至新版本,您将留下大量工作。

但是,对于您描述的 Unity(和其他一些注入框架)具有称为"自动工厂"的功能。它使用 .NET Func 委托。这是一项 .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;
}

我希望我的这个简短解释能激励你解决你的问题。

您可以

为注册定义唯一的名称;

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 }