首页 文章

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

提问于
浏览
260

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

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

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

public MyClass(Container con)

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

9 回答

  • 3

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

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

    当发生这种情况时,是时候refactor to Facade Services了 . 简而言之,创建一个新的更多 coarse-grained 接口,隐藏您当前所需的部分或全部细粒度依赖项之间的交互 .

  • 359

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

  • 23

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

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

  • 55

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

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

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

    Best practices for IoC in complex service layer

  • -7

    问题:

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

    2)如果继承了类(例如: RepositoryBase ),那么更改构造函数签名会导致派生类发生更改 .

    解决方案1

    IoC Container 传递给构造函数

    为什么

    • 不再增加参数列表

    • 构造函数的签名变得简单

    为什么不

    • 使您的课程与IoC容器紧密耦合 . (这会导致问题1.您希望在使用不同IoC容器的其他项目中使用该类.2 . 您决定更改IoC容器)

    • 使您的课程描述性降低 . (您无法真正查看类构造函数并说出它运行所需的内容 . )

    • 类可以访问可能的所有服务 .

    解决方案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;
          }
       }
    

    为什么

    • 向类添加新依赖项不会影响派生类

    • 类与IoC Container无关

    • 类是描述性的(在其依赖性方面) . 按照惯例,如果您想知道 A 取决于哪个类,那么该信息将在 A.Dependency 中累积

    • 构造函数签名变得简单

    为什么不

    • 需要创建额外的类

    • 服务注册变得复杂(您需要分别注册每个 X.Dependency

    • 在概念上与传递 IoC Container 相同

    • ..

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

  • 1

    这是我使用的方法

    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

  • 0

    我读过这整个帖子,两次,我认为人们正在回答他们所知道的,而不是被问到的 .

    JP的原始问题看起来像是通过发送一个解析器来构造对象,然后是一堆类,但我们假设这些类/对象本身就是服务,注入成熟 . 如果他们不是?

    JP,如果你想利用DI并希望将注入与上下文数据混合的荣耀,这些模式中没有一个(或假设"anti-patterns")专门解决这个问题 . 它实际上归结为使用一个能够在这样的努力中支持你的包 .

    Container.GetSevice<MyClass>(someObject1, someObject2)
    

    ...很少支持这种格式 . 我认为编程这种支持的难度增加了与实现相关的可悲性能,这使得它对开源开发人员来说没有吸引力 .

    但它应该完成,因为我应该能够为MyClass'es创建和注册工厂,并且该工厂应该能够接收未被推送为“服务”的数据/输入,仅仅是为了传递数据 . 如果“反模式”是关于消极后果,则强制存在人工服务类型以传递数据/模型肯定是消极的(与你将你的课程包装成容器的感觉相同 . 同样的本能适用) .

    但是,有一些框架可能有所帮助,即使它们看起来有点难看 . 例如,Ninject:

    Creating an instance using Ninject with additional parameters in the constructor

    这是针对.NET的,很流行,并且仍然没有它应该的那么干净,但我确信你选择使用的语言是什么 .

  • 0

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

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

    以下是需要注意的事项的不完整列表

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

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

    或者Mappers(小心,这些东西会让你陷入困境)

    查看模型和其他DTO(永远不要重用一个,并尽量保持最小!!!!)

  • 0

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

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

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

    两种方式都有效,这是一个偏好问题 .

相关问题