IoC.Resolve vs Constructor Injection

2019-01-13 03:46发布

问题:

I heard a lot of people saying that it is a bad practice to use IoC.Resolve(), but I never heard a good reason why (if it's all about testing than you can just mock the container, and you're done).

now the advantages of using Resolve instead of Constructor Injection is that you don't need to create classes that have 5 parameters in the constructor, and whenever you are going to create a instance of that class you're not gonna need to provide it with anything

回答1:

IoC.Resolve<> is an example of the Service Locator pattern. That pattern imposes a few restrictions that constructor injection does not:

  • Objects can have no more fine-grained context than the application domain, due to the static calls
  • Objects decide which versions of dependencies to resolve. All instances of a certain class will get the same dependency configuration.
  • The temptation to couple code to the container is high, for example instead of creating an intention-revealing factory.
  • Unit testing requires container configuration, where the classes could just be created and used otherwise. (This is especially troublesome when you want to test multiple configurations of the same class, due to the second issue above.)
  • An application's structure cannot be inferred from its public API. (Constructor parameters are a good thing. They are not a problem you should feel the need to solve.)

These limitations, in my mind, relegate the Service Locator pattern to a middle ground between big-ball-of-mud and dependency injection: useful if you must use it, but by far not the best choice.



回答2:

If you create classes that have 5 dependencies, you have problems other than IoC.Resolve.

Pulling dependencies (as opposed to having them pushed via constructor) completely misses the point of using IoC framework. You want to invert the dependencies. Not have your classes depend on IoC framework, but the other way around.

If you don't need all dependencies in certain scenarios, than perhaps you should split your class, or have some dependencies made optional by making them property dependencies.


Your classes depend on the container. They won't work unless you provide them with one. Whether it's a real one, or a fake one does not matter. They are inherently bound to the container via static dependency. This imposes additional work on you to do anything with your classes. Any time you want to use your class, you need to drag the container with them. For no benefit! Service locator is just one global bag of everything, which is against probably all tenets of object oriented programming.



回答3:

Ioc.Resolve is essentially the Service Locator pattern. It has its place, but is not ideal. Constructor injection is preferred from an architecture standpoint, as dependencies are more explicit, whereas the SL hides the dependencies within a class. This reduces testability, and makes the process more complex than it needs to be.

If I may, I would suggest you read my recent series on techniques to reduce code coupling, which covers SL, DI, and IoC.



回答4:

One advantage is that with constructor injection, all class dependencies are visible up-front.

With .Resolve you have to read the code just to figure the dependencies out.



回答5:

I have to point out it's not necessarily evil to skip constructor injection and use static injection. There are great applications of this, the most concrete example is using it in a Factory pattern implementation.

public static class ValidationFactory
{
    public static Result Validate<T>(T obj)
    {
        try
        {
            var validator = ObjectFactory.GetInstance<IValidator<T>>();
            return validator.Validate(obj);
        }
        catch (Exception ex)
        {
            var result = ex.ToResult();
            ...
            return result;
        }
    }    
}

I use this with StructureMap to handle my validation layer.

Edit: Another example I have of using the container directly is to make some of your domain objects be singletons without making them static classes and introducing all the wierdness that static classes do.

In some of my views I wire up some entities like this. Normally I would us a Enum with a Description attribute to give me 3 value choices but the 3rd one in this case needs to be a string also and not an int so I created an interface with those 3 properties and inherit all of the domain objects from it. Then I have my container scan my assembly and register all of them automatically then to pull them out I just have

SomeObject ISomeView.GetMyObject
{
    get { return new SomeObject { EmpoweredEnumType = 
            ObjectFactory.GetNamedInstance<IEmpEnum>("TheObjectName");
        }
}


回答6:

Since the question is debatable I'm not going to say "use this or that"

Looks like using Service Locator is not a bad thing if you are OK to depend on it (and we usually do anyway on some DI framework). With DI we can easily change the framework, with Service Locator we create coupling to SL part of the framework.

In regards to Bryan Watts answer when you read later on in Service Locator vs Dependency Injection

...With [constructor] injection there is no explicit request, the service appears in the application class - hence the inversion of control.

Inversion of control is a common feature of frameworks, but it's something that comes at a price. It tends to be hard to understand and leads to problems when you are trying to debug. So on the whole I prefer to avoid it unless I need it. This isn't to say it's a bad thing, just that I think it needs to justify itself over the more straightforward alternative.

And then if you read later is another justification to actually use constructor injection (Inversion of control).

My opinion is that in small projects it's OK to use SL, as the main thing is not to create coupling between our custom developed classes.

Using StructureMap exmaple this should be acceptable:

public class Demo
{
    private ISomething something = ObjectFactory.GetInstance<ISomething>();
    private IFoo foo = ObjectFactory.GetInstance<IFoo>();
}

Yes the code is depending on SM Frx, but how often do you change DI Frx anyway?

And for unit testing a mock can be setup

public class SomeTestClass
{
    public SomeTest()
    {
        ObjectFactory.Inject<ISomething>(SomeMockGoesHere);
        ObjectFactory.Inject<IFoo>(SomeMockGoesHere);

        Demo demo = new Demo() //will use mocks now
    }
}

the advantages of using Resolve instead of Constructor Injection is that you don't need to create classes that have 5 parameters in the constructor

but you may end up with making more "plumbing" for unit testing.



回答7:

I would say that's a crazy amount of parameters to be injected.

Strive for one parameter, maybe 2 at most, which is in almost all scenarios possible, and of course should be an interface. Any more than that, and I smell a rat (design flaw).



回答8:

you don't need to create classes that have 5 parameters in the constructor, and whenever you are going to create a instance of that class you're not gonna need to provide it with anything

A couple points:

  • If you're using a DI container, it should be creating the instances of that class for you. In that case, you don't have to provide it with anything yourself for production use. For testing, you'll have to provide it with the dependencies through the constructor, but:
  • If the class depends on (uses in some way) those 5 things you're talking about providing to the constructor (and you wouldn't be providing them if it didn't) you WILL have to provide it with them one way or the other. When testing (which is the only time you should have to call the constructor yourself) you can either pass those things to it through the constructor or you can write code to set up the container and add those 5 things to it so that when IoC.Resolve() gets called, they're actually there. Passing them to the constructor is a lot easier, I'd say.

Dependencies will exist even if you don't make that apparent through the class's API (it's constructor in this case). However, it'll be a lot harder to understand and test classes that try to hide their dependencies like this.