Inversion of Control (IoC) and Dependency Injection (DI)

What is Inversion of Control (IoC)?

Inversion of Control (IoC) is a generic term for changing (i.e. inverting) which of at least two sites controls a specific behavior. In other words, inversion of control is a simple statement about where the responsibility or control over some behavior belongs.  Thus, inversion of control falls naturally out of writing cohesive units that adhere to the single-responsibility principle (pdf) and the open-closed principle (pdf).  As inversion of control is really about the control over behavior, IoC can be implemented in many different ways. For example, control may be inverted from callee to caller through:

  • Function pointers and function objects in C/C++
  • Callbacks
  • Lambda methods and delegates in C#
  • Dependency Injection
  • Event Dispatching

The term inversion of control is quite generic, enough so that Martin Fowler has said:

Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name Dependency Injection.

When related to dependency injection (DI), inversion of control is to move the control and the creation and wiring of dependencies to outside the usage site. This often involves changing from procedural code to a declarative style wherein the connections between dependencies are configured separately from usage.

For example, it’s common to see code like the following:

public class QueryService {
    public QueryService() {
        this.db = new SqlServerDBConnectionWrapper(/*...*/)
    }
    // ...
}

For one-off software, the above may be perfectly acceptable, but in typical enterprise software better change control points are needed.  For example, the above will fail to compile if the SqlServerDBConnectionWrapper constructor is changed. The problem with the above in any large-scale software is that the dependencies are hard coded. If a change request comes along requiring that an Oracle Database be supported the code must change because the dependency has been hard-wired. Or, to tie it back to the open-closed principle (OCP), the above code is not closed with respect to the database connection wrapper.

// somewhere in the configuration code
public void SetupBindings() {
    iocConfiguration.Bind<SqlServerDBConnectionWrapper>().To<IDBConnectionWrapper>();
}

// elsewhere
public class QueryService {
    public QueryService(IDBConnectionWrapper db) {
        this.db = db;
    }
    // ...
}

Although some IoC containers are nothing more than DI containers, others aim to be a general purpose IoC container and support facets of IoC beyond dependency injection. DI and IoC containers allow concrete implementations to be bound to abstract components, such as interfaces. When we use dependency injection we move from procedurally defining dependencies to declaratively defining those dependencies. This allows for dependencies to be easily changed later, as in the above example.  Using a little logic, it becomes possible to use a different database connection wrapper whenever needed.

Benefits of IoC

  1. Encourages proper placement of behaviors and responsibilities. As you consider which parts of the code might need to be inverted it becomes more obvious where the lines of responsibility fall and, with that clarity, a better design becomes more likely.
  2. Provides better configurability and testability. Proper use of an IoC container encourages the separation of component wiring from use which makes the application more configurable and more easily tested.
  3. Centralizes configuration management. When the configuration of components happens in one place it becomes easier to spot duplication and make changes that affect the configuration as a whole.
  4. Handles deeply nested object hierarchies. Computers are good handling the repetitive and mundane.

IoC Container Features

The following items should be considered when choosing and working with an IoC/DI container:

  1. Registration (e.g. binding a concrete implementation to an abstract type)
  2. Resolution – runtime creation of requested types
  3. Lifecycle Management – control of when objects are disposed or garbage collected
  4. Configuration Management (e.g. passing settings into necessary objects)

I’m not going to go into details on the above features in this post, but there are many benefits to using an IoC/DI container. It’s very possible to write good software without an IoC container. In some cases, not using an IoC container will allow problems to be spotted and fixed earlier. Of course, poorly used, IoC containers can exacerbate existing problems. But, that said, you should consider the benefits of using an IoC container over using straightforward DI code.

Tags: , , , ,

No comments yet.

Leave a Reply