如何避免依赖注入构造函数的疯狂?

时间:2010-03-10 20:08:15

标签: c# java dependency-injection inversion-of-control ioc-container

我发现我的构造函数开始看起来像这样:

public MyClass(Container con, SomeClass1 obj1, SomeClass2, obj2.... )

随着参数列表不断增加。由于“容器”是我的依赖注入容器,为什么我不能这样做:

public MyClass(Container con)

每节课?有什么缺点?如果我这样做,感觉我正在使用一个美化的静电。请分享您对IoC和依赖注入疯狂的想法。

9 个答案:

答案 0 :(得分:376)

如果您将容器用作服务定位器,那么它是正确的,它或多或少是一个美化的静态工厂。由于很多原因I consider this an anti-pattern

构造函数注入的一个很好的好处是它会明显违反Single Responsibility Principle

当发生这种情况时,是refactor to Facade Services的时间。简而言之,创建一个新的,更强大的粗粒度界面,该界面隐藏了您当前所需的部分或全部细粒度依赖关系之间的交互。

答案 1 :(得分:58)

我认为您的类构造函数不应该引用您的IOC容器周期。这表示您的类和容器之间不必要的依赖关系(IOC试图避免的依赖类型!)。

答案 2 :(得分:24)

传递参数的难度不是问题。问题是你的班级做得太多了,应该分解得更多。

依赖注入可以作为类太大的早期警告,特别是因为传递所有依赖项的痛苦越来越大。

答案 3 :(得分:3)

我遇到了一个关于基于构造函数的依赖注入的类似问题,以及传递所有依赖项的复杂程度。

我过去使用的方法之一是使用服务层使用应用程序外观模式。这将有一个粗糙的API。如果此服务依赖于存储库,则它将使用私有属性的setter注入。这需要创建一个抽象工厂并将创建存储库的逻辑移动到工厂中。

详细的解释代码可以在这里找到

Best practices for IoC in complex service layer

答案 4 :(得分:2)

注入容器是您最终会后悔的捷径。

过度注入不是问题,通常是其他结构缺陷的症状,最明显的是关注点分离。 这不是一个问题,但是可以有很多来源,而使之如此难以修复的原因是,您将不得不同时处理所有这些问题(考虑一下意大利面条的整理)。

这是要注意的事情的不完整列表

域名设计不佳(汇总了…等)

关注点分离差(服务组合,命令,查询),请参阅CQRS和事件源。

或Mappers(请注意,这些事情可能会导致您遇到麻烦)

查看模型和其他DTO(切勿重复使用,并尽量将其减少到最小!!!)

答案 5 :(得分:1)

问题:

1)具有不断增加的参数列表的构造函数。

2)如果继承了类(例如:RepositoryBase),则更改构造函数 签名会导致派生类的更改。

解决方案1 ​​

IoC Container传递给构造函数

为什么

  • 不再增加参数列表
  • 构造函数的签名变得简单

为什么不

  • 使您的课程与IoC容器紧密耦合。 (当你想在其他使用不同IoC容器的项目中使用该类时,会导致问题.2。你决定更改IoC容器)
  • 使您的课程描述性降低。 (你无法真正看到类构造函数并说出它运行所需的内容。)
  • Class可以访问所有可能的服务。

解决方案2

创建一个对所有服务进行分组并将其传递给构造函数

的类
 public abstract class EFRepositoryBase 
 {
    public class Dependency
    {
        public DbContext DbContext { get; }
        public IAuditFactory AuditFactory { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
        }
    }

    protected readonly DbContext DbContext;        
    protected readonly IJobariaAuditFactory auditFactory;

    protected EFRepositoryBase(Dependency dependency)
    {
        DbContext = dependency.DbContext;
        auditFactory= dependency.JobariaAuditFactory;
    }
  }

派生类

  public class ApplicationEfRepository : EFRepositoryBase      
  {
     public new class Dependency : EFRepositoryBase.Dependency
     {
         public IConcreteDependency ConcreteDependency { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory,
            IConcreteDependency concreteDependency)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
            ConcreteDependency = concreteDependency;
        }
     }

      IConcreteDependency _concreteDependency;

      public ApplicationEfRepository(
          Dependency dependency)
          : base(dependency)
      { 
        _concreteDependency = dependency.ConcreteDependency;
      }
   }

为什么

  • 向类添加新依赖项不会影响派生类
  • Class与IoC Container
  • 无关
  • 类是描述性的(在其依赖性方面)。按照惯例,如果您想知道A取决于哪个类,那么该信息会累积在A.Dependency
  • 构造函数签名变得简单

为什么不

  • 需要创建其他类
  • 服务注册变得复杂(您需要单独注册每个X.Dependency
  • 在概念上与传递IoC Container
  • 相同
  • ..

解决方案2只是一个原始的,如果有坚实的论据反对它,那么描述性评论将不胜感激

答案 6 :(得分:1)

我阅读了整个主题两次,我认为人们是根据他们所知道的来回应,而不是根据所要求的来回应。

JP最初的问题看起来像是他先发送一个解析器,然后再发送一堆类来构造对象,但我们假设这些类/对象本身就是服务,已经可以注入。如果不是,那该怎么办?

JP,如果您希望利用DI 并且希望将注入与上下文数据混合在一起的荣耀,那么这些模式(或所谓的“反模式”)都不会专门解决这个问题。实际上可以归结为使用一个可以为您提供支持的软件包。

Container.GetSevice<MyClass>(someObject1, someObject2)

...很少支持这种格式。我相信对此类支持进行编程的困难,加上与实现相关的糟糕性能,使得它对开源开发人员没有吸引力。

但是应该这样做,因为我应该能够为MyClass创建并注册一个工厂,并且该工厂应该能够接收不仅仅被用于“服务”的数据/输入。为了传递数据。如果“反模式”是负面影响,那么强迫存在用于传递数据/模型的人工服务类型肯定是负面的(与您将类包装到容器中的感觉相同。本能同样适用)。

尽管有些框架看起来有些难看,但有些框架可能会有所帮助。例如,Ninject:

Creating an instance using Ninject with additional parameters in the constructor

这是针对.NET的,很流行,仍然没有应有的清晰程度,但是我敢肯定,无论您选择使用哪种语言,都有一些东西。

答案 7 :(得分:0)

这是我使用的方法

public class Hero
{

    [Inject]
    private IInventory Inventory { get; set; }

    [Inject]
    private IArmour Armour { get; set; }

    [Inject]
    protected IWeapon Weapon { get; set; }

    [Inject]
    private IAction Jump { get; set; }

    [Inject]
    private IInstanceProvider InstanceProvider { get; set; }


}

这是一种粗略的方法,如何在注入值后执行注入和运行构造函数。这是一个功能齐全的计划。

public class InjectAttribute : Attribute
{

}


public class TestClass
{
    [Inject]
    private SomeDependency sd { get; set; }

    public TestClass()
    {
        Console.WriteLine("ctor");
        Console.WriteLine(sd);
    }
}

public class SomeDependency
{

}


class Program
{
    static void Main(string[] args)
    {
        object tc = FormatterServices.GetUninitializedObject(typeof(TestClass));

        // Get all properties with inject tag
        List<PropertyInfo> pi = typeof(TestClass)
            .GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
            .Where(info => info.GetCustomAttributes(typeof(InjectAttribute), false).Length > 0).ToList();

        // We now happen to know there's only one dependency so we take a shortcut just for the sake of this example and just set value to it without inspecting it
        pi[0].SetValue(tc, new SomeDependency(), null);


        // Find the right constructor and Invoke it. 
        ConstructorInfo ci = typeof(TestClass).GetConstructors()[0];
        ci.Invoke(tc, null);

    }
}

我目前正在开发一个像这样工作的爱好项目 https://github.com/Jokine/ToolProject/tree/Core

答案 8 :(得分:-7)

您使用的依赖注入框架是什么?您是否尝试过使用基于setter的注射?

基于构造函数的注入的好处是对于不使用DI框架的Java程序员来说看起来很自然。你需要5件事来初始化一个类,然后你的构造函数有5个参数。缺点是你已经注意到了,当你有很多依赖时它会变得笨拙。

使用Spring,您可以使用setter传递所需的值,并且可以使用@required注释来强制注入它们。缺点是您需要将初始化代码从构造函数移动到另一个方法,然后通过使用@PostConstruct标记它来注入所有依赖项之后进行Spring调用。我不确定其他框架,但我认为他们做了类似的事情。

这两种方式都有效,这是一种偏好。