【问题标题】:Simple Factory with parameter condition, using Unity 2.0带有参数条件的简单工厂,使用 Unity 2.0
【发布时间】:2013-01-28 13:52:55
【问题描述】:

假设我有一个简单工厂 (SimpleProductFactory)使用条件参数来确定如何创建 Products,如下所示:

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 上阅读了很多类似的问题,但无法将它们及其答案满足我的需求。)

【问题讨论】:

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


    【解决方案1】:

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

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

    【讨论】:

    • 这样的Factory应该是什么样的?它应该只在里面使用Container吗?
    【解决方案2】:

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

    但是,对于您描述的 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;
    }
    

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

    【讨论】:

    • 是的,在我的问题的 SomeClient 类中使用容器不是 DI 而是 Service Locator。
    【解决方案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;
    

    【讨论】:

    • 谢谢。是的,但是我错过了工厂的附加逻辑,而且我需要将 RuntimeData.Condition 映射到客户端中的类名!我不希望客户知道基于此获得产品的条件。
    • 用通用接口将产品设置代码封装在产品内部...product.setup();就可以省去整个开关
    • @UlfÅkerstedt 也许我没有正确理解这个问题;但是我的回答与您当前的代码提供了相同的行为;请提供您的完整代码,以便我可以轻松理解问题。
    【解决方案4】:

    正如@ChrisTavares 所描述的那样,正如in this answer 所描述的那样,解决方案是简单地将工厂注入 到客户端SomeClient 中。 此外,为了遵循Dependency Inversion Principle(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 }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多