代码之家  ›  专栏  ›  技术社区  ›  JP Richardson

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

  •  267
  • JP Richardson  · 技术社区  · 16 年前

    我发现我的构造器开始像这样:

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

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

    public MyClass(Container con)
    

    9 回复  |  直到 13 年前
        1
  •  443
  •   Steven    5 年前

    如果将容器用作服务定位器,那么它或多或少是一个美化的静态工厂,这是对的。有很多原因 I consider this an anti-pattern this excerpt 从我的书)。

    构造函数注入的一个好处是它违反了 Single Responsibility Principle 非常明显。

    当这种情况发生时,是时候 refactor to Facade Services 粗粒 接口,它隐藏当前所需的某些或所有细粒度依赖项之间的交互。

        2
  •  68
  •   derivation    16 年前

        3
  •  28
  •   kyoryu    16 年前

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

        4
  •  4
  •   Community Mohan Dere    9 年前

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

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

    Best practices for IoC in complex service layer

        5
  •  3
  •   tchelidze    9 年前

    问题:

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

    2) 如果类被继承(例如: RepositoryBase 签名导致派生类中的更改。

    解决方案1

    IoC Container 至建造师

    为什么?

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

    为什么不呢?

    • 使类与IoC容器紧密耦合。 (当1.您想在使用不同IoC容器的其他项目中使用该类时,会出现问题。2.您决定更改IoC容器)
    • (您不能真正地查看类构造函数并说出它运行所需的内容。)
    • 类可以访问所有服务。

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

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

    为什么?

    • 向类添加新的依赖项不会影响派生类
    • A A.Dependency
    • 构造函数签名变得简单

    为什么不呢?

    • 需要创建其他类
    • 服务注册变得复杂 (您需要注册 X.Dependency
    • 概念上与传递相同 IoC容器

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

        6
  •  3
  •   Craig Brunetti    8 年前

    我读了两遍这篇文章,我认为人们的反应是他们所知道的,而不是所问的。

    JP,如果你想利用DI 和 想要把注入和上下文数据混合在一起的荣耀,这些模式(或者假定的“反模式”)都没有专门解决这个问题。它实际上可以归结为使用一个软件包来支持你这样的努力。

    Container.GetSevice<MyClass>(someObject1, someObject2)
    

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

    但是应该这样做,因为我应该能够为我的类创建和注册一个工厂,并且该工厂应该能够接收数据/输入,而不是仅仅为了传递数据而被推到“服务”中。如果“反模式”是负面的后果,那么强制人工服务类型的存在对于传递数据/模型肯定是负面的(与你把你的类打包成容器的感觉一样)。同样的直觉也适用)。

    不过,有些框架可能会有所帮助,即使它们看起来有点难看。例如,Ninject:

    Creating an instance using Ninject with additional parameters in the constructor

    这是针对.NET的,它很受欢迎,而且还没有达到它应有的干净程度,但我相信无论您选择使用哪种语言,都会有一些东西。

        7
  •  3
  •   Marius    8 年前

    注入容器是一条你最终会后悔的捷径。

    过度注入不是问题所在,它通常是其他结构性缺陷的症状,最明显的是关注点分离。 这不是一个问题,但可以有许多来源,而使这如此难以解决的是,你将不得不处理所有的问题,有时在同一时间(想想解开意大利面条)。

    这是一份不完整的要注意的事项清单

    糟糕的域设计(聚合根)。等)

    关注点分离不良(服务组合、命令、查询)请参见CQRS和事件源。

    查看模型和其他DTO(永远不要重用一个,并尽量将它们保持在最低限度!!!!)

        8
  •  2
  •   Ronijo    9 年前

    这就是我使用的方法

    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

        9
  •  -8
  •   David W Crook    16 年前

    您使用的依赖注入框架是什么?你试过使用基于setter的注入吗?

    使用Spring,您可以用setter来传递所需的值,并且可以使用@required注释来强制它们被注入。缺点是,您需要将初始化代码从构造函数移到另一个方法,并在所有依赖项都被注入后,通过用@PostConstruct标记它来调用Spring。我不确定其他框架,但我假设它们也有类似的功能。

    两种方法都有效,这是一个偏好的问题。