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.

koolaid

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