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

发布于 2024-08-24 19:56:37 字数 292 浏览 9 评论 0原文

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

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

参数列表不断增加。既然“Container”是我的依赖注入容器,为什么我不能

public MyClass(Container con)

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

I find that my constructors are starting to look like this:

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

with ever increasing parameter list. Since "Container" is my dependency injection container, why can't I just do this:

public MyClass(Container con)

for every class? What are the downsides? If I do this, it feels like I'm using a glorified static. Please share your thoughts on IoC and Dependency Injection madness.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

倒带 2024-08-31 19:56:37

你是对的,如果你使用容器作为服务定位器,它或多或少是一个美化的静态工厂。由于多种原因我认为这是一种反模式(另请参阅< a href="https://freecontent.manning.com/the-service-locator-anti-pattern/" rel="noreferrer">此摘录来自我的书)。

构造函数注入的一大好处是,它使单一责任原则的违反变得显而易见。

当这种情况发生时,就需要重构外观服务了。简而言之,创建一个新的、更粗粒度的界面,隐藏您当前需要的部分或全部细粒度依赖项之间的交互。

You are right that if you use the container as a Service Locator, it's more or less a glorified static factory. For lots of reasons I consider this an anti-pattern (also see this excerpt from my book).

One of the wonderful benefits of Constructor Injection is that it makes violations of the Single Responsibility Principle glaringly obvious.

When that happens, it's time to refactor to Facade Services. In short, create a new, more coarse-grained interface that hides the interaction between some or all of the fine-grained dependencies you currently require.

醉殇 2024-08-31 19:56:37

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

I don't think your class constructors should have a reference to your IOC container period. This represents an unnecessary dependency between your class and the container (the type of dependency IOC is trying to avoid!).

空城之時有危險 2024-08-31 19:56:37

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

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

The difficulty of passing in the parameters is not the problem. The problem is that your class is doing too much, and should be broken down more.

Dependency Injection can act as an early warning for classes getting too big, specifically because of the increasing pain of passing in all of the dependencies.

妥活 2024-08-31 19:56:37

问题:

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

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

解决方案1

IoC容器传递给构造函数

为什么

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

为什么不

  • 让你类与 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 容器无关
  • 类是描述性的(就其依赖性而言)。按照惯例,如果你想知道 A 依赖于什么类,该信息会累积在 A.Dependency
  • 构造函数签名中变得简单

为什么不

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

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

Problem :

1) Constructor with ever increasing parameter list.

2) If class is inherited (Ex: RepositoryBase) then changing constructor
signature causes change in the derived classes.

Solution 1

Pass IoC Container to constructor

Why

  • No more ever increasing parameter list
  • Constructor's signature becomes simple

Why not

  • Makes you class tightly coupled to IoC container. (That causes problems when 1. you want to use that class in other projects where you use different IoC container. 2. you decide to change IoC container)
  • Makes you class less descriptive. (You can't really look at class constructor and say what it needs for functioning.)
  • Class can access potentially all service.

Solution 2

Create a class which groups all service and pass it to constructor

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

Derived class

  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

  • Adding new dependency to class does not affect derived classes
  • Class is agnostic of IoC Container
  • Class is descriptive (in aspect of its dependencies). By convention, if you want to know what class A Depends on, that information is accumulated in A.Dependency
  • Constructor signature becomes simple

Why not

  • need to create additional class
  • service registration becomes complex (You need to register every X.Dependency separately)
  • Conceptually same as passing IoC Container
  • ..

Solution 2 is just a raw though, if there is solid argument against it, then descriptive comment would be appreciated

娇纵 2024-08-31 19:56:37

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

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

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

IoC 的最佳实践在复杂服务层

I came across a similar question about constructor based dependency Injection and how complex it was getting to pass in all the dependencies.

One of the approach, I have used in past is to use the application facade pattern using a service layer. This would have a coarse API. If this service depends on repositories, It would use a setter injection of the private properties. This requires creating an abstract factory and moving the logic of creating the repositories into a factory.

Detailed code with explanation can be found here

Best practices for IoC in complex service layer

尝蛊 2024-08-31 19:56:37

我读了整篇文章两遍,我认为人们是根据他们所知道的而不是根据所问的内容来回应的。

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

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

Container.GetSevice<MyClass>(someObject1, someObject2)

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

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

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

创建实例在构造函数中使用带有附加参数的 Ninject

这适用于 .NET,很流行,但仍然没有达到应有的干净程度,但我确信无论您选择使用哪种语言,都有一些东西。

I read this whole thread, twice, and I think people are responding by what they know, not by what is asked.

JP's original question looks like he's constructing objects by sending a resolver, and then a bunch of classes, but we're assuming that those classes/objects are themselves services, ripe for injection. What if they are not?

JP, if you're looking to leverage DI and desire the glory of mixing injection with contextual data, none of these patterns (or supposed "anti-patterns") specifically address that. It actually boils down to using a package which will support you in such an endeavor.

Container.GetSevice<MyClass>(someObject1, someObject2)

... this format is rarely supported. I believe the difficulty of programming such support, added to the miserable performance that would be associated with the implementation, makes it unattractive for opensource developers.

But it should be done, because I should be able to create and register a factory for MyClass'es, and that factory should be able to receive data/input that aren't pushed into being a "service" just for the sake of passing data. If "anti-pattern" is about negative consequences, then forcing the existence of artificial service types for passing data/models is certainly negative (on par with your feeling about wrapping up your classes into a container. Same instinct applies).

There are framework that may help, though, even if they look a bit ugly. For example, Ninject:

Creating an instance using Ninject with additional parameters in the constructor

That's for .NET, is popular, and is still nowhere as clean as it should be, but I'm sure there's something in whatever language you choose to employ.

虐人心 2024-08-31 19:56:37

这是我使用的方法

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

This is the approach I use

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


}

Here is a crude approach how to perform the injections and run constructor after injecting values. This is fully functional program.

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

    }
}

I am currently working on a hobby project which works like this
https://github.com/Jokine/ToolProject/tree/Core

岁吢 2024-08-31 19:56:37

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

过度注入不是问题,它通常是其他结构缺陷的症状,最明显的是关注点分离。
这不是一个问题,但可能有很多来源,而导致这个问题如此难以解决的原因是你必须处理所有这些问题,有时甚至是同时处理(想想解开意大利面条)。

以下是一个不完整的列表,列出了需要注意的

不良域设计(聚合根……等)

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

或者映射器(小心,这些东西可能会给你带来麻烦)

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

Injecting the container is a shortcut that you will ultimately regret.

Over injection is not the problem, it is usually a symptom of other structural flaws, most notably separation of concerns.
This is not one problem but can have many sources and what makes this so difficult to fix is that you are going to have to deal with all of them, sometimes at the same time (think of untangling spaghetti).

Here is an incomplete list of the things to look out for

Poor Domain Design (Aggregate root’s …. etc)

Poor separation of concerns (Service composition, Commands, queries) See CQRS and Event Sourcing.

OR Mappers (be careful, these things can lead you into trouble)

View Models and other DTO’s (Never reuse one, and try to keep them to a minimal !!!!)

绾颜 2024-08-31 19:56:37

方法的参数过多暗示(不一定)您的方法太大/有太多责任。

此外,在同一方法中使用的参数(可能)具有高内聚性->值得考虑将它们归为一类 ->仅需传递一个参数。

Too many parameters for a method is a hint (not necessarily) that your method is too big / has too many responsibilites.

Furthermore, parameters that are used in the same method (may) have a high cohesion -> it is worth considering putting them into one class -> only one parameter must be passed.

柒七 2024-08-31 19:56:37

您使用什么依赖注入框架?您是否尝试过使用基于设置器的注入来代替?

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

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

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

What dependency injection framework are you using? Have you tried using setter based injection instead?

The benefit for constructor based injection is that it looks natural for Java programmers who don't use DI frameworks. You need 5 things to initialize a class then you have 5 arguments for your constructor. The downside is what you have noticed, it gets unwieldy when you have lots of dependencies.

With Spring you could pass the required values with setters instead and you could use @required annotations to enforce that they are injected. The downside is that you need to move the initialization code from the constructor to another method and have Spring call that after all the dependencies are injected by marking it with @PostConstruct. I'm not sure about other frameworks but I assume they do something similar.

Both ways work, its a matter of preference.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文