【问题标题】:Using an Ioc Container at runtime within a factory to determine class initialization在工厂内运行时使用 Ioc 容器来确定类初始化
【发布时间】:2011-03-15 22:54:42
【问题描述】:

这是一个好的模式吗?有一个工厂类知道 IUnityContainer...

我的基本需求是在运行时根据类的 Id 解析 ICalculationRuleProcess。它可能基于 ID 以外的其他内容,我知道...基本上我有一组已知的 ID 需要处理,因为我手动将记录引导到数据库中并且无法编辑记录。对于每个 ID,我都有一个相关的课程。我在每个实现 ICalculationRuleProcess 的类中也有不同数量的构造函数参数,因此与使用 Activator.CreateInstance 的一些疯狂的 switch 语句和变量构造函数参数相比,使用 IoC 容器非常有用

这是我所做的:

  1. 在容器本身中注册了 IUnityContainer 实例。我不确定这是否可能,但它确实有效。
  2. 在注册中使用唯一标识符注册了所有 ICalculationRuleProcess 类(基本上只是每个可能的 DistributionRule 的 Id.ToString())
  3. 创建了一个工厂来确定正确的 ICalculationRuleProcess,并让它使用 IoC 容器找出要加载的正确类。
  4. 将工厂类 (ICalculationRuleProcessFactory) 注册到 IoC 容器中
  5. 无论需要在何处使用 ICalculationRuleProcess,我都让该类在其构造函数中获取一个 ICalculationRuleProcessFactory,并让它调用 Create 方法来确定要使用哪个 ICalculationRuleProcess。

工厂的代码在这里:

  public interface ICalculationRuleProcessFactory
  {
    ICalculationRuleProcess Create( DistributionRule distributionRule );
  }

  public class CalculationRuleProcessFactory : ICalculationRuleProcessFactory
  {
    private readonly IBatchStatusWriter _batchStatusWriter;
    private readonly IUnityContainer _iocContainer;

    public CalculationRuleProcessFactory(
      IUnityContainer iocContainer,
      IBatchStatusWriter batchStatusWriter )
    {
      _batchStatusWriter = batchStatusWriter;
      _iocContainer = iocContainer;
    }

    public ICalculationRuleProcess Create( DistributionRule distributionRule )
    {
      _batchStatusWriter.WriteBatchStatusMessage( 
        string.Format( "Applying {0} Rule", distributionRule.Descr ) );

      return _iocContainer.Resolve<ICalculationRuleProcess>(
        distributionRule.Id.ToString() );
    }
  }

【问题讨论】:

    标签: c# design-patterns ioc-container


    【解决方案1】:

    考虑到您描述的限制,这对我来说似乎没问题。最重要的是,您的所有规则都实现了ICalculationRuleProcess,并且这些规则的所有使用者都只知道该接口。

    你的工厂接受容器依赖并不是天生的坏事,尤其是作为一个接口。考虑一下,如果您曾经不得不更改容器实现,您可以创建一个完全不使用 Unity 的 IUnityContainer 实现(只需将接口的所有成员转发到替换中的相应方法容器)。

    如果它真的困扰你,你可以通过创建一个不可知的 IoC 接口来添加另一个间接层,该接口具有必要的 RegisterResolve方法,并创建一个转发的实现这些到 Unity。

    【讨论】:

    • +1 我总是在我自己的接口中包装UnityContainer,部分原因是某些方法是扩展方法,因此不能在IUnityContainer 接口上进行单元测试
    【解决方案2】:

    还有另一种方法可以在工厂不依赖IUnityContainer 的情况下实现这一点,这本身并不是坏事。这只是思考问题的另一种方式。

    流程如下:

    1. 注册ICalculationRuleProcess的所有不同实例。
    2. 获取所有已注册的ICalculationRuleProcess 并为每个人创建一个创建 lambda。
    3. 使用ICalculationRuleProcess 创建 lambda 列表注册 ICalculationRuleProcessFactory
    4. ICalculationRuleProcessFactory.Create 中返回正确的进程。

    现在最棘手的部分是保留注册时使用的 ID。曾经的解决方案是简单地将 Id 保留在 ICalculationProcess 接口上,但它可能在语义上不属于那里。这就是这个解决方案变得丑陋的地方(这更像是 Unity 中缺少功能的情况)。但是,有了一个扩展方法和一个小的额外类型,它在运行时看起来不错。

    所以我们在这里要做的是创建一个扩展方法,它返回所有注册及其名称。

    public class Registration<T> where T : class {
        public string Name { get; set; }
        public Func<T> CreateLambda { get; set; }
    
        public override bool Equals(object obj) {
    
            var other = obj as Registration<T>;
            if(other == null) {
                return false;
            }
    
    
            return this.Name == other.Name && this.CreateLambda == other.CreateLambda;
        }
    
    
        public override int GetHashCode() {
            int hash = 17;
            hash = hash * 23 + (Name != null ? Name.GetHashCode() : string.Empty.GetHashCode());
            hash = hash * 23 + (CreateLambda != null ? CreateLambda.GetHashCode() : 0);
            return hash;
        }
    
    
    }
    
    public static class UnityExtensions {
        public static IEnumerable<Registration<T>> ResolveWithName<T>(this UnityContainer container) where T : class {
            return container.Registrations
              .Where(r => r.RegisteredType == typeof(T))
              .Select(r => new Registration<T> { Name = r.Name, CreateLambda = ()=>container.Resolve<T>(r.Name) });
        }
    }
    
     public class CalculationRuleProcessFactory : ICalculationRuleProcessFactory
      {
        private readonly IBatchStatusWriter _batchStatusWriter;
        private readonly IEnumerable<Registration<ICalculationRuleProcess>> _Registrations;
    
        public CalculationRuleProcessFactory(
          IEnumerable<Registration<ICalculationRuleProcess>> registrations,
          IBatchStatusWriter batchStatusWriter )
        {
          _batchStatusWriter = batchStatusWriter;
          _Registrations= registrations;
        }
    
        public ICalculationRuleProcess Create( DistributionRule distributionRule )
        {
          _batchStatusWriter.WriteBatchStatusMessage( 
            string.Format( "Applying {0} Rule", distributionRule.Descr ) );
    
          //will crash if registration is not present
          return _Registrations
            .FirstOrDefault(r=>r.Name == distributionRule.Id.ToString())
            .CreateLambda();
        }
      }
    
    //Registrations
    var registrations = container.ResolveWithName<ICalculationRuleProcess>(container);
    container.RegisterInstance<IEnumerable<Registration<ICalculationRuleProcess>>>(registrations);
    

    写完这篇文章后,我意识到这比架构上漂亮的解决方案更具创造性。但无论如何,请随时从中汲取灵感。

    【讨论】:

      【解决方案3】:

      嘿,Rob,我打算使用基本相同的模式。我有多种类型的购物车项目,它们需要与他们自己的不同类的特定验证器实例集相关联。

      我认为这种模式有点味道,并不是工厂引用了 IoC 容器,而是通常在应用程序根目录中配置 IoC 容器,这通常是 UI 层。如果创建一个疯狂的自定义工厂只是为了处理这些关联,那么它可能应该在域中。

      简而言之,这些关联可能不是在应用程序运行之前设置的整体程序结构的一部分,因此不应在应用程序根目录中定义。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-02-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多