How to avoid Dependency Injection constructor madness?

前端 未结 9 1821
别跟我提以往
别跟我提以往 2020-11-22 03:59

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

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

with ever

相关标签:
9条回答
  • 2020-11-22 04:43

    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

    0 讨论(0)
  • 2020-11-22 04:47

    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

    0 讨论(0)
  • 2020-11-22 04:51

    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.

    0 讨论(0)
提交回复
热议问题