Quantcast
Channel: Scott Bamford
Viewing all articles
Browse latest Browse all 16

Dependency Inversion Principle (DIP) is much more than using the technique of Dependency Injection

$
0
0

What is the Dependency Inversion Principe (DIP)

The Dependency Inversion Principle (often referred to as DIP) is one of the five basic principles of object orientated programming and design known as SOLID.

The principle states:

A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
B. Abstractions should not depend on details. Details should depend on abstractions.

To see how code can be made to comply with this principle lets look at the relationship between two classes in an example:

public class Repository
{
    public void DoSomething()
    {
        // Do something.
    }
}

public class Processor
{
    private Repository repository;

    public Processor()
    {
        repository = new Repository();
    }

    public void ProcessData()
    {
        repository.DoSomething();

        // Other tasks...
    }
}

In this example the Processor class has a dependency of Repository for two reasons:

  1. Processor depends on the functionality of Repository to meet its own promised functionality.
  2. Processor creates a new instance of Repository and is responsible for its lifecycle.

The temptation when reading these reasons is to try and tackle the second point (which we’ll do later when we talk about Dependency Injection), but its actually the first point that is most important when it comes to the Dependency Inversion Principle.

As it currently stands Processor is so dependent on Repository that any change to Repository could directly affect the ability of Processor to fulfil its promised functionality.  At a minimum any change to Repository will require a review/retest of Processor to identify any impact from the change. So lets go about removing the dependency through abstraction to solve this.

First we need to understand the functionality required of Repository by Processor and state those requirements as an interface/abstract class:

public interface IRepository
{
    void DoSomething();
}

public class Processor
{
    private IRepository repository;

    public Processor()
    {
        repository = new Repository();
    }

    public void ProcessData()
    {
        repository.DoSomething();

        // Other tasks...
    }
}

After this first step Processor is no longer dependent on the functionality of the Repository class (forget the new Repository() statement – we’ll address that in the next section).  Instead has stated in IRepository its requirements for repository functionality and thereby become dependent on the abstraction of its needs as represented by the IRepository interface. This was our goal.

This next point is one that is often misunderstood – the owner of this IRepository interface is now Processor.  It is not Repository.  Many people get this ownership backwards and set about defining IRepository by starting with Repository and exposing all the functionality of the Repository class through the new interface. Once the interface is then in place they would modify Processor to consume that interface.  The problem with doing that is that we would not have inverted our dependency to be based on our needs, but simply swapped our dependency of a concrete definition of Repository and its functionality for an abstract definition of the same functionality, still owned by the detail Repository and still exposes Processor to changes in Repository and the interfaces it owns. The interface itself does nothing to invert our dependencies. Given that the LSP principle of SOLID already allowed us to replace Repository with a subclassed implementation we haven’t really gained anything.

So you can see that its not the abstraction of an interface covering Repository’s functionality that inverted our dependency, its the abstraction of an interface that stated Processors need.

Lets complete this example now with the second step and get Repository to implement the needs of Processor through the IRepository interface:

public class Repository : IRepository
{
    public void DoSomething()
    {
        // Do something.
    }
}

At this point you may want to point out that the code we have ended up with is absolutely no different than the code we would have ended up with if we had simply abstracted the functionality of Repository into IRepository – and you would be correct.  This shows that software design is more about planning for initial implementation and long term maintenance than it is about changing the code you write.  Few programmers like to admit this, but it is true.

The code may be the same but the design from a maintenance point of view is very different, lets take a look at both options

If we designed IRepository to be an abstraction of Repository:

  1. Any change needed to the public interface of Repository would need to be reflected in IRepository so the abstraction of Repository remains as per our design.
  2. Any change to IRepository (triggered by a change to the public interface of Repository) will still require a review/retest of Processor.
  3. We can make as many classes as we want dependent on the functionality of Repository through IRepository.

Therefore is IRepository is an abstraction of Repository we are still left with the same dependency/coupling from a Processor point of view as we had before the IRepository interface was introduced – only we now have more code to maintain.

If on the other hand, as we have done here, we designed IRepository an abstraction of the needs of Processor:

  1. Any change to the needs of IRepository by Processor will result in a change of IRepository.  This would only break the Processor class as it will no longer be fulfilling its promised functionality until we correct it.
  2. We now have a choice – we can implement the new needs of the IRepository interface into Repository or we can:
    • Implement the new functionality of IRepository into Repository so it can continue to meet the needs of Processor; or
    • Remove the IRepository interface from Repository’s definition, as it no longer meets the needs of Processor that owns the abstract interface, and provide a new concrete implementation of IRepository to meet the needs
  3. Other users of Repository (either directly or through their own interfaces expressing their individual needs) do not need to be updated or retested.

As you can see here, although the code in our example is the same, by inverting the dependency between Processor and Repository we have ensured that maintenance of Repository only affects Repository, and maintenance on Processor (and its associated IRepository abstraction) only have to affect Processor.  Way may choose to use other classes in the solving of the new need, but we do not have to, we are not dependent on doing so.  If all users of Repository employ this same principle of abstraction of needs, it also means we don’t have to worry about any maintenance we do choose to do on Repository to meet the new IRepository needs of Processor rippling on to any other classes Repository is indirectly used by as their own abstraction of needs protects them.

If at this point you are starting to worry about the number of different interfaces required to keep this inversion, let me jump in and say yes it is essential that you create some shared Standard Interfaces that act as “standards” for common needs classes may have. However done correctly, these standard interfaces will be tightly version controlled and built for a single purpose, and therefore if instead of defining IRepository for Processor we choose to use a standard abstraction of needs represented by an IStandardRepository interface; Processor still remains the sole class involved in any decision to stop using that standard interface if the needs of Processor changes such that the standard no longer meets Processor’s needs, in which case it will go back to defining its own IRepository to represents its needs and therefore all the benefits of the inverted design remain. Standardised abstractions are not owned by the class that needs it, or those that implement it, they are independent. However using standard abstractions that already represents a classes need does not introduce any new coupled dependencies and does not take away from the class the ability to change the definition of its needs should they change.

In our example here we have used the technique of an Interface (or abstract class in some languages) to achieve our design, but it is critical to understand that it is not the use of the technique that made our design comply with DIP. It was our choice to design to complied with DIP that mattered.  Once we made the choice we could use any technique that met those needs. Our design itself is also not dependent on the technique used to meet it.

What is Dependency Injection

This article is unlike most you will find on the matter of dependency injection as rather than jumping straight into the technique we have first made sure we have a clear understanding of the Dependency Inversion Principle. We can talk about dependency injection from the position of our need, rather than simply as a technique or tool for us to use.

Dependency Injection is a technique we can use to overcome the second dependency between Processor and Repository in the original code (I did promised I’d come back to it)- the fact that we directly instate a new Repository() within Processor.

We have three ways to solve this, each has its pro’s and con’s, a full discussion of which is outside the scope of this article; but which one you use will mostly depend on your team’s taste, and many times be restricted by limitations and style of any existing existing code-base.  I could point you to a long list of blog posts and articles discussing (with various degrees of objectivity) which is better than the other – personally I like a little more balance and to practice the principle above of deciding a design and letting any technique be used as long as it meets the design principles, rather than believing one solution fits all problems and designs.

Service Location

OK if I don’t say this now I’m sure many people will jump to tell me service location is not dependency injection, and I accept that some strong advocates of the two styles of dependency injection described below this section go so far as to hate Service Location as if it is the worst sin a developer could make – but despite these loud opinions – Service Location in very many cases is a viable and good choice for tasks like the one we are trying to achieve here.

We can use Service Location to remove the direct dependency “new Repository()” within Processor and replace it with a line that says “give me the best available (or configured choice) IRepository implementation please”.   Here is the code:

public class Processor
{
    private IRepository repository;

    public Processor()
    {
        repository = ServiceLocator.Resolve<IRepository>();
    }

    public void ProcessData()
    {
        repository.DoSomething();

        // Other tasks...
    }
}

The implementation of ServiceLocator is outside the scope of this article, however the result of all implementations is the same – a class implementing IRepository will be returned from the Resolve() call. This means in the future if we want to use a different implementation of IRepository within Processor (or Repository stops implementing it) we can make that take place with an appropriate code change, or configuration change, to connect the IRepository interface to the new concrete implementation without having to modify Processor’s code.

One very valid argument raised about using a Service Locator is that the ServiceLocator will need to have been configured before we can use Processor but Processor does not advertise this fact in its public interface. This can cause unexpected errors if someone creates a new Processor() and is not aware of its implementation detail of using ServiceLocator. Many of you will already know my best practice principle about “always providing a default implementation of your interfaces” (which one day I’ll get round to writing an article on) and if you make sensible use of modern language features like reflection then there is no reason a ServiceLocator can’t be self configuring in these situations – putting its use back where it was intended by the author of Processor – as an implementation choice rather than a public dependency/requirement of the class. This argument against use of service locators is therefore only valid if we allow it to be in the code we write.

If there is enough interest I’ll create a new article with full code and description of a self-configuring ServiceLocator based on reflection as described above.  Get in touch with me via email, twitter, or in a comment here to let me know.

Property Based Dependency Injection

In a nutshell the other approach to Dependency Injection is the process of letting some of the dependencies of your class be passed in from the calling code rather than being instated directly within the class. One common method is to convert our private members variables into Properties to allow the injection to take place.  Again a full implementation is outside the scope of this article, however here is how Processor would look if we were using property based dependency injection.

public class Processor
{
    public IRepository Repository { get; set; }

    public Processor()
    {
    }

    public void ProcessData()
    {
        Repository.DoSomething();

        // Other tasks...
    }
}

Here you can see the constructor no longer initialises repository and instead it has become a part of the public interface of the class and it is expected to be set from outside the class before ProcessData() is called. e.g.:

var processor = new Processor();
processor.Repository = new Repository();
processor.ProcessData();

A very valid argument against this approach is that again the calling code has to know to set processor.Repository before calling ProcessData() or any other method that might use it. This again is exposing the implementation detail of the class to those who use it. You should also ask yourself carefully for each and every dependency you expose like this if you are comfortable making this dependency part of your public interface for the class or if it really should stay an implementation detail. The number one mistake I see when people use dependency injection without full thought for the Dependency Inversion Principle is that every class consumed in the implementation of Processor would become a dependency. This not only breaks any encapsulation the class may once have contained, but also means it becomes completely impractical handling dependency injection by hand, and a dependency injection framework will have to be used. Like service locators dependency injection frameworks also need to be configured before they are first used.

If you do decide that a particular dependency does belong as a public property – but don’t want to pass your problems of configuration onto the caller or a framework of their choice – can I suggest that you use a second technique, which might be service location, to instate your dependencies on first use if they haven’t been supplied to you. This can take your class back to a zero configuration position so long as your service locator is self configuring, while still giving users of your class the ability override your dependencies for extension or for mocking.

A simple implementation of a self-configuring and therefore optional dependency property would be:

public class Processor
{
    public Processor()
    {
    }

    public IRepository Repository
    {
        get
        {
            if (m_repository == null) {
                m_repository = ServiceLocator.Resolve<IRepository>();
            }

            return m_repository;
        }
        set { m_repository = value; }
    }

    private IRepository m_repository;

    public void ProcessData()
    {
        Repository.DoSomething();

        // Other tasks...
    }
}

Constructor Based Dependency Injection

The main difference between property based dependency injection and constructor based dependency injection is that in constructor based dependency injection all dependencies must be supplied when instating the class rather than one at a time in properties. This gives two benefits:

  1.  It is not possible to make the state of the class invalid due to dependencies not being set – as all dependencies must be set in the constructor and therefore every instance of the class will always have all of its dependencies satisfied.
  2. Because all dependencies are initialised at the start of the objects life, implementation details of which methods use which dependencies no longer has to be understood by anyone setting up the class for consumption.

These two reasons help to explain why constructor based dependency injection is the most popular method used by advocates of dependency injection and dependency injection frameworks.

Lets look at the code for using constructor based dependency injection for Processor.

public class Processor
{
    private IRepository repository;

    public Processor(IRepository repository)
    {
        this.repository = repository;
    }

    public void ProcessData()
    {
        repository.DoSomething();

        // Other tasks...
    }
}

And here is the code to manually initialise it with its dependencies:

var processor = new Processor(
    repository: new Repository()
    );
processor.ProcessData();

You can see here that the issue of forgetting to set dependencies is not a concern in the way it was for property based dependency injection. However the concerns about breaking encapsulation with the things you decide to add a public dependencies remain, and still need thinking about carefully. Common to both property and constructor based dependency injection as well is the fact that real object graphs get much more complex than in our example (what if Repository had some dependencies that needed to be injected?) and so a dependency injection framework quickly becomes essential to keep the code maintainable in the long term.

I would also repeat my advice of making sure there is a default implementation of each dependency to save the caller having to do manual configuration or having to use a dependency injection framework if they don’t want or need to.

Summary

You have now seen that the Dependency Inversion Principle is a design principle we take into our software designs and code that is focused on inverting dependencies by having classes depend on their needs and not on concrete or abstract classes/interfaces that other state of their capabilities which might meet our needs.

We can use techniques such as interfaces or abstract classes to help us abstract the needs of a class into an abstract dependency that can be met by any class that wants to. This breaks the functional dependency between a class and its detail.

We can use techniques such as dependency injection or service location to help us de-couple any particular implementation of an interface from the class that needs it. This breaks the dependency between a class and the detail used to fulfil its needs.

You will have also observed that simply using dependency injection does not in any way mean we are using dependency inversion, and if used wrongly, can start to damage encapsulation and restrict the environments our code can be reused within. Furthermore if we start with the premise of using a particular technique, such as dependency injection, our designs can end up dependent on the technique we chose, rather than the principle of real dependency inversion.

If we apply the Dependency Inversion Principle to our designs – we can then safely choose to use the most appropriate technique or pattern to meet the needs of that design, but if our design needs change, we will still be able to move to a different technique and always maintain our principle of dependency inversion.

 



Viewing all articles
Browse latest Browse all 16

Trending Articles