It’s the last SOLID principle: the Dependency Inversion Principle:
The general idea of this principle is simple; If high-level modules depend on low-level modules, we should change (invert) that, and both high level and low-level modules should use abstractions, not implementations.
Let’s start with the difference between high-level modules and low-level modules. High-level modules are modules that provide or use complex logic and are using other modules or classes. Low-level modules are more like ‘utility’ modules, not depending on or using other modules. Have a look at the following examples of low level and high-level code:
An example implementation of the TTaskManager class can be:
As you can see in this example, our high-level module TTaskManager depends on the low-level module TEmailer (because of the TEmailer.Create call). And that is a problem, so we should invert that dependency. Why? You’ll figure that out later on.
Both low-level and high-level modules should also depend on abstractions. How do we change this? The simplest way is to use interfaces. An interface is by definition an abstraction, so by using interfaces instead of classes, we remove the dependency on implementations. As an extra benefit, we meet the second requirement of the Dependency Inversion as well, because an interface will not have the knowledge of the details involved of how things get done in the actual implementation.
Since we are concentrating here on explaining the fifth Solid principle, I will not explain in detail how to add an interface to a class and how to use it. In the previous posts about the Solid principles, we have done that several times. In the end, we will have the following interfaces:
The implementation of classes becomes as follows:
Ok, so let’s start our transition to meet the Dependency Inversion principle by changing this example of the TTaskManager. There is more than one way of doing this. One is by using Dependency Injection. Dependency Inversion is the principle, and Dependency Injection is a way of making this principle work. However, it’s not mandatory to use Dependency Injection. Let me explain this.
Think of the dependency of our TTaskManager on TEmailer. Where does this dependency actually happen? The easiest way of identifying dependencies is by looking for .Create calls. In this case, the line
introduces a dependency on the TEmailer class. In essence, every .Create call is a dependency on another class. As we have said, we do not want high-level modules to depend on low-level modules. So, for this class, we can use Dependency Injection to solve this. It is very simple; just inject the dependency via the constructor of the TTaskManager class:
We now have injected the dependency on the emailer (or actually, to the IMessageSender) class, so we can use it without any problem:
And that is just how dependency injection works. As long as you consequently push the creation of classes back (i.e. do not use the .Create anywhere) you will automatically use Dependency Injection.
But where does this lead us? In the end you have to create a class somewhere, right?
If you push back the creation of classes, you will end up in the program files (the DPR). Have a look at my example for this TaskManager example program:
In the example code, we create two tasks, add the tasks to the Taskmanager and complete one task. We create only classes in the DPR right now. But isn’t the DPR in fact itself also a high-level module? I think it is, and again, we should not create a dependency in a high-level module.
I’ve shown you the way of doing this in classes via Dependency Injection. Here I’d like to show you how it’s done via a factory class. The basic role of a factory class is to provide instances of interfaces. And as we are at the top level of our program, this is the place where we will create our classes. Let’s create a new unit and create our factory class in this new unit. As this is a factory class, we’ll use class functions to give back instances of classes:
Let’s look at the implementation of the CreateTaskManager function for example:
and for the CreateMessageSender function:
and lastly to the DPR file again:
To summarise what we have done so far: In order to comply with the fifth Solid principle, we first use dependency injection to bring up the dependencies as far as possible. Because of this, no create call is used in the classes anywhere. Secondly, we converted the code to interfaces, so that we are no longer dependent on details, but on abstractions. And finally, we created one factory class in which all objects are created.
Finally, let’s see what advantage this gives us. In the code above, we have an Emailer class that takes care of sending an email when a task is completed. Now suppose we don’t want to do this via email, but via a text message. In the old situation we would have had to change the TEmailer instance everywhere it’s used to TTexter. This is because high-level modules were dependent on these low-level modules. In the new structure, it is very easy to change this. We only need to change the CreateMessageSender class in the ClassFactory:
and with just one line of code, the whole application is adapted to the new requirements. Taking the other Solid principles into account (Liskov Substitution, Open Closed), we can make this adjustment without any problems. But there are more benefits. Let’s say we want to add unit tests to our application. If you just create a unit test project and test this application 50 times a day, you would end up with 50 emails, or 50 text messages in your inbox. So, in your unit test project, you can now simply have another factory class, with a dummy or mock implementation of the MessageSender class. And that’s all, no more emails or text messages.
So, that is the last one of the five SOLID principles. I hope you got a lot of useful information which you can use when developing in Delphi. I am convinced that your applications will be more robust, more readable and more extensible if you apply the five SOLID principles consistently when developing. Next time I will give a short summary and refresher of the principles. Thanks, and see you soon!