- Constructor Injection
public class Widget { private WidgetDependency _dependency; public Widget(WidgetDependency dependency) { _dependency = dependency; } }
- Setter Injection
public class Widget { public WidgetDependency Dependency { get; set; } }
- Late-Bound Injection
public class Widget { public WidgetDependency Dependency { get { return ServiceLocatorObject.Resolve<widgetdependency>(); } } }
Equally, over the years, I've unilaterally preferred the third option. Though I've never really given it a whole lot of thought or attempted to articulate why. It was just a preference. To some (and indeed often to attendees of my What Is Dependency Injection presentation) it can certainly seem like the more complex choice. Sure, it does present a little more code and a little more round-about way if getting what you're looking for, but it carries benefits. And I've recently found a compelling benefit that previously had just sort of tagged along for the ride without explicitly being known to me.
I started thinking about it a couple days ago when I answered a question on Stack Overflow. While answering the question I have to admit that I may not have fully understood what was being asked. But now I think I do. Looking back, it seems to me now that the communications failure between the asker and myself was specifically in regard to the above dependency injection options.
The asker was most likely supplying his dependencies by means of either constructor injection or setter injection. And, since I generally stay clear of those methods, it just didn't occur to me. Instead, again, I prefer the Common Service Locator as a pattern where a single object, bootstrapped at application start, is responsible for, well, locating services. Or dependencies, as it were.
The concern about which he was asking was how to write unit tests without first knowing the implementation of the code being tested. He's seen that Test Driven Development is preferred throughout the industry but couldn't reconcile how one can effectively write tests without knowing some details about the implementation. Specifically with regards to dependencies. And, furthermore, as the implementation changes and the dependencies change, he asserts that the unit tests would have to change as well.
He's right. Because he's using one of my non-preferred methods of dependency injection. And therein lies the problem. Let's assume for the moment that he's using constructor injection. In the example, then, he ends up with an object like this:
Looks clean enough, right? But there's a problem. There's a leaky abstraction. His assertion was that, prior to implementing the class, the only known footprint is this:
And that it's the implementation details which change the footprint to the previous one with the injectable dependencies. Thus, one ends up in a cycle. Define the object, write tests, implement the object (thus changing its footprint), re-write the tests to match, make updates to the object later in the software life cycle (maybe further changing its footprint), re-write the tests again to match, and so on.
He's right, that is a vicious cycle. And it's all stemming from that leaky abstraction. The problem isn't in the tests or in Test Driven Development at all. The problem is in a flaw in the design of the object. That constructor is making implementation details externally visible. It's not only allowing but requiring that all external neighbors to it know about its implementation details on some level in order to supply those dependencies. And, of course, as those details and dependencies change then all neighbors must also change.
This is tight coupling. If a change to Object A requires that Object B must also change then Object A and Object B are coupled around that feature. In this case those changes are internal implementation details, and so this coupling is of course wrong.
And this is precisely that subtle reasoning I've had behind my preferred method of dependency injection which has never explicitly occurred to me before. With something like Common Service Locator (or, more commonly for me, a home-grown implementation of the same pattern), the dependency injection within the object instead looks more like this:
Externally to the object there is no knowledge of the dependency. The coupling is eliminated. So things like tests (or any other neighbors to this object anywhere in the system) don't need to know anything about its implementation details, just as it should be.
I started thinking about it a couple days ago when I answered a question on Stack Overflow. While answering the question I have to admit that I may not have fully understood what was being asked. But now I think I do. Looking back, it seems to me now that the communications failure between the asker and myself was specifically in regard to the above dependency injection options.
The asker was most likely supplying his dependencies by means of either constructor injection or setter injection. And, since I generally stay clear of those methods, it just didn't occur to me. Instead, again, I prefer the Common Service Locator as a pattern where a single object, bootstrapped at application start, is responsible for, well, locating services. Or dependencies, as it were.
The concern about which he was asking was how to write unit tests without first knowing the implementation of the code being tested. He's seen that Test Driven Development is preferred throughout the industry but couldn't reconcile how one can effectively write tests without knowing some details about the implementation. Specifically with regards to dependencies. And, furthermore, as the implementation changes and the dependencies change, he asserts that the unit tests would have to change as well.
He's right. Because he's using one of my non-preferred methods of dependency injection. And therein lies the problem. Let's assume for the moment that he's using constructor injection. In the example, then, he ends up with an object like this:
public class MyMathObject { public MyMathObject(MyOtherClass1 dependency) { // implementation details } public int Add(int addend, int augend) { // implementation details } }
Looks clean enough, right? But there's a problem. There's a leaky abstraction. His assertion was that, prior to implementing the class, the only known footprint is this:
public class MyMathObject { public int Add(int addend, int augend) { // implementation details } }
And that it's the implementation details which change the footprint to the previous one with the injectable dependencies. Thus, one ends up in a cycle. Define the object, write tests, implement the object (thus changing its footprint), re-write the tests to match, make updates to the object later in the software life cycle (maybe further changing its footprint), re-write the tests again to match, and so on.
He's right, that is a vicious cycle. And it's all stemming from that leaky abstraction. The problem isn't in the tests or in Test Driven Development at all. The problem is in a flaw in the design of the object. That constructor is making implementation details externally visible. It's not only allowing but requiring that all external neighbors to it know about its implementation details on some level in order to supply those dependencies. And, of course, as those details and dependencies change then all neighbors must also change.
This is tight coupling. If a change to Object A requires that Object B must also change then Object A and Object B are coupled around that feature. In this case those changes are internal implementation details, and so this coupling is of course wrong.
And this is precisely that subtle reasoning I've had behind my preferred method of dependency injection which has never explicitly occurred to me before. With something like Common Service Locator (or, more commonly for me, a home-grown implementation of the same pattern), the dependency injection within the object instead looks more like this:
public class MyMathObject { public int Add(int addend, int augend) { var dependency = ServiceLocatorObject.Resolve<widgetdependency>(); // more implementation details } }
Externally to the object there is no knowledge of the dependency. The coupling is eliminated. So things like tests (or any other neighbors to this object anywhere in the system) don't need to know anything about its implementation details, just as it should be.