The Dependency Inversion Principle

In this post I want to discuss the D of the S.O.L.I.D. principles and patterns. The principles and patterns subsumed in S.O.L.I.D. can be seen as the cornerstones of “good” application design. In this context D is the place holder for the dependency inversion principle. In a previous post I already have discussed the S which is the placeholder for single responsibility principle.

What is Bad Design?

Let’s first discuss a little bit about what bad design is. Is bad design when somebody claims

That’s not the way I would have done it…

Well, sorry, but this is not a valid measure for the quality of the design! This statement is purely based on personal preferences. So let’s find other, better criteria to define bad design. If a system exhibits any or all of the following three traits then we have identified bad design

  • the system is rigid: it’s hard to change a part of the system without affection too many other parts of the system
  • the system is fragile: when making a change, unexpected parts of the system break
  • the system or component is immobile: it is hard to reuse it in another application because it cannot be disentangled from the current application.

An immobile design

Let’s now have a look a the latter of the traits of bad design mentioned above. A design is immobile when the desirable parts of the design are highly dependent upon other details that are not desired.

Imagine a sample where we have developed a class which contains a highly sophisticated encryption algorithm. This class takes as an input a source file name and a target file name. The content to encrypt is then read from the source file and the encrypted content is written to the target file.

public class EncryptionService
{
    public void Encrypt(string sourceFileName, string targetFileName)
    {
        // Read content
        byte[] content;
        using(var fs = new FileStream(sourceFileName, FileMode.Open, FileAccess.Read))
        {
            content = new byte[fs.Length];
            fs.Read(content, 0, content.Length);
        }
 
        // encrypt
        byte[] encryptedContent = DoEncryption(content);
 
        // write encrypted content
        using(var fs = new FileStream(targetFileName, FileMode.CreateNew, FileAccess.ReadWrite))
        {
            fs.Write(encryptedContent, 0, encryptedContent.Length);
        }
    }
 
    private byte[] DoEncryption(byte[] content)
    {
        byte[] encryptedContent = null;
        // put here your encryption algorithm...
        return encryptedContent;
    }
}

Listing 1: Encryption Service depending on Details

The problem with the above class is that it is highly coupled to a certain input and output. In this case input and output are both files. But you might have invested quite some time and resources to develop the encryption algorithm which is the core responsibility of this service. It’s a shame that this encryption algorithm cannot be used in another context where the content to be encrypted is not present in a file but rather in a database and also where the encrypted content should not be written to a file but rather sent to a web service.

Certainly we could make the above service more flexible and change it’s implementation. We can put in place a switch for the content source type and one for the destination type of the encrypted content.

public enum ContentSource { File, Database }
public enum ContentTarget { File, WebService }
 
public class EncryptionService_2
{
    public void Encrypt(ContentSource source, ContentTarget target)
    {
        // Read content
        byte[] content;
        switch (source)
        {
            case ContentSource.File:     content = GetFromFile(); break;
            case ContentSource.Database: content = GetFromDatabase(); break;
        }
 
        // encrypt
        byte[] encryptedContent = DoEncryption(content);
 
        // write encrypted content
        switch (target)
        {
            case ContentTarget.File:       WriteToFile(encryptedContent); break;
            case ContentTarget.WebService: WriteToWebService(encryptedContent); break;
        }
    }
 
    // rest of code omitted for brevity
}

Listing 2: Slightly improved Encryption Service

However this adds new interdependencies to the system. As time goes on, and more and more source and/or destination types must participate in the encryption program, the “Encrypt” method will be littered with switch/case statements and will be dependent upon many lower level modules. It will eventually become rigid and fragile.

Comes the dependency inversion principle to the rescue.

The Dependency Inversion Principle

Theory: the dependency inversion principle states

a) High level modules should not depend upon low level modules. Both should depend upon abstractions

b) Abstractions should not depend upon details. Details should depend upon abstractions

One way to characterize the problem above is to notice that the method containing the high level policy, i.e. the Encrypt method , is dependent upon the low level detailed method that it controls. (i.e. GetFromFile and WriteToWebService). If we could find a way to make the Encrypt method independent of the details that it controls, then we could reuse it freely. We could produce other applications which used this service to encrypt content originating from any content source to any destination.

Consider the simple class diagram below.

image

Here we have an EncryptionService class which uses an abstract “Reader” class (identified by an interface IReader) and an abstract “Writer” class (identified by an interface IWriter). Note that the abstraction in this case is not achieved through inheritance but through the use of interfaces. We have separated the interface from the implementation.

The Encrypt method uses the “Reader” to get the content and sends the encrypted content to the “Writer”.

public class EncryptionService
{
    public void Encrypt(IReader reader, IWriter writer)
    {
        // Read content
        byte[] content = reader.ReadAll();
 
        // encrypt
        byte[] encryptedContent = DoEncryption(content);
 
        // write encrypted content
        writer.Write(encryptedContent);
    }
 
    // rest of code omitted for brevity...
}

Listing 3: Encryption Service only depending upon Abstractions

The Encrypt method of the encryption service is now independent of a specific content reader or writer. The dependencies have been inverted; the EncryptionService class depends upon abstractions, and the detailed readers and writers depend upon the same abstractions.

The definition of the two interfaces used is

public interface IReader
{
    byte[] ReadAll();
}
 
public interface IWriter
{
    void Write(byte[] content);
}

Listing 4: Reader and Writer Interfaces

Now we can reuse the encryption service. We can invent new kinds of “Reader” and “Writer” implementations that we can supply to the Encrypt method of the service. Moreover, no matter how many kinds of “Readers” and “Writers” are created, the encryption service will depend upon none of them. There will be no interdependencies to make the application fragile or rigid. And the encryption service can be used in many different contexts. The service is mobile.

Why call it dependency inversion?

The dependency structure of a well designed object oriented application is “inverted” with respect to the dependency structure that normally results from a “traditional” application which is implemented in a more procedural style. In a procedural application high level modules depend upon low level modules and abstractions depend upon details (as in listing 1 and 2).

Consider the implications of high level modules that depend upon low level modules. It is the high level modules that contain the important policy decisions and business models of an application. It is these models that contain the identity of the application. Yet, when these modules depend upon the lower level modules, then changes to the lower level modules can have direct effects upon them; and can force them to change.
This predicament is absurd! It is the high level modules that ought to be forcing the low level modules to change. It is the high level modules that should take precedence over the lower level modules. High level modules simply should not depend upon low level modules in any way.
Moreover, it is high level modules that we want to be able to reuse. When high level modules depend upon low level modules, it becomes very difficult to reuse those high level modules in different contexts. However, when the high level modules are independent of the low level modules, then the high level modules can be reused quite simply.

Summary

When implementing an application the modules and components of a higher abstraction level should never directly depend upon the (implementation-) details of modules or components of a lower abstraction level. It’s the high level components that make an application unique. It’s the high level modules that contain most of the business value. Thus the high level components should dictate whether low level components have to change or not and not vice versa.

When a component does not depend on lower level components directly but only through abstractions this component is mobile - that is, the component is reusable in many different contexts.

Furthermore when the design is respecting the dependency inversion principle an application is less brittle or fragile. If some changes have to be made to a low level module there are no side effects to be expected that manifest themselves in other (possibly unexpected) parts of the system.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Gabriel Schenker

Gabriel N. Schenker started his career as a physicist. Following his passion and interest in stars and the universe he chose to write his Ph.D. thesis in astrophysics. Soon after this he dedicated all his time to his second passion, writing and architecting software. Gabriel has since been working for over 12 years as an independent consultant, trainer, and mentor mainly on the .NET platform. He is currently working as chief software architect in a mid-size US company based in Austin TX providing software and services to the pharmaceutical industry as well as to many well-known hospitals and universities throughout the US and in many other countries around the world. Gabriel is passionate about software development and tries to make the life of developers easier by providing guidelines and frameworks to reduce friction in the software development process. Gabriel is married and father of four children and during his spare time likes hiking in the mountains, cooking and reading.
This entry was posted in design, practices, SOLID. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://emiajnet.blogspot.com Jaime

    Very interesting and informative as always.
    Congrats.
    A great postcast about SOLID can be found here:
    http://www.hanselminutes.com/default.aspx?showID=163
    Have a nice day.

  • http://www.cornetdesign.com Cory Foy

    Good post, but why not just use the TextReader/TextWriter or StreamReader/StreamWriter in your example?

  • http://www.lostechies.com/members/gnschenker/default.aspx Gabriel N. Schenker

    @Cory: I could have used these classes. But there the abstraction is through inheritance. In my sample the abstraction is through interfaces. Most people struggle more with interfaces than with inheritance (at least in my personal experience as a trainer…)

  • http://blogs.microsoft.co.il/blogs/kim Kim

    Assuming:
    1) That this component is not consumed by external parties.
    2) you know that you only have to support encryption of files.

    Would you still always create a DI implementation? Or would you refactor towards this when the need comes up? (extract interface etc.)

  • MrTea

    @Kim

    Personally I would favour di in all cases, apart from anything else it’s more testable, and another key benefit is DI will make it easier for other developers to debug/work on your code.

    Take an example where another developer comes in and needs to change your encryption mechanism, far better that they only have to focus on the encryption code itself than to have to wade through the unrelated logic to do with opening files etc.

    If they do need to alter the file reading/writing part of the logic, again they can focus on just that (in isolation) and as long as it doesn’t break the contract as defined by the interface, then everything should continue to work fine afterwards.

  • Yann Schwartz

    Hello. While I agree DI is a good thing, your example is not really telling, since plain old polymorphism would do nicely here, like another commenter said : there is a nice abstraction of something meant to write or read bytes, it’s called System.IO.Stream…

  • Ryan Martin

    @Yann

    I think he’s trying to illustrate a point using familiar concepts.
    You can argue the usefulness of the example as an implementation but I think he illustrates the usefulness of DI quite nicely.

  • Yann Schwartz

    @Ryan

    Oops. Sorry, I had actually read “Dependency Injection” instead of “Dependency inversion”. So, agreed, this example shows dependency inversion, in a way (even if you could get away with it with using Stream instead of your own home-cooked interfaces).

  • Chuan Li

    Simple put, a compilation module, whether a class, a method, should know as little as outside scope as possible.

  • Netizen

    Gabriel plagiarized this from Robert Martin. It’s ok to include others writing as long as the original author is given due credit. For example, the section “Why call it dependency inversion?” is copied literally word-for-word from Mr. Martin’s article: http://www.objectmentor.com/resources/articles/dip.pdf

  • http://www.lostechies.com/members/gnschenker/default.aspx Gabriel N. Schenker

    @Netizen: you are using strong words here! You are accusing me of not contributing own stuff but stealing from others and just put my name on it…
    If that is your opinion then I have nothing to say any more and just let other readers decide. But if this was not your intention then pay attention what words you use when criticizing others contributions.
    Not everybody can be an inventor. We also need engineers to make things usable in every days life. Whenever I copy things from other sources I tend to give cross references. If I forgot one then I am sorry for that. It was not by intent.

  • anon

    The part of your example that confuses me is that I don’t think the EncryptionService class should even be concerned about reading or writing to/from streams. Wouldn’t you say that the whole problem would go away if you followed the Single Responsibility Principle and exposed the private method as public? Now the service would be as mobile as possible

    I feel like this weakens your example as a whole.

  • http://recepdaban@blogspot.com recepd

    Greate article about dependency..

    Thanks.

  • Keshav Baweja

    Hi, the blog is easy to read but is a copy of Robert Martin’s post on Object Mentor.  

  • Guest

    Hi, thanks for this good post, but i feel DIP and OCP are the same, somehow. In OCP also we change dependency from concretes to abstractions till our code satisfy OCP. could you please explain me what are difference between OCP and DIP?

  • Masoud Sanaei Ardakani

    I feel when I satisfy the DIP, the OCP will be satisfy itself, maybe i didn’t understand these two principles differences, could you please explain me more the differences between DIP and OCP?