Dependency Injection And Inversion Of Control — Part 2: Implement DI
Tags: Developer Tools, Android App Development, Developer, Programming, Coding

The Dependency Injection and Inversion of Control article series consists of 3 parts:

Implement DI to code

You have read part 1 but still, do not understand much about DI, IoC, do not know how to apply them to code? Do not worry, in part 2 will provide the sample code, explain more clearly about what I said in part 1. After reading this section, you return to part 1, you will see “through” it.

What is dependency?

Dependency is low-level modules or services that are called from outside. With normal code, high-level modules call low-level modules. The high-level module will depend and the low-level module will create dependencies.

To understand it, see the Checkout function of the Cart class below. This function will save the order to the database and email it to the user. The Cart class will initialize and call the Database module, EmailSender module, Logger module, these modules are dependencies.

public class Cart {

public void Checkout(int orderId, int userId) {

Database db = new Database();

db.Save(orderId);



Logger log = new Logger();

log.LogInfo(“Order has been checkout”);



EmailSender es = new EmailSender();

es.SendEmail(userId);

}

}

Is there anything wrong with this method? It seems no, writing code is fast too. But this style of writing “could” lead to some future problems:

It is difficult to test this Checkout function, as it involves both Database and EmailSender modules.

In case we want to change the Database module, EmailSender, … we have to fix all initialization places and call these modules. Doing this is time-consuming, error-prone.

In the long term, the code will become “cohesive”, the module has high cohesion, a changing module will entail a series of changes. This is a nightmare to maintain code.

Inversion of Control and Dependency Injection were born to solve these problems.

How to restrict coupling between classes. Inversion of Control available

In order for the modules not to “stick” with each other, they are not directly connected, but rather through the interface. That is the last principle in SOLID.

1. High-level modules should not depend on low-level modules. Both should depend on abstraction.

2. Interface (abstraction) should not depend on details, but vice versa. (Classes communicate with each other through interfaces, not implementations.)

We, in turn, create interfaces IDatabase, IEmailSender, ILogger, the other classes will initially inherit these interfaces in turn. To make it easier to understand, I will now temporarily call IDatabase, IEmailSender, ILogger as Interface, classes like Database, EmailSender, Logger as Modules.

// Interface

public interface IDatabase

{

void Save(int orderId);

}



public interface ILogger

{

void LogInfo(string info);

}



public interface IEmailSender

{

void SendEmail(int userId);

}



// Module implement Interface

public class Logger : ILogger

{

public void LogInfo(string info)

{

//…

}

}



public class Database : IDatabase

{

public void Save(int orderId)

{

//…

}

}



public class EmailSender : IEmailSender

{

public void SendEmail(int userId)

{

//…

}

}

The new checkout function will look like this:

public void Checkout(int orderId, int userId)

{// If you want to change the database, we just need to change the code below

// Modules XMLDatabase, SQLDatabase must implement IDatabase

// IDatabase db = new XMLDatabase ();

// IDatebase db = new SQLDatabase ();

IDatabase db = new Database ();

db.Save (orderId); ILogger log = new Logger ();

log.LogInfo (“Order has been checkout”); IEmailSender es = new EmailSender ();

es.SendEmail (userId);

}

With the interface, we can easily change, swap low-level modules without affecting the Cart module. This is the first step of IoC.

For ease of management, we can put all module initialization functions into the constructor of the Cart class.

public class Cart

{

private readonly IDatabase _db;

private readonly ILogger _log;

private readonly IEmailSender _es;



public Cart ()

{

_db = new Database ();

_log = new Logger ();

_es = new EmailSender ();

}



public void Checkout (int orderId, int userId)

{

_db.Save (orderId);

_log.LogInfo (“Order has been checkout”);

_es.SendEmail (userId);

}

}

This way looks good, at first. However, if there are many other modules that need to use Logger, Database, we have to initialize the Sub-Modules in the constructor of that module. It doesn’t look right, does it?

First, people used ServiceLocator to solve this problem. For each Interface, we set a corresponding Module. When needed, we will take that Module from ServiceLocator. This is also a way to implement IoC.

public static class ServiceLocator

{

public static T GetModule()

{

//….

}

}



//We just call function GetModule

public class Cart

{

public Cart()

{

_db = ServiceLocator.GetModule();

_log = ServiceLocator.GetModule();

_es = ServiceLocator.GetModule();

}

}

This is still a problem: all classes depend on ServiceLocator.

Dependency Injection solves this problem. The low-level modules will be injected (passed into) the high-level modules through the constructor or through properties. Put simply, easy to understand about DI:

We do not call the new operator to instantiate the instance, but that instance will be passed in from outside (Manual transmission, or thanks to DI Container).

After applying Dependency Injection, we will use the Cart class as follows:

public Cart (IDatabase db, ILogger log, IEmailSender es)

{

_db = db;

_log = log;

_es = es;

}



// Dependency Injection in the simplest way

Cart myCart = new Cart (new Database (),

new Logger (), new EmailSender ());

// When need to change database, logger

myCart = new Cart (new XMLDatabase (),

new FakeLogger (), new FakeEmailSender ());

You probably think: After using Dependency Injection, you must initialize the Module, that’s worse than ServiceLocator. Normally, we use DI Container. Just define once, DI Container will automatically inject low-level modules into high-level modules.

// For each Interface, we define a corresponding Module

DIContainer.SetModule <IDatabase, Database> ();

DIContainer.SetModule <ILogger, Logger> ();

DIContainer.SetModule <IEmailSender, EmailSender> ();



DIContainer.SetModule <Cart, Cart> ();



// DI Container will inject Database, Logger itself into Cart

var myCart = DIContainer.GetModule ();



// When needing to change, we just need to fix the code define

DIContainer.SetModule <IDatabase, XMLDatabase> ();

After applying Dependency Injection, your code will be longer, seem more “complicated” and will be harder to debug. In return, the code will be flexible, easy to change and easier to test.

As I said in the previous post, DI is not always the right choice, we need to consider the pros and cons. DI is applied in many back-end frameworks (ASP.MVC, Struts2) and front-end (AngularJS, KnockoutJS). Most of the big projects in IT companies apply DI, so knowledge of DI will be very helpful when interviewing and working.

-

So where does the above DI Container come from? We can write by ourselves, or use some popular DI Containers in C # such as Unity, StructureMap, NInject. In part 3, I will show you how to write a simple DI Container and use the existing DI Containers.