Stories about Software


Encapsulation vs Inversion of Control

This is a post that I’ve had in my drafts folder for nearly two years. Well, I should say I’ve had the title and a few haphazard notes in my drafts folder for nearly two years; I’m writing the actual post right now. The reason I’ve had it sitting around for so long is twofold: (1) it’s sort of a subjective, tricky topic and (2) I’ve struggled to find a concrete stand to take. But, I think it’s relatively important, so I’ve always vowed to circle back to it and, well, here we are.

The draft was first conceived when I’d given a presentation about testability and inversion of control — specifically, using dependency injection via constructors and setters to achieve these ends. I talked, among other things about the Open/Closed Principle, and how this allows modifications to system behavior that favor adding over editing code. The idea here is that we can achieve new functionality with a minimum of violence to the code base and in a way for which it is easy to write unit tests. Everyone wins, right?

Well, not everyone was drinking the Kool-Aid. I fielded a question about encapsulation that, at the time, I hadn’t prepared to answer. “Doesn’t this completely violate encapsulation?” I was a little poleaxed, and sputtered out an answer off the cuff, saying basically, “well, not completely….” I mean, if you’ve written a class that takes ILogger in its constructor and uses it for logging, I control the logger implementation but you control when and how it is used. So, you encapsulate the logger’s usage but not its implementation and this stands in contrast to what would happen if you instantiated your own logger or implemented it yourself — you would encapsulate everything and nothing would be up to me as a client of your code. Certainly, you have more encapsulation. So, I finished: “…not completely…. but who cares?!?” And that was the end of the discussion since we were out of time anyway.


I was never really satisfied with that answer but, as Creedence Clearwater says, “time and tears went by, and I collected dust.” When I thought back to that conversation, I would think to myself that encapsulation was passe in the same way that deep inheritance hierarchies were passe. I mean, sure, I learned that encapsulation was one of the four cornerstone principles of OOP, but so is inheritance, and that’s kind of going away with “favor composition over inheritance.” So, hey, “favor dependency injection over encapsulation.” Right? Still, I didn’t find this entirely satisfying — just good enough not to really occupy much of a place in my mind.

But then I remember a bit of a brouhaha last year over a Stack Overflow question. The question itself wasn’t especially remarkable (and was relatively quickly closed), but compiler author and programming legend Eric Lippert dropped by to say “DI is basically a bad idea.” To elaborate, he said:

There is no killer argument for DI because DI is basically a bad idea. The idea of DI is that you take what ought to be implementation details of a class and then allow the user of the class to determine those implementation details. This means that the author of the class no longer has control over the correctness or performance or reliability of the class; that control is put into the hands of the caller, who does not know enough about the internal implementation details of the class to make a good choice.

I was floored. Here we have one of the key authors of the C# compiler saying that the “D” in the SOLID principles was a “bad idea.” I would have dismissed it as blasphemy if (1) I were the sort to adopt approaches based on dogma and (2) he hadn’t helped author at least 4 more compilers than I have. And, while I didn’t suddenly rip the IoC containers out of my projects and instantiate everything inside constructors, I did revisit this topic in terms of my thoughts.

Maybe encapsulation, in the information hiding sense, isn’t so passe. And maybe DI isn’t a magic bullet. But why not? What’s wrong with the author of a class ceding control over some aspects of its behavior by allowing collaboration? And, isn’t any method parameter technically a form of DI, if we’re going to be pedantic about it?

The more I thought about it, the more I started to see competing and interesting use cases. Or, I should say, the more I started to think what class authors are telling their collaborators by using each of these techniques:

Encapsulation: “Don’t worry — I got this.”
Dependency Injection: “Don’t worry — if this doesn’t work, you can always change it.”

So, let’s say that you’re writing a Mars Rover or maybe a compiler or something. The attitude that you’re going to bring to that project is one in which correctness, performance and reliability are all incredibly important because you have to get it right and there’s little room for error. As such, you’re likely going to adopt implementation preference of “I’m going to make absolutely sure that nothing can go wrong with my code.”

But let’s say you’re writing a line of business app for Initrode Inc and the main project stakeholder is fickle, scatterbrained, and indecisive. Then you’re going to have an attitude in which ease and rapidity of system changes is incredibly important because you have to change it fast. As such, you’re likely to adopt an implementation preference of “I’m going to make absolutely sure that changing this without blowing everything up is easy.”

There’s bound to be somewhat of an inverse relationship between flexibility and correctness. As a classic example, a common criticism of Apple’s “walled garden” approach was that it was so rigid, while a common praise of the same was how well it worked. So I guess my take-away from this is that Dependency Injection and, more broadly, Inversion of Control, is not automatically desirable, but I also don’t think I can get to Eric’s take that it’s “basically a bad idea,” either. It’s simply an exchange of “more likely to be correct now” for “more likely to be correct later.” And in the generally agile world in which I live and with the kind of applications that I write, “later” tends to give more value.

Uncle Bob Martin once said, I believe in his Clean Coders video series, that (paraphrased) the second most important characteristic of good software is that it meet the customer requirements. The most important characteristic is that it be easy to change. Reason being, if a system is correct today but rigid, it will be wrong tomorrow when the customer wants changes. If the system is wrong today but flexible, it’s easy to make it right tomorrow. It may not be perfect, but I like DI because I need to be right tomorrow.

  • Rich

    A good, thought provoking read. Thanks.

  • http://www.daedtech.com/blog Erik Dietrich

    Thanks! Glad you liked it.

  • Medo

    I think the type of relationship between the class and the dependency should be a main factor in deciding whether to inject it. A good measure is probably how the dependency affects testability. A “pure” dependency that doesn’t affect the outside world – e.g. a data structure – should most likely be a hidden detail, unless you really want to make it configurable to change the performance characteristics. Anything that the class uses to communicate with its environment – e.g. database connections or streams or objects that are shared between the class and the rest of the system – should likely be injected.

    Basically, if the class interacts with the environment, that interaction is more than an implementation detail IMO, and the client SHOULD know about it and be able to affect it, unless that particular interaction is the main point of the class. This isn’t entirely black and white of course. E.g. code that caches data in temp files interacts with the environment, but in a way that doesn’t much affect the rest of the system or the outside world.

    Also, not all code needs to have the same level of insight into the class. I consider the construction a very special case where it is OK to require some knowledge of the nitty gritty details. This can mean that the constructing code might need to be changed if the interaction between the class and its environment changes (which shouldn’t happen that often), but the clients that interact with the class later, probably through more generic interfaces, should not be affected by that.

  • Matt B

    Nice article and certainly made me think. The point about the Mars rover and making sure the code is correct could be taken two ways I suppose. In order to ensure your code works as expected it is important to write unit tests for it, obviously DI helps in this situation but of course you then have to hand over control of the dependencies over to something else. (p.s. I found a typo, excahnge should be exchange I’m guessing)?

  • Rob

    In SOLID, the D is related to the S. If something is the responsibility of the class in question then I should be encapsulated, If the class needs something that is not its direct responsibility, such as a logging component, then that should be injected. It seems simple to me…

  • http://tech.trailmax.info/ trailmax

    Certainly an interesting thought to a bunch of cool-aid drinkers (such as myself -). What I don’t get is what is the opposite of DI. If we do not pass control from our class to (say) Logger, how do we do logging?
    If our class creates an instance of Logger, that is not IoC in my mind, but we still pass control to some other component to to logging and that breaks encapsulation in some way.

    Opposite of this I only see spaghetti code and ten-thousand-lines classes.

    Mind… blown!

  • http://www.daedtech.com/blog Erik Dietrich

    I think you’ve defined some really nice heuristics here for when to invert control and when not to, frankly. I’ve never given it a ton of thought and have always favored erring on the side of injection for the sake of testability, with a loose governing principle that “coordinating” type classes (data access, repositories, controllers, etc) should be injected and “entity” type classes are fine to instantiate. But considering the external (to the application or module) interactions is probably a more “pure” way of reasoning about it, for lack of a better word. Good thoughts.

  • http://www.daedtech.com/blog Erik Dietrich

    Thanks for the heads up on the typo. Fixed now.

    First off, let me say that I’m a fan of DI and testing code units in isolation. That said, the Devil’s Advocate argument is that it’s not really important to *unit* test the code to ensure that it works, but rather to somehow verify (and probably automated) correctness checks. In other words, if you had monolithic code with few or no entry points, but you wrote exhaustive integration tests that covered every scenario and line of code, this would suffice for verification. The drawback would be extreme rigidity if requirements changed, but if you declare that you won’t ever change it, I suppose this is an option.

    Again, this is not the style I’d favor, personally, but I think the argument could be made.

  • http://www.daedtech.com/blog Erik Dietrich

    A long time ago, I wrote a post advocating that people who had come from the C world to Java, C++ or C# should move to inverting control in their approach to OOP. ( http://www.daedtech.com/inverting-control ) In this post, I described the opposite of DI as “Command and Control.” In essence, what you have is main as the root of a tree structure, with the leaf nodes as the peons, like a company’s org chart. DI, on the other hand, strikes me as a paradigm more akin to building things with Legos — you assemble small pieces into slightly larger components, and then build those components into increasingly large/interesting structures.

    So, assuming that some semblance of order and decorum is preserved in the code base and you don’t have the kind of big ball of mud you’re referring to, you have separation of concerns but no “seams” to use for testing or configuring. So if you want a different logger implementation in your classes, you have to find all of the classes that use the logger, modify them to instantiate a different logger, and then rebuild/redeploy. I think this latter scenario is probably more in line with what Eric Lippert was talking about, as opposed to a spaghetti-fied mess. The class using the logger controls what it’s doing and it also controls which logger it uses, meaning that when you use it, it assumes all responsibility for everything it does.

  • http://tech.trailmax.info/ trailmax

    Ah, that makes sense! thanks for the write-up!

  • http://iobservable.net/ Mihail Slavchev

    I follow a simple rule for DI – I use it whenever my class has to use some orthogonal functionality (e.g. Logger). There are patterns which are more suitable than DI when my class has to change its behavior based on the injection.

  • http://www.daedtech.com/blog Erik Dietrich

    If you’re so inclined, do you have an example (gist or description)?

  • http://iobservable.net/ Mihail Slavchev

    Think of it as of a rule of thumb, not a dogmatic law. Just sharing what works for me so far.

  • http://www.daedtech.com/blog Erik Dietrich

    I’m definitely a fan of rules of thumb over dogma :)

    I was just genuinely curious as to an example or two of what you meant (e.g. do you modify behavior through inheritance, runtime binding, etc)?

  • http://iobservable.net/ Mihail Slavchev

    It depends. When I think of traditional design patterns like state or strategy I see a beautiful usage of both composition and inheritance. My point is it is not one or another.

    I remember a recent case when I replaced DI with simple inheritance and it worked well for me. I was using DI to inject a factory which was responsible for returning the correct strategy depending on the available device memory. Take a look at this gist https://gist.github.com/anonymous/cee8ab7ddf64a2893cfe

    What I dislike in this DI option was that an important decision was hidden deep in the code. It was too implicit for me. So I refactor the code to use one base class and two very thin subclasses.

  • http://www.daedtech.com/blog Erik Dietrich

    Thanks for the example. That makes a lot of sense now that I can visualize it, and I agree that your refactoring is an improvement. I like DI when it’s “flat,” so to speak. But I cringe when I see code bases that inject a dependency and the injected class queries the dependency for another and a third dependency.

    I find that unit tests help kind of bring these smells to the surface (I blogged about it once: http://www.daedtech.com/recognizing-test-smells ). You find yourself setting up mocks that return other mocks that return other mocks… and, yeah, that’s ugly. Your comment about important decisions being hid in strange places is spot on.

  • Pingback: Links & reads for 2014 Week 19 | Martin's Weekly Curations()

  • Pingback: The Baeldung Weekly Review 18()

  • Matt

    I’m with you Rob. For the types of services and applications I work on, it’s usually pretty clear what should and shouldn’t be injected into a class, and I strongly disagree that DI violates encapsulation when done right (you inject *dependencies*, not *responsibilities*).
    That said, I don’t write a lot of frameworks, work on OS kernels, device drivers, etc., so it may be that certain practicalities might favour a different approach for different kinds of software.

  • http://r.je Tom Butler

    Interesting article but the problem I have with it is you’re inferring that encapsulating all dependencies can somehow give you a different end product than injecting them… which is clearly not the case. You can build the exact same object graph with DI as you can using the new keyword in the constructor giving an identical level of ‘correctness’.

    The problem is, it’s not so strictly enforced and can be built in a different way, but by the same token, anyone who can alter new A(new B()); to new A(new C()) can also open up the A class and alter the constructor there for the same result.

    I’d argue that instantiating objects in constructors breaks encapsulation itself because the object doing the construction knows which implementation it’s going to use, whereas with DI that information is hidden from the object… it depends on which way around you look at it.

  • http://r.je Tom Butler

    I agree with you but I think caution is in order if reuse is ever an issue. Let’s say you have an object (A) instantiating another object (B). You move these classes to another project where the requirements are slightly different. You have to change (B)’s constructor or replace (B) with (C) for the new project. This causes an issue because any enhancements/bugfixes to the (A) class cannot be easily backported to the original project as you have multiple branches.

  • http://www.daedtech.com/blog Erik Dietrich

    If memory serves, I was writing the post with the perspective that I’d be writing a class and would have control over the class’s collaborators, but not how the class was used. So, if I were building a Restaurant class, and I wanted the restaurant to be cafeteria-style, I can control that. I could instantiate a CafeteriaStyle strategy object (or whatever) and enforce that — anyone who used my Restaurant would get it cafeteria style.

    But if I allow collaborators to inject an IStyle interface, I lose control of that. So now, this unknown user of my class has the ability to inject “SitDownServiceStyle” or any style he dreams up, including one that I may consider to be incorrect or that somehow works against the Restaurant class.

    So sure, you could wind up with the same object graph in both scenarios, but in one scenario I control it from an external collaborator’s perspective, and in the other scenario, that collaborator controls it, and I’m at his mercy, whatever I think the object graph ought to look like.

  • http://www.daedtech.com/blog Erik Dietrich

    Interesting take on constructor instantiation and encapsulation, by the way. I’ve never thought of it that way before, and what you say makes sense to me.