在工厂的运行时使用Ioc容器来确定类初始化

时间:2010-07-14 16:00:43

标签: c# design-patterns ioc-container

这是一个好模式吗?有一个代码味道给我一个工厂类知道IUnityContainer ...

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

这是我做的:

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

      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() );
        }
      }
    

3 个答案:

答案 0 :(得分:3)

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

您的工厂采用容器依赖关系本身并不是很糟糕,尤其是作为接口。考虑一下,如果您曾 更改容器实现,您可以创建一个完全不使用Unity的IUnityContainer实现(只需将接口的所有成员转发到其中的相应方法)替换容器)。

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

答案 1 :(得分:2)

如果没有工厂依赖于IUnityContainer,你可以采用另一种方式实现这一点,ICalculationRuleProcess本身并不是坏事。这只是思考问题的另一种方式。

流程如下:

  1. 注册ICalculationRuleProcess
  2. 的所有不同实例
  3. 获取所有已注册的ICalculationRuleProcessFactory,并为每个人创建一个创建lambda。
  4. 使用ICalculationRuleProcess创建lambda的列表注册ICalculationRuleProcessFactory.Create
  5. ICalculationProcess中返回正确的流程。
  6. 现在,棘手的部分是保留注册所依据的ID。一旦解决方案是简单地将Id保留在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); 接口上,但它可能在语义上不属于那里。这就是这个解决方案陷入丑陋的地方(这更像是Unity中缺少功能的情况)。但是,使用扩展方法和一个小额外类型,它在运行时看起来很不错。

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

    {{1}}

    在我写完这篇文章之后,我意识到这是一个更具创意的lambda douchebaggery,而不是一个架构上非常漂亮的解决方案。但无论如何,随时可以从中获取想法。

答案 2 :(得分:0)

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

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

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