Clarification on the Dependency Inversion Principle

abstractiondependency-injectiondependency-inversioninterfaces

I'm reading the book "Agile Software Development, Principles, Patterns, and Practices" by Robert C. Martin.

When he talks about the Dependency Inversion Principle he gives the following example of a DIP violation:

DIP Violation

This seems very clear to me, as the higher-level object Button is depending on a lower-level object Lamp.

The solution he comes with is:

Solution

He creates an interface so that way Button is no longer depending on the object Lamp.

The theory seems really clear to me, however I can't wrap my head around using the principle in real-life projects.

  • Who is going to determine what classes (that implement SwitchableDevice) need to be called?

  • Who tells Button what devices he need to turn on/off?

  • How do you tell an object that uses something abstract which concrete things it needs to use? (Please correct me if this question is completely wrong)

If anything is unclear about my question, please let me know, I'll be glad to clarify things for you.

Best Answer

  • Who is going to determine what classes (that implement SwitchableDevice) need to be called?
  • Who tells Button what devices he need to turn on/off?
  • How do you tell an object that uses something abstract which concrete things it needs to use? (Please correct me if this question is completely wrong)

The term often used is "wiring", which refers to the activity of connecting decoupled classes together. It typically involves connecting abstract interfaces to concrete classes, and managing the injection of each concrete instance when the application starts.

Wiring commonly happens in a top-level part of a program near the entry point known as the Composition Root. That might be a method called by main() or it might even be main() itself. (This isn't always the case though; for example, ASP.NET handles wiring on a per-HTTP-request basis, so this is more about typical use cases than hard/fast rules)

The analogy could be similar to wiring an electronic system which has a lot of small components that need to be connected together in order to create a working system; e.g. wiring up the many components of a robot which has all kinds of moving parts and circuit boards.

The motivation for this approach to software construction is to be able to treat an application like a set of independent, disconnected, cohesive "building blocks" which are fused together at the top-level of the application in order to make something which works.

Each building block of an application may represent (or take responsibility for) an aspect of that application's functionality. For example, an object repository, a factory, an interface to a network messaging system, a business rules engine, an event processing system, an interface to an external API, a "main menu" UI, etc.

Splitting classes/components out like this alllows each component to be developed on its own, with its own independent development lifecycle, its own unit tests, and its own identity; agnostic to other components of the application.

Many applications have some kind of shell or controller type component which hosts the user interface (GUI app) or main thread (service/console app) which bridges the 'gap' between the UI/UX and the rest of the app components.

Breaking an application's behaviour into many smaller classes often results in a number of single instance, (mostly) stateless objects whose lifetime matches that of the application, but which still need to interact with each other; these types of classes are ideal candidates for Dependency Injection.

There there are plenty of cases where application behaviour might come from throwaway, short-lived, stateful objects too - sometimes these are also injected, but they can be a little more awkward to deal with, so these sometimes get encapsulated inside other patterns such as Strategy, Factory, etc.

Some very simple c# code for Bob Martin's example might look like:

static void Main(string[] args)
{
    ISwitchableDevice switchableDevice = new Lamp();
    var button = new Button(switchableDevice);
    // TODO - wiring for other classes which use button and switchableDevice
}

I don't think Uncle Bob's example on it's own is really sufficient to demonstrate DI/IoC in a real application - it only demonstrates constructor injection as an alternative to object ownership.

Here's a very contrived potential example of a background service whose responsibility is to listen for network control messages and toggle a lamp on and off:

static void Main(string[] args)
{
    ISwitchableDevice switchableDevice = new Lamp();
    var button = new Button(switchableDevice);
    var messageListener = new NetworkMessageListener();
    var serviceController = new ServiceController(messageListener, button);

    // The wiring is done, now start the main thread and block...
    serviceController.StartMainThread();
    Thread.Wait();
}

In the above example, I'm assuming that the app is made up of 4 different classes, all of which are shown in the Main method; those classes don't need to call new to create other classes because Main() (the composition root) uses poor-man's DI to create the various components of the application and wire them all up together.

You can assume that each concrete class' constructor accepts its dependencies using some interface, and that none of those classes actually know about each other.

The above pattern scales out very well - when a new component needs to be added to the application, it's added into the composition root, and the composition root handles its wiring/dependencies (and/or injects it into one of the existing components if necessary).

Related Topic