带有参数条件的简单工厂,使用Unity 2.0

时间:2013-01-28 13:52:56

标签: c# .net dependency-injection unity-container factory

假设我有一个使用条件参数的简单工厂(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上已经阅读了很多类似的问题,但是根据我的需要,它们无法满足它们及其答案。)

4 个答案:

答案 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 }