Skip to content

, ,

Embracing Change

August 21, 2007

(Or, How I Learned to Stop Worrying and Love Dependency Injection)

The dependency injection debate has continued to rage. Jacob posted a reply to Ayende’s two posts, which Ayende then responded to. Donald Belcham (the Igloo Coder) also got in the act. The debate has basically been geared around a few questions:

  1. Is there merit to dependency injection outside of unit testing and mocking?
  2. Is dependency injection alone enough to allow loose coupling?
  3. Is it dangerous to rely on a container to do “voodoo” to wire up objects?
  4. Doesn’t relying on interfaces violate YAGNI?

First off, I believe (as I’ve stated before) that there is a lot of benefit in dependency injection above and beyond unit testing. My development is not quite test-driven (yet), but I consistently rely on dependency injection to guide me in designing my software’s architecture. In my experience, the best feature of DI is that it makes it natural to separate concerns. Like most developers, I’m lazy, so if something is easy, it’s more likely to become second nature. These days, it’s very unlikely for me to accidentally violate SRP.

However, I think I haven’t been clear about one aspect of my stance: I also believe that dependency injection by hand is probably worse than using factories. As Jacob pointed out, by exposing the dependencies of a type, you can actually increase your coupling, because you’re requiring that any code that consumes your type also has knowledge of the type’s dependencies. Likewise, types that consume that type must know about the original types dependencies, and so on. In real-world scenarios, dependency injection by hand simply does not scale.

Enter a dependency injection framework. In the past, I’ve referred to embracing a dependency injector as going Hollywood (from the Hollywood Principle: “don’t call us, we’ll call you”). If you go Hollywood, you submit yourself to the glitz and glamour of your injector, with its fancy XML mapping files or [Inject] attributes. Relying on a bunch of voodoo like reflection to wire up your objects might seem daunting at first, but it has a very important purpose: it effectively means you never have to think about how your objects need to communicate. You just need to know which objects need to talk to which. Tell the injector, and it will figure out the rest.

I can understand the reluctance to give up control over such a fundamental aspect of your software, but you do it already. There’s this big elephant of a framework called the CLR hiding behind our software, and no one is complaining (too loudly) about the things it does to and for us. Adding a dependency injector into the mix is just another step, and if it lets me be lazy about busywork like wiring up objects, then sign me up! I think there’s a preconceived notion about people who consider software from an architectural perspective, that somehow we are more interested in the elegance of the system than in what it does for its users. I can’t speak for everyone, but for me, I just recognize that a good architecture means I can spend more time adding features, and less time doing mundane tasks.

I’d say that there’s different levels of commitment to dependency injection, too. You can use DI with or without programming against interfaces. I tend toward the latter because, while it’s more work, it’s another tool that helps me to design my software. If I’m designing a service-oriented architecture, defining interfaces for each of my services makes it easier to keep the interaction points simple. In spite of my interest in DI, I’m still very much a proponent of information hiding, and it’s very difficult to “over-expose” a type when you have to add them to an interface definition. Sure, I might never make another concrete implementation of an interface. But, if it’s there, I can take advantage of it if I need it. Not to mention that with a dependency injector, it’s perfectly natural to program against interfaces anyway — after all, there’s no constructors to call!

I made a comment, which has been repeated by Ayende, that dependency injection (particularly when used with interface-based development) makes your code easier to change. The tools that we have today for refactoring are great, but they aren’t magical. If you couple your types together too tightly, or jam all sorts of concerns into the same type, you will have a hell of a time separating them when it becomes necessary. Trust me. I’ve been there.

Great effort has been applied in .NET to supporting configuration. To me, programming against interfaces and using a dependency injector to wire everything up is a lot like putting connection strings in a configuration file. Sure, you can hardcode constants. They’re not going to change today, or maybe even for the foreseeable future. But they will. Given enough time, all requirements change. I’d rather plan for the inevitable.

I think what everyone is missing is that this is not a zero-sum game. No one is saying that if you don’t use dependency injection, your software will turn into a big ball of mud, your friends will abandon you, and your dog will bite you when you try to pet it. Likewise, no one is saying that if you embrace dependency injection, your code will turn into a symphony of light, become sentient, and begin to write itself while you nap at your desk. It’s just a principle to follow, like information hiding or single-responsibility. It can be misused, but if used correctly, it can and will change the way you think about software — and I think for the better.

About these ads

From → miscellaneous

  1. Yoni permalink

    “by exposing the dependencies of a type, you can actually increase your coupling, because you’re requiring that any code that consumes your type also has knowledge of the type’s dependencies”

    Can you provide a code sample to explain this statement? The way I see it an object should usually accept a type – which encapsulates its external dependencies – at construction. For example:

    class A
    B _b;
    public A(B b)
    _b = b;

    Now the code that constructs A is simply required to have an instance of B available at the time of construction. This is simple OOP and I don’t see why it shouldn’t scale…

    Regarding the issue of the interfaces, why not simply follow this rule:
    Dependencies should always be interfaces with at least two concrete implementations.

    If your dependencies are interfaces which have only one concrete implementation how can you tell you’ve got the correct separation between the client and the service?

  2. First off, there’s nothing wrong with dependency injection by hand when you just have a single layer of dependencies. Things start to get really tough, really fast when you add layers. For example, here’s what happens when you add a single layer of dependencies (rather than A->B, you’re going A->B->C):

    class A
    B _b;
    public A(B b) { _b = b; }

    class B
    C _c;
    public B(C c) { _c = c; }

    class C { … }

    To create an A, you have to chain up these dependencies:

    A myA = new A(new B(new C()));

    And remember, we’ve only added a single layer of dependencies… this is still an overly simplistic scenario that you wouldn’t see in most real-world applications. More realistic services have multiple dependencies, circular relationships, etc, which are very difficult to manage by hand. It’s not that you can’t do it, it’s that it’s much, much more difficult without a framework to help.

    This also means that whatever code you’re creating A in needs to know of the existence of not only the dependencies of A (in this case, B) — but also the dependencies of A’s dependencies (in this case, C). That means that A is now coupled to C, and so is any code that consumes A. This can quickly expand exponentially in larger systems. You could easily get to the situation where every service in your system must be aware of every other service. Blech.

    You’re also ignoring another major benefit of DI frameworks: the ability to control a service’s lifecycle. For example, with Ninject (and Windsor and Guice too), you can create a singleton simply by decorating your class definition with a [Singleton] attribute. To implement a singleton by hand, you either have to rely on something like a static Instance property, or a factory… neither of which is cleaner than a simple attribute. :)

    I’m not sure I understand your question about interface-based development. Generally, I declare an actual interface to help me determine how a given service will interact with the rest of the application. That’s why I feel they’re useful even if you never end up defining more than one concrete implementation of the interface.

  3. Yoni permalink

    Well all I can tell you is that even though I’m doing all my DI by hand I never got to the point of having more than “new A(new B(new C()))” – which, actually, does not couple A to C but rather couples the code which constructs A to B and C (an important difference). The reason is not that I don’t have more layers of dependencies but rather that the object creation process itself is divided among objects. Meaning there is usually only “new A(b)” where b was constructed earlier and is accessible to the current context. This control over the way objects are created allows for simple scenarios to be handled with a few lines of code (which is shorter than configuring the DI container), and complex scenarios to be handled with all the power of OOP (which will always be more flexible and easier to debug than the DI container).

    The singleton feature is nice but I rarely use singletons so that doesn’t help either :). And again, controlling object creation in code gives you a lot more control over the service’s lifecycle…

    Regarding interface-based development I wasn’t asking a question but rather offering advice. The problem of interfaces with single implementations being YAGNI can easily be solved by only allowing service’s with two or more implementations. But I guess this should probably wait for another discussion.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Get every new post delivered to your Inbox.

%d bloggers like this: