10

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

4

4 回答 4

6

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

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

于 2013-01-28T20:23:25.353 回答
6

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

但是,对于您描述的 Unity(和其他一些注入框架)的情况,有一个称为“自动工厂”的功能。它使用 .NET Func<TResult> 委托。这是一个 .NET 功能,因此它不会将您的类与 Unity 绑定。

这是如何使用它,首先注册你的服务。不要使用 a 注册它,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;
}

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

于 2013-01-30T10:32:04.013 回答
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;
于 2013-01-28T13:55:40.130 回答
2

正如@ChrisTavares 所描述的那样,正如在这个答案中所描述的那样,解决方案是将工厂简单地注入到客户端SomeClient中。此外,为了遵循依赖倒置原则 (DIP),客户端应该只依赖于一个抽象工厂,例如工厂接口IProductFactory

这实际上只是一个简单的依赖注入(DI)的问题。Unity 的使用仅作为 DI 促进者来处理(构造函数)依赖项的解析。只有具体工厂ProductFactory需要在统一容器中注册。产品的创建完全由工厂处理,使用“新”关键字完全可以。

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 }
于 2013-10-04T13:43:05.873 回答