We start our example with a service class in which the concerns are not separated. Then we will improve maintainability step-by-step, by first separating concerns and then programming to interface in order to make our modules loosely coupled. At the final point, we will have our first DI application. The source code for all the examples of this book is available for download on the publisher's website.
The main responsibility of this service is to send an e-mail using the information provided. In order to make the example simple and clear, client initialization is omitted.
Then, we add some logging to it, so that we know what is going on in our service:
After a little while, we find it useful to add time to our logs. In this example, sending the mail message and logging functionality are two different concerns which are addressed in a single class, and it is not possible to change the logging mechanism without touching the MailService
class. Therefore, in order to add time to our logs, we have to change the MailService
class. So, let's re-factor this class and separate the concern of logging from sending a mail prior to adding the time functionality:
The ConsoleLogger
class is only responsible for the logging mechanism, and this concern is removed from MailService
. Now, it is possible to modify the logging mechanism without affecting MailService
:
Now, we need to write our logs in Windows Event Log rather than showing them in console. Looks like we need an EventLogger
class as well:
Although the concern of sending mail and logging are now separated in two different classes, MailService
is still tightly coupled to the
ConsoleLogger
class, and it is not possible to replace its logger without modifying it. We are just one step away from breaking the tight coupling between the MailService
and Logger
classes. We should now introduce the dependencies as interfaces rather than concrete classes:
Both the ConsoleLogger
and EventLogger
classes should implement this interface:
Now, it is time to remove the references to the concrete ConsoleLogger
class and address ILogger
instead:
But the previous code won't compile because it doesn't make sense to instantiate an interface. We should introduce this dependency as a constructor parameter and have the concrete object injected into it by a third party:
At this point, our classes are loosely coupled and we can change the loggers freely without affecting the
MailService
class. Using DI, we have also separated the concern of creating a new instance of the logger class, which includes the concern of deciding what concrete logger to use from the main responsibility of MailService
, which is sending an e-mail:
The main method of this application is where we decide what concrete objects to inject in our dependent classes. This (preferably) unique location in the application where modules are composed together is named Composition Root
by Mark Seemann. For more information on DI, Dependency Injection in .NET, by Mark Seemann is recommended.