Dependency Inversion Principle

I’m developer for a couple of years now and I’ve had the opportunity to work with various technologies like J2EE, .NET or PHP. Some of projects were from scratch developement and others were just bug fixing or change request implementation.

Multiples times I’ve seen projets having symptoms of what Robert C. Martins calls "bad design":

  • It’s hard to modify a single component – without impacts on others (rigidity)
  • A change break other parts of the applications (fragility)
  • It’s hard to reuse existing code (immobility)
  • It’s hard to test a single component without its dependencies (lack of testability)

I’m sure you have already faced to same situations. Isn’t it?

In this post I want to present you a design principle that will help you to improve the global quality of you code in terms of flexibility, testability, maintenability and reusability.

Furthermore this principle is language and framwork independant and it is all the more interesting that it is simple to implement.

Example

I will take a very simple example in order to facilitate the understanding. Let’s consider an application which read an XML file and create the corresponding JSON file. Here is the class diagram:

Class diagram of the basic example

The implementation of these classes is in C# and I deliberately simplified them in order to stay focused on the essentials.

The FileTransformer class uses a XmlReader and a JsonWriter in order to create a JSON representation of the XML file. The "Transform()" method of this class just loops until the end of the Xml File and writes in a JSON format what the method "Read()" of the XmlReader returns.

namespace DependencyInversionPost
{
    public class FileTransformer
    {
        private XmlReader _reader;
        protected XmlReader Reader
        {
            get
            {
                return this._reader;
            }
            set
            {
                this._reader = value;
            }
        }
        private JsonWriter _writer;
        protected JsonWriter Writer
        {
            get
            {
                return this._writer;
            }
            set
            {
                this._writer = value;
            }
        }
        public FileTransformer()
        {
            this.Reader = new XmlReader();
            this.Writer = new JsonWriter();
        }
        public void Transform()
        {
            while (!this.Reader.IsEnd())
            {
                this.Writer.Write(this.Reader.Read(());
            }
        }
    }
}

Here are a blueprint of what XmlReader and JsonWriter should/could look like.

namespace DependencyInversionPost
{
    class XmlReader
    {
        public bool IsEnd()
        {
            //test if we are at the end of the file
            throw new NotImplementedException();
        }
        public String Read()
        {
            //return a String corresponding to the actual XML element
            throw new NotImplementedException();
        }
    }
}
namespace DependencyInversionPost
{
    public class JsonWriter
    {
        public void Write(String element)
        {
            //Write the JSON object corresponding to the String element given in parameter
            throw new NotImplementedException();
        }
    }
}

Now we have a program that converts XML file in a corresponding JSON file. The job is done and I’m happy. I’m so proud of it that I will publishing it on github! Everybody will be able to change it, reuse its components or extend it… Why not create new reader or writer types?!

However the problem is that the only reusable components are XMLReader and JsonWriter. The FileTransformer isn’t. The reason is that this class is directly dependant of the two others. By instantiating JsonWriter and XmlWriter directly in the FileTransformer constructor I create hard coded dependencies. In other words my high level component is dependant of lower level modules.

If somebody wants to add new Reader or Writer classes he has to modify the FileTransformer implementation. Normally a high level module should never be altered to use a new implementation of a low level module.

If we consider symptoms of a bad design listed above and apply them to the actual example we can say that:

  • only some parts of the application could be reused
  • it’s impossible to test the FileTransformer class without testing simultaneously XmlReader and JsonWriter classes
  • the adding of new features (new readers and writers) involves the edition of other components

The Dependency Inversion

The dependency inversion principle says:

  1. High level modules should not depend upon low level modules. Both should depend upon abstraction
  2. Abstractions should not depend upon details. Details should depend upon abstractions.

Following this principle let’s try to improve our example. What we have to do is to remove dependencies and create abstractions.
To create abstraction we will introduce interfaces that Reader and Writer classes have to implement.
To remove dependencies we can:

  • pass reader and writer objects as parameters of the FileTransformer constructor
  • pass reader and writer objects as parameters of the "Transform()" method
  • create setter methods to pass reader and writer objects

Generally I choose to use the first option. I prefer to have a fully working object directly after its instantiation rather than having to configure it with setters.

Here is the class diagram of a loosely coupled implementation of the example
Diagramme de classe 2

namespace DependencyInversionPost
{
    public interface IFileTransformer
    {
        public void Transform();
    }
}

The high level module depends upon abstractions (IFileReader and IFileWriter) which are passed as contructor parameters.

namespace DependencyInversionPost
{
    public class FileTransformer : IFileTransformer
    {
        private IFileReader _reader;
        protected IFileReader Reader
        {
            get
            {
                return this._reader;
            }
            set
            {
                this._reader = value;
            }
        }
        private IFileWriter _writer;
        protected IFileWriter Writer
        {
            get
            {
                return this._writer;
            }
            set
            {
                this._writer = value;
            }
        }
        public FileTransformer(IFileReader reader, IFileWriter writer)
        {
            this.Reader = reader;
            this.Writer = writer;
        }
        public void Transform()
        {
            while (!this.Reader.IsEnd())
            {
                this.Writer.Write(this.Reader.Read(());
            }
        }
    }
}
namespace DependencyInversionPost
{
    public interface IFileReader
    {
        bool IsEnd();
        String Read();
    }
}
namespace DependencyInversionPost
{
    class XmlReader : IFileReader
    {
        public bool IsEnd()
        {
            //test if we are at the end of the file
            throw new NotImplementedException();
        }</p>
<p>        public String Read()
        {
            //return a String corresponding to the actual XML element
            throw new NotImplementedException();
        }
    }
}
namespace DependencyInversionPost.V2
{
    public interface IFileWriter
    {
        void Write(String element);
    }
}
namespace DependencyInversionPost
{
    public class JsonWriter : IFileWriter
    {
        public void Write(String element)
        {
            //Write the JSON object corresponding to the String element given in parameter
            throw new NotImplementedException();
        }
    }
}

After these changes:

  • The high level module (FileTransformer) doesn’t depend upon details anymore (XmlReader and JsonWriter) but upon abstractions (IFileReader and IFileWriter)
  • Details (XmlReader and JsonWriter) depend upon (or implement) abstractions (IFileReader and IFileWriter)

Now all modules of our application are reusable. You can easly create new writers or readers and use the same implementation of FileTransformer with them.

The switch between specific detail implementations doesn’t involve a modification of the high level module.

Furthermore you can easly test the FileTransformer class without having to test details. You have now the possibility to create stub/mock objects.

Finally the global quality of the application is improved: more of flexibility, maintnability and reusability.

Conclusion

In this blog post I tried to present you by a pragmatic approach and very important principle in object oriented programming. Applying it will improve the quality of your code in terms of flexibility, testability, maintnability and reusability.

Resources:

About these ads

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s