我发现我的构造函数开始是这样的:

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

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

public MyClass(Container con)

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


你是对的,如果你使用容器作为一个服务定位器,它或多或少是一个美化的静态工厂。出于很多原因,我认为这是一种反模式(请参阅我书中的节选)。

构造函数注入的一个好处是,它使违反单一责任原则的行为变得非常明显。

当这种情况发生时,是时候重构到Facade Services了。简而言之,创建一个新的、更粗粒度的接口,隐藏当前所需的一些或所有细粒度依赖项之间的交互。


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


传入参数的困难不是问题。问题是你的类做的太多了,应该再分解一些。

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


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

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

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

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


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

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

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

复杂服务层IoC的最佳实践


问题:

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

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

解决方案1

将IoC容器传递给构造函数

Why

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

为什么不

使您的类与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;
      }
   }

Why

向类添加新的依赖项不会影响派生类 类与IoC容器无关 类是描述性的(就其依赖关系而言)。按照惯例,如果您想知道A依赖于什么类,该信息将在A依赖项中积累 构造函数签名变得简单

为什么不

需要创建额外的类 服务注册变得复杂(需要分别注册每个X.Dependency) 概念上与传递IoC容器相同 . .

解决方案2只是一个原始的,如果有可靠的理由反对它,那么描述性的评论将会受到赞赏


这就是我使用的方法

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


我读了整个帖子,两遍,我认为人们是根据他们所知道的来回应,而不是根据被问到的问题。

JP最初的问题看起来像是通过发送一个解析器来构造对象,然后是一堆类,但我们假设这些类/对象本身就是服务,可以进行注入。如果不是呢?

JP,如果您希望利用DI并希望将注入与上下文数据混合在一起,那么这些模式(或假定的“反模式”)都没有专门解决这个问题。它实际上可以归结为使用一个包,它将支持你在这样的努力。

Container.GetSevice<MyClass>(someObject1, someObject2)

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

但这是应该做到的,因为我应该能够为MyClass'es创建和注册一个工厂,并且该工厂应该能够接收数据/输入,而不是仅仅为了传递数据而被推为“服务”。如果“反模式”是消极的结果,那么强制存在用于传递数据/模型的人工服务类型肯定是消极的(就像将类打包到容器中一样)。同样的本能也适用)。

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

在构造函数中使用Ninject和附加参数创建一个实例

这是针对。net的,很流行,但仍然没有达到应有的干净程度,但我相信无论你选择使用什么语言,都有一些东西。


注入容器是一个您最终会后悔的快捷方式。

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

这里是一个不完整的注意事项列表

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

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

或绘图器(要小心,这些东西会给你带来麻烦)

视图模型和其他DTO(永远不要重用它们,并尽量将它们保持在最小值!!!!)


一个方法的参数太多是一个暗示(不一定),你的方法太大/有太多的责任。

此外,在同一方法中使用的参数(可能)具有很高的内聚性——>值得考虑将它们放在一个类中——>只必须传递一个参数。