51

What is the purpose of IOC Containers? The combined reasons for it can be simplified to the following:

When using OOP/SOLID Development principles, Dependency Injection gets messy. Either you have the top-level entry points managing dependencies for multiple levels below themselves and passing dependencies recursively through construction, or you have somewhat duplicate code in factory/builder patterns and interfaces that build dependencies as you need them.

There is no OOP/SOLID way to perform this AND have super pretty code.

If that previous statement is true, then how do IOC Containers do it? As far as I know, they aren’t employing some unknown technique that can't be done with manual D.I. So the only explination is that IOC Containers break OOP/SOLID Principles by using static objects private accessors.

Do IOC Containers break the following principles behind the scenes? This is the real question since I have a good understanding, but have a feeling somebody else has a better understanding:

  1. Scope control. Scope is the reason for nearly every decision I make on my code design. Block, Local, Module, Static/Global. Scope should be very explicit, as much at block-level and as few at Static as possible. You should see declarations, instantiations, and lifecycle endings. I trust the language and GC to manage scope as long as I’m explicit with it. In my research I’ve found that IOC Containers set up most or all dependencies as Static and control them through some AOP implementation behind the scenes. So nothing is transparent.
  2. Encapsulation. What is the purpose of encapsulation? Why should we keep private members so? For practical reasons it is so implementors of our API can’t break the implementation by changing the state (which should be managed by the owner class). But also, for security reasons, it’s so injections can’t occur that overtake our member states and bypass validation and class control. So anything (Mocking frameworks or IOC frameworks) that somehow injects code before compile time to allow external access to private members is pretty huge.
  3. Single Responsibility Principle. On the surface, IOC Containers seem to make things cleaner. But imagine how you would accomplish the same thing without the helper frameworks. You would have constructors with a dozen or so dependencies being passed in. That doesn’t mean cover it up with IOC Containers, it is a good thing! It’s a sign to re-factor your code and follow SRP.
  4. Open/Closed. Just like SRP isn’t Class-Only (I apply SRP down to single-responsibility lines, let alone methods). Open/Closed is not just a high level theory to not alter the code of a class. It’s a practice of understanding the configuration of your program and having control over what gets altered and what gets extended. IOC Containers can change the way your classes work altogether partially because:

    • a. The main code isn’t making the determination of switching out dependencies, the framework configuration is.

    • b. The scope could be altered at a time that isn’t controlled by the calling members, it is instead determined externally by a static framework.

So the configuration of the class isn’t really closed is it, it alters itself based on the configuration of a third party tool.

The reason this is a question is because I am not necessarily a master of all IOC Containers. And while the idea of an IOC Container is nice, they appear to just be a façade that covers up poor implementation. But in order to accomplish external Scope control, Private member access, and Lazy loading, a lot of non-trivial questionable things have to go on. AOP is great, but the way it is accomplished through IOC Containers is also questionable.

I can trust C# and the .NET GC to do what I expect it to. But I can’t put that same trust in a third party tool that is altering my compiled code to perform workarounds to things I wouldn’t be able to do manually.

E.G.: Entity Framework and other ORMs create strongly Typed objects and map them to database entities, as well as provide boiler-plate basic functionality to perform CRUD. Anybody could build their own ORM and continue to follow OOP/SOLID Principles manually. But those frameworks help us so we don’t have to re-invent the wheel every time. Whereas IOC Containers, it seems, help us purposely work around OOP/SOLID Principles and cover it up.

  • 13
    +1 Correlation between IOC and Explicitness of code is exactly the thing I have problems with. Manual DI can easily become a chore, but it at least puts selfcontained explicit program flow in one place - "You get what you see, you see what you get". While IOC containers' bindings and declarations can easily become a hidden parallel program on its own. – Eugene Podskal Sep 18 '14 at 19:02
  • 6
    How is this even a question? – Stephen Sep 19 '14 at 1:24
  • 8
    This seems more like a blog post than a question. – Bart van Ingen Schenau Sep 19 '14 at 6:43
  • 4
    I have an internal category for questions, "too argumentative", which ofc isn't a formal close reason but that I downvote and sometimes even vote to close "not a real question". It has two aspects, though, and this question only meets the first. (1) Question states a thesis and asks whether it is correct, (2) questioner responds to answers that disagree, defending the initial position. Matthew's answer contradicts some of what the question states, so unless the questioner crawls all over that I personally categorise this as a good-faith question, albeit of the form "I'm right, aren't I?" – Steve Jessop Sep 19 '14 at 8:10
  • 3
    @BenAaronson Thank you Ben. I'm trying to communicate what I have learned through my research and get answers about the nature of IOC Containers. As a result of my research I have found these principles to be broken. The best answer to my question would confirm or deny that IOC Containers can somehow do things we can't do manually without breaking OOP/SOLID Principles. Because of your great comments, I have refactored my question with more examples. – Suamere Sep 19 '14 at 14:14
35

I'll go through your points numerically, but first, there's something you should be very careful of: don't conflate how a consumer uses a library with how the library is implemented. Good examples of this are Entity Framework (which you yourself cite as a good library) and ASP.NET's MVC. Both of these do an awful lot under the hood with, for example, reflection, which would absolutely not be considered good design if you spread it through day-to-day code.

These libraries are absolutely not "transparent" in how they work, or what they're doing behind-the-scenes. But that's not a detriment, because they support good programming principles in their consumers. So whenever you talk about a library like this, remember that as a consumer of a library, it's not your job to worry about its implementation or maintenance. You should only worry about how it helps or hinders the code you write which uses the library. Don't conflate these concepts!

So, to go through by point:

  1. Immediately we reach what I can only assume is an example of the above. You say that IOC containers set up most dependencies as static. Well, perhaps some implementation detail of how they work includes static storage (though given that they tend to have an instance of an object like Ninject's IKernel as their core storage, I doubt even that). But this is not your concern!

    Actually, an IoC container is just as explicit with scope as poor man's dependency injection. (I'm going to keep comparing IoC containers to poor man's DI because comparing them to no DI at all would be unfair and confusing. And, to be clear, I'm not using "poor man's DI" as a pejorative.)

    In poor man's DI, you'd instantiate a dependency manually, then inject it into the class that needs it. At the point where you construct the dependency, you'd choose what to do with it- store it in a locally scoped variable, a class variable, a static variable, don't store it at all, whatever. You could pass the same instance into lots of classes, or you could create a new one for each class. Whatever. The point is, to see which is happening, you look to the point- probably near the application root- where the dependency is created.

    Now what about an IoC container? Well, you do exactly the same! Again, going by Ninject terminology, you look at where the binding is set up, and find whether it says something like InTransientScope, InSingletonScope, or whatever. If anything this is potentially clearer, because you have code right there declaring its scope, rather than having to look through a method to track what happens to some object (an object may be scoped to a block, but is it used multiple times in that block or just once, for example). So maybe you're repelled by the idea of having to use a feature on the IoC container rather than a raw language feature to dictate scope, but as long as you trust your IoC library- which you should!- there's no real disadvantage to it.

  2. I still don't really know what you're talking about here. Do IoC containers look at private properties as part of their internal implementation? I don't know why they would, but again, if they do, it's not your concern how a library you're using is implemented.

    Or, maybe, they expose a capability like injecting into private setters? Honestly I've never encountered this, and I'm dubious about whether this really is a common feature. But even if it's there, it's a simple case of a tool that can be misused. Remember, even without an IoC container, it's but a few lines of Reflection code to access and modify a private property. It's something you should almost never do, but that doesn't mean .NET is bad for exposing the capability. If somebody so obviously and wildly misuses a tool, it's the person's fault, not the tool's.

  3. The ultimate point here is similar to 2. The vast majority of the time, IoC containers DO use the constructor! Setter injection is offered for very specific circumstances where, for particular reasons, constructor injection can't be used. Anybody who uses setter injection all the time to cover up how many dependencies are being passed in is massively absuing the tool. That's NOT the tool's fault, it's theirs.

    Now, if this was a really easy mistake to innocently make, and one that IoC containers encourage, then okay, maybe you'd have a point. It'd be like making every member of my class public then blaming other people when they modify things they shouldn't, right? But anybody who uses setter injection to cover up violations of SRP is either willfully ignoring or completely ignorant of basic design principles. It's unreasonable to lay the blame for this at the IoC container.

    That's especially true because it's also something you can do just as easily with poor man's DI:

    var myObject 
       = new MyTerriblyLargeObject { DependencyA = new Thing(), DependencyB = new Widget(), Dependency C = new Repository(), ... };
    

    So really, this worry seems completely orthogonal to whether or not an IoC container is used.

  4. Changing how your classes work together is not a violation of OCP. If it was, then all dependency inversion would be encouraging a violation of OCP. If this was the case, they wouldn't both be in the same SOLID acronym!

    Furthermore, neither points a) nor b) come close to having anything to do with OCP. I don't even know how to answer these with respect to OCP.

    The only thing I can guess is that you think OCP is something to do with behaviour not being altered at runtime, or about where in the code the lifecycle of dependencies is controlled from. It's not. OCP is about not having to modify existing code when requirements are added or change. It's all about writing code, not about how code you've already written is glued together (though, of course, loose coupling is an important part of achieving OCP).

And one final thing you say:

But I can’t put that same trust in a third party tool that is altering my compiled code to perform workarounds to things I wouldn’t be able to do manually.

Yes, you can. There is absolutely no reason for you to think that these tools- relied on by vast numbers of projects- are any more buggy or prone to unexpected behaviour than any other third-party library.

Addendum

I just noticed your intro paragraphs could use some addressing too. You sardonically say that IoC containers "aren’t employing some secret technique we’ve never heard of" to avoid messy, duplication-prone code to build a dependency graph. And you're quite right, what they're doing is actually addressing these things with the same basic techniques as we programmers always do.

Let me talk you through a hypothetical scenario. You, as a programmer, put together a large application, and at the entry point, where you're constructing your object graph, you notice you have quite messy code. There are quite a few classes that are used again and again, and every time you build one of those you have to build the whole chain of dependencies under them again. Plus you find you don't have any expressive way of declaring or controlling the lifecycle of dependencies, except with custom code for each one. Your code is unstructured and full of repetition. This is the messiness you talk about in your intro paragraph.

So first, you start to refactor a bit- where some repeated code is structured enough you pull it out into helper methods, and so on. But then you start to think- is this a problem that I could perhaps tackle in a general sense, one that isn't specific to this particular project but could help you in all your future projects?

So you sit down, and think about it, and decide that there should be a class that can resolve dependencies. And you sketch out what public methods it would need:

void Bind(Type interfaceType, Type concreteType, bool singleton);
T Resolve<T>();

Bind says "where you see a constructor argument of type interfaceType, pass in an instance of concreteType". The additional singleton parameter says whether to use the same instance of concreteType each time, or always make a new one.

Resolve will simply try to construct T with any constructor it can find whose arguments are all of types which have previously been bound. It can also call itself recursively to resolve the dependencies all the way down. If it can't resolve an instance because not everything has been bound, it throws an exception.

You can try implementing this yourself, and you'll find you need a bit of reflection, and some caching for the bindings where singleton is true, but certainly nothing drastic or horrifying. And once you're done- voila, you have the core of your very own IoC container! Is it really that scary? The only real difference between this and Ninject or StructureMap or Castle Windsor or whatever one you prefer is that those have a lot more functionality to cover the (many!) use cases where this basic version wouldn't be sufficient. But at its heart, what you have there is the essence of an IoC container.

  • Thanks Ben. I've honestly only worked with IOC Containers for about a year, and though I don't do it, tutorials and peers around me really focus on property injection (including private). It's crazy, and it brings up all the points I make. But even if others are in this situation, I think the best thing they can do is learn more about how IOC Containers should work to follow principles, and point out these principle-breakers in code reviews. However... (continued) – Suamere Sep 20 '14 at 13:28
  • My biggest concern isn't necessarily listed in OOP/SOLID Principles, and that is explicitness of scope. I still we are throwing Block, Local, Module, and Global level scope understanding out the window for a keyword. The whole idea of using blocks, and declarations determining when something falls out of scope or is disposed is huge for me, and I think that was the scariest part of IOC Containers for me, to replace that very basic piece of development with an extension keyword. – Suamere Sep 20 '14 at 13:32
  • So I marked your response as the answer because it really does address the heart of this. And the way I read it is that you just have to trust what IOC Containers do under the covers because everybody else does. And as far as the openings to possible misuse and the elegant way it covers it up, that's for a shop and code reviews to worry about and they should do their due diligence to follow good development principles. – Suamere Sep 20 '14 at 13:34
  • 2
    In regards to 4, Dependency Injection is not in the SOLID acronym. 'D' = 'Dependency Inversion Principle', which is a totally unrelated concept. – astreltsov May 8 '15 at 15:29
  • @astr Good point. I've replaced "injection" with "inversion" as I think that's what I meant to write in the first place. That's still a bit of an over-simplification as dependency inversion doesn't necessarily imply multiple concrete implementations, but I think the meaning is clear enough. It would be dangerous to depend on a polymorphic abstraction if switching out implementations was an OCP violation. – Ben Aaronson May 8 '15 at 15:53
27

Don't IOC containers break a lot of these principles?

In theory? No. IoC containers are just a tool. You can have objects that have a single responsibility, well separated interfaces, can't have their behavior modified once written, etc.

In practice? Absolutely.

I mean, I've heard not a few programmers say that they use IoC containers specifically to allow you to use some configuration to change the behavior of the system - violating the Open/Closed Principle. There used to be jokes made about coding in XML because 90% of their job was fixing Spring configuration. And you're certainly correct that hiding the dependencies (that admittedly, not all IoC containers do) is a quick and easy way to make highly coupled and very fragile code.

Let's look at it a different way. Let's consider a very basic class that has a single, basic dependency. It depends on an Action, a delegate or functor that takes no parameters and returns void. What is this class' responsibility? You can't tell. I could name that dependency something clear like CleanUpAction which makes you think it does what you want it do. As soon as IoC comes into play it becomes anything. Anyone can go into the configuration and modify your software to do pretty much arbitrary things.

"How is that different from passing in an Action to the constructor?" you ask. It's different because you can't change what's being passed into the constructor once the software is deployed (or at runtime!). It's different because your unit tests (or your consumers' unit tests) have covered the scenarios where the delegate is passed into the constructor.

"But that's a bogus example! My stuff doesn't allow changing behavior!" you exclaim. Sorry to tell you, but it does. Unless the interface you're injecting is just data. And your interface isn't just data, because if it were just data you'd be building a simple object and using that. As soon as you have a virtual method in your injection point, the contract of your class using IoC is "I can do anything".

"How is that any different from using scripting to drive things? That's perfectly fine to do." some of you ask. It isn't different. From a program design perspective, there's no difference. You're calling out of your program to run arbitrary code. And sure, that's been done in games for ages. It's done in tons of systems administration tools. Is it OOP? Not really.

There's no excuse for their current ubiquity. IoC containers have one solid purpose - to glue together your dependencies when you have so many combinations of them, or they shift so rapidly that it is impractical to make those dependencies explicit. So very few businesses actually fit that scenario though.

  • 3
    Right. And a lot of shops will claim their product is so complex that they fall into that category, when they really don't. As is the truth with many scenarios. – Suamere Sep 18 '14 at 19:39
  • 12
    But IoC is exact opposite to what you said. It employs Open/Closeness of of the program. If you can change whole program's behavior without having to change code itself. – Euphoric Sep 19 '14 at 7:20
  • 6
    @Euphoric - open for extension, closed for modification. If you can change the whole program's behavior, it's not closed for modification, is it? IoC configuration is still your software, it is still code used by your classes to do work - even if it's in XML rather than Java or C# or whatever. – Telastyn Sep 19 '14 at 11:33
  • 4
    @Telastyn The "closed for modification" means that it should not be necessary to change(modify) the code, to change the behavior of a whole. With any external configuration, you can just point it to a new dll and have the behavior of a whole change. – Euphoric Sep 19 '14 at 13:16
  • 12
    @Telastyn That's not what the open/closed principle says. Otherwise a method that takes parameters would violate OCP because I could "modify" its behaviour by passing it different parameters. Likewise under your version of OCP, it would be in direct conflict with DI, which would make it rather strange that both appear in the SOLID acronym. – Ben Aaronson Sep 19 '14 at 13:18
14

Does using an IoC container necessarily violate OOP/SOLID principles? No.

Can you use IoC containers in such a way that they violate OOP/SOLID principles? Yes.

IoC containers are just frameworks for managing dependencies in your application.

You stated Lazy injections, property setters, and a few other features I haven't yet felt the need to use, which I agree with can give you a less than optimal design. However, the usages of these specific features are due to limitations in the technology you're implementing IoC containers in.

For example, ASP.NET WebForms, you are required to use property injection as it's not possible to instantiate a Page. This is understandable because WebForms was never designed with strict OOP paradigms in mind.

ASP.NET MVC on the other hand, it's entirely possible to use an IoC container using the very OOP/SOLID friendly constructor injection.

In this scenario (using constructor injection), I believe it promotes SOLID principles for the following reasons:

  • Single Responsibility Principle - Having many smaller classes allows these classes to be very specific and have one reason for existing. Using an IoC container makes organizing them much easier.

  • Open/Closed - This is where using an IoC container really shines, you can extend the functionality of a class by way of injecting different dependencies. Having dependencies that you inject lets you modify the behavior of that class. A good example is changing the class that you're injecting by writing a decorator to say add caching or logging. You can completely change implementations of your dependencies if written in an appropriate level of abstraction.

  • Liskov Substitution Principle - Not really relevant

  • Interface Segregation Principle - You can assign multiple interfaces to a single concrete time if you want, any IoC container worth a grain of salt will easily do this.

  • Dependency Inversion - IoC containers let you do dependency inversion easily.

You define a bunch of dependencies and declare the relationships and scopes, and bing bang boom: you magically have some addon that alters your code or I.L. at some point and makes things work. That isn't explicit or transparent.

It is both explicit and transparent, you need to tell your IoC container how to wire up dependencies and how to manage object lifetimes. This can be either by convention, by configuration files, or by code written in your system's main applications.

I agree that when I write or read code using IOC containers, it gets rid of some slightly inelegant code.

This is the whole reason for OOP/SOLID, to make systems manageable. Using an IoC container is inline with that goal.

The writer of that class says, "If we didn't use an IOC Container, the Constructor would have a dozen parameters!!! That's horrible!"

A class with 12 dependencies is probably an SRP violation no matter what context you're using it in.

  • 2
    Excellent answer. Additionally, I think all the major IOC containers also support AOP, which can help a lot with OCP. For cross-cutting concerns, AOP is usually a more OCP-friendly route than a Decorator. – Ben Aaronson Sep 19 '14 at 4:51
  • 2
    @BenAaronson Considering that AOP injects code into a class, I wouldn't call it more OCP-friendly. You're forcefully opening and changing a class at compile/runtime. – Doval Sep 19 '14 at 14:03
  • 1
    @Doval I don't see how it's injecting code into a class. Note I'm talking about Castle DynamicProxy style, where you have an interceptor, rather than the IL-weaving style. But an interceptor is essentially just a decorator that uses some reflection so that it doesn't have to couple itself to a particular interface. – Ben Aaronson Sep 19 '14 at 14:07
  • "Dependency Inversion - IoC containers let you do dependency inversion easily" - they don't, they are simply leveraging something that is beneficial whether you have IoC or not. Same with other principles. – Den Sep 23 '14 at 8:47
  • I really do not get the logic of explanation that something is not bad based on the fact, that it is a framework. ServiceLocators are made as frameworks too and they are deemed as bad:). – ipavlu Sep 10 '17 at 1:39
5

Don't IOC containers both break, and allow coders to break, a lot of OOP/SOLID principles?

The static keyword in C# allows developers to break a lot of OOP/SOLID principles but it's still a valid tool in the right circumstances.

IoC containers allow for well structured code and reduce the cognitive burden on developers. An IoC container can be used to make following SOLID principles much easier. It can be misused, sure, but if we excluded the use of any tool that can be misused we would have to give up programming altogether.

So while this rant raises some valid points about poorly written code, it is not exactly a question and it is not exactly useful.

  • It is my understanding of IOC Containers that in order to manage scope, laziness, and accessibility, it alters the compiled code to do things that break OOP/SOLID, but hides that implementation under a nice framework. But yes, it additionally opens other doors for incorrect use. The best answer to my question would be some clarification on how IOC Containers are implemented that adds or negates from my own research. – Suamere Sep 19 '14 at 13:04
  • 2
    @Suamere - I think you may be using too broad a definition of IOC. The one I use does not modify compiled code. There are plenty of other non-IOC tools which also modify compiled code. Maybe your title should be more like: "Does modifying compiled code break ...". – Cerad Sep 19 '14 at 15:05
  • @Suamere I'm not that experienced with IoC, but I never heard of IoC changing compiled code. Would you please provide information about what platforms/languages/frameworks are you talking about. – Euphoric Sep 19 '14 at 16:30
  • 1
    "An IoC container can be used to make following SOLID principles much easier." - can you elaborate please? Explaining how specifically it helps with each principle would be helpful. And of course leveraging something is not same as helping something happen (e.g. DI). – Den Sep 23 '14 at 8:38
  • 1
    @Euphoric I don't know about the .net frameworks suamere was talking about, but Spring certainly does do under-the-hood code modification. The most obvious example is its support for method-based injection (whereby it overrides an abstract method in your class to return a dependency it manages). It also makes extensive use of auto-generated proxies in order to wrap your code to provide additional behaviors (e.g. for automatically managing transactions). A lot of this is not actually related to its role as a DI framework, however. – Jules Nov 29 '14 at 16:35
3

I see multiple mistakes and misunderstandings in your question. The first major one is that you are missing distinction between property/field injection, constructor injection and service locator. There has been lots of discussion (eg. flamewars) about which is the right way to do things. In your question, you seem to assume only property injection and ignore constructor injection.

Second thing is that you assume that IoC containers somehow affect design of your code. They don't, or at least, it should not be necessary to change the design of your code if you use IoC. Your problems with classes with many constructors is case of IoC hiding real problems with your code (most possibly a class that has breaks SRP) and hidden dependencies are one of the reasons why people discourage use of property injection and service locator.

I don't understand where the problem with open-closed principle comes into this, so I won't comment on it. But you are missing one part of SOLID, that has major influence on this : Dependency Inversion principle. If you follow DIP, you will find out, that inverting a dependency introduces an abstraction, whose implementation needs to be resolved when an instance of class is created. With this approach, you will end up with lots of classes, that require multiple interfaces to be realized and provided at construction to function properly. If you were to then provide those dependencies by hand, you would have to do lots of additional work. IoC containers make it easier to do this work and even allow you to change it without having to recompile the program. You are probably confusion Open/Closed principle with Dependency Inversion principle in your question.

So, the answer to your question is no. IoC containers don't break OOP design principles. Quite contrary, they solve problems caused by following SOLID principles (especially Dependency-Inversion principle).

  • Tried applying IoC (Autofac) to a non-trivial project recently. Realized I had to do a similar job with non-language constructs (new() vs API): specifying what should be resolved to interfaces and what should be resolved to concrete classes, specifying what should be an instance and what should be a singleton, making sure property injection works right to alleviate a circular dependency. Decided to abandon. Works well with Controllers in ASP MVC though. – Den Sep 23 '14 at 8:45
  • @Den Your project was not obviously designed with Dependency inversion in mind. And nowhere I said that using IoC is trivial. – Euphoric Sep 23 '14 at 9:20
  • Actually, that the thing - I am using Dependency Inversion throughout from the very beginning. IoC affecting the design beyond that is my biggest worry. E.g. why would I use interfaces everywhere just to make IoC simpler? Also see the top comment on the question. – Den Sep 23 '14 at 10:06
  • @Den you technically don't "need" to use interfaces. Interfaces help with mocking for unit testing. You could also mark things you need to mock virtual. – Fred Apr 19 '17 at 15:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.