Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Java 9 Dependency Injection

You're reading from   Java 9 Dependency Injection Write loosely coupled code with Spring 5 and Guice

Arrow left icon
Product type Paperback
Published in Apr 2018
Publisher Packt
ISBN-13 9781788296250
Length 246 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Authors (2):
Arrow left icon
Nilang Patel Nilang Patel
Author Profile Icon Nilang Patel
Nilang Patel
Krunal Patel Krunal Patel
Author Profile Icon Krunal Patel
Krunal Patel
Arrow right icon
View More author details
Toc

Inversion of Control

IoC is a design methodology used to build a loosely coupled system in software engineering by inverting the control of flow from your main program to some other entity or framework.

Here, the control refers to any additional activities a program is handling other than its main activities, such as creating and maintaining the dependency objects, managing the application flow, and so on.

Unlike procedural programming style, where a program handles multiple unrelated things all together, IoC defines a guideline where you need to break the main program in multiple independent programs (modules) based on responsibility and arrange them in such a way that they are loosely coupled.

In our example, we break the functionality into separate modules. The missing part was how to arrange them to make them decoupled, and we will learn how IoC makes that arrangement. By inverting (changing) the control, your application becomes decoupled, testable, extensible, and maintainable.

Implementing DIP through IoC

DIP suggests that high-level modules should not depend on low-level modules. Both should depend on abstraction. IoC provides a way to achieve the abstraction between high-level and low-level modules.

Let's see how we can apply DIP through IoC on our Balance Sheet example. The fundamental design problem is that high-level modules (balance sheet) tightly depend on low-level (fetch and export data) modules.

Our goal is to break this dependency. To achieve this, IoC suggests inverting the control. In IoC, inverting the control can be achieved in the following ways:

  • Inverting the interface: Make sure the high-level module defines the interface, and low-level modules follow it
  • Inverting object creation: Change the creation of dependency from your main modules to some other program or framework
  • Inverting flow: Change the flow of application

Inverting the interface

Inverting the interface means inverting the interaction control from low-level modules to high-level modules. Your high-level module should decide which low-level modules can interact with it, rather than keep changing itself to integrate each new low-level module.

After inverting the interface, our design would be as per the following diagram:

In this design, the balance sheet module (high-level) is interacting with fetch data and export data (low-level) modules with common interface. The very clear benefits of this design are that you can add new fetch data and export data (low-level) modules without changing anything on the balance sheet module (high-level).

As far as low-level modules are compatible with the interface, the high-level modules will be happy to work with it. With this new design, high-level modules are not dependent on low-level modules, and both are interacting through an abstraction (interface). Separating the interface from the implementation is a prerequisite to achieve DIP.

Let's change our code as per this new design. First, we need to create two interfaces: to fetch the data and export the data as follows:

public interface IFetchData {
//Common interface method to fetch data.
List<Object[]> fetchData();
}
public interface IExportData {
//Common interface method to export data.
File exportData(List<Object[]> listData);
}

Next, all low-level modules must implement these interfaces as per the following snippet:

public class FetchDatabase implements IFetchData {
public List<Object[]> fetchData(){
List<Object[]> dataFromDB = new ArrayList<Object[]>();
//Logic to call database, execute a query and fetch the data
return dataFromDB;
}
}

public class FetchWebService implements IFetchData {
public List<Object[]> fetchData(){
List<Object[]> dataFromWebService = new ArrayList<Object[]>();
//Logic to call Web Service and fetch the data and return it.
return dataFromWebService;
}
}

public class ExportHTML implements IExportData{
public File exportData(List<Object[]> listData){
File outputHTML = null;
//Logic to iterate the listData and generate HTML File
return outputHTML;
}
}
public class ExportPDF implements IExportData{
public File exportData(List<Object[]> dataLst){
File pdfFile = null;
//Logic to iterate the listData and generate PDF file
return pdfFile;
}

}

Finally, the balance sheet  module needs to rely on interfaces to interact with low-level modules. So the updated BalanceSheet module should look like the following snippet:

public class BalanceSheet {
private IExportData exportDataObj= null;
private IFetchData fetchDataObj= null;

public Object generateBalanceSheet(){
List<Object[]> dataLst = fetchDataObj.fetchData();
return exportDataObj.exportData(dataLst);
}
}

You may have observed that, the generateBalanceSheet() method became more straightforward. It allows us to work with additional fetch and export modules without any change. It is thanks to the mechanism of inverting the interface that makes this possible.

This design looks perfect; but still, there is one problem. If you noticed, the balance sheet module is still keeping the responsibility of creating low-level module objects (exportDataObj and fetchDataObj). In other words, object creation dependency is still with the high-level modules.

Because of this, the Balance Sheet module is not 100 percent decoupled from the low-level modules, even after implementing interface inversion. You will end up instantiating low-level modules with if/else blocks based on some flag, and the high-level module keeps changing for adding additional low-level modules integration.

To overcome this, you need to invert the object creation from your higher-level module to some other entity or framework. This is the second way of implementing IoC.

Inverting object creation 

Once the abstraction between modules is set, there is no need to keep the logic of creating dependency objects in higher-level modules. Let us understand the importance of inversion of object creation design with one more example.

Suppose you are designing a war game. Your player can shoot the enemy with various weapons. You created separate classes (low-level module) for each of the weapons. While playing the game, your player can add the weapon based on points earned.

Also, the player can change the weapon. To implement inversion of interface, we created an interface called Weapon, which will be implemented by all weapon modules, as per the following diagram:

Assume that there are three weapons initially that you kept in the game. If you keep weapon creation code in your player module, the logic of choosing a weapon would be as per the following snippet:

public class Player {
private Weapon weaponInHand;
public void chooseWeapon(int weaponFlag){
if(weaponFlag == 1){
weaponInHand = new SmallGun();
}else if(weaponFlag ==2){
weaponInHand = new Rifle();
}else{
weaponInHand = new MachineGun();
}
}

public void fireWeapon(){
if(this.weaponInHand !=null){
this.weaponInHand.fire();
}
}
}

Since the player module is taking care of creating the object of weapons, we are passing a flag in the chooseWeapon() method. Let us assume that, over a period of time, you add a few more weapons to the game. You end up changing the code of the Player module every time you add a new weapon.

The solution to this problem is to invert the object creation process from your main module to another entity or framework.

Let's first apply this solution to our Player module. The updated code would be as follows:

public class Player {
private Weapon weaponInHand;
public void chooseWeapon(Weapon setWeapon){
this.weaponInHand = setWeapon;
}

public void fireWeapon(){
if(this.weaponInHand !=null){
this.weaponInHand.fire();
}
}
}

You can observe the following things:

  • In the chooseWeapon() method, we are passing the object of weapons through the interface. The Player module is no longer handling the creation of weapon objects.
  • This way, the Player (higher-level) module is completely decoupled from Weapon (low-level) modules.
  • Both modules interact through the interface, defined by higher-level modules.
  • For any new weapon added into the system, you do not need to change anything in the player module.

Let's apply this solution (invert creating object) to our balance sheet module. The updated code for the BalanceSheet module would be as per the following snippet:

public class BalanceSheet {

private IExportData exportDataObj= null;
private IFetchData fetchDataObj= null;

//Set the fetch data object from outside of this class.
public void configureFetchData(IFetchData actualFetchDataObj){
this.fetchDataObj = actualFetchDataObj;
}
//Set the export data object from outside of this class.
public void configureExportData(IExportData actualExportDataObj){
this.exportDataObj = actualExportDataObj;
}

public Object generateBalanceSheet(){
List<Object[]> dataLst = fetchDataObj.fetchData();
return exportDataObj.exportData(dataLst);
}
}

Here are some quick observations:

  • Objects of fetch data and export data modules are created outside the balance sheet module, and passed through configureFetchData() and configureExportData() methods
  • The balance sheet module is now  100 percent decoupled from fetch data and export data modules
  • For any new type of fetch and export data, no change is required in balance sheet modules

At this moment, the relation between DIP and IoC can be described as per the following diagram:

Finally, we implemented DIP through IoC and solved one of the most fundamental problems of interdependency between modules.

But hold on, something is not complete yet. We have seen that keeping the object creation away from your main module will eliminate the risk of accommodating changes and make your code decoupled. But we haven't explored how to create and pass the dependency object from outside code into your module. There are various ways of inverting object creation.

Different ways to invert object creation

We have seen how inversion of object creation helps us to decouple the modules. You can achieve the inversion of object creation with multiple design patterns as follows:

  • Factory pattern
  • Service locator
  • Dependency injection

Inversion of object creation through the factory pattern

The factory pattern takes the responsibility of creating an object from a client who uses it. It generates the object of classes that are following a common interface. A client has to pass only type of the implementation it wants and the factory will create that object.

If we apply the factory pattern to our balance sheet example, the process of inverting of object creation is depicted as per the following diagram:

  • Client (in our case, it's a balance sheet module) talks to the factory—Hey factory, can you please give me the fetch data object? Here is the type.
  • The factory takes the type, creates the object, and passes it to the client (the balance sheet module).
  • The factory can create the object of the same type only.
  • The factory class is a complete black box for its clients. They know it's a static method to get objects.

The Balance Sheet module can get FetchData objects from FetchDataFactory. The code of FetchDataFactory will be as follows:

public class FetchDataFactory {
public static IFetchData getFetchData(String type){
IFetchData fetchData = null;
if("FROM_DB".equalsIgnoreCase(type)){
fetchData = new FetchDatabase();
}else if("FROM_WS".equalsIgnoreCase(type)){
fetchData = new FetchWebService();
}else {
return null;
}
return fetchData;
}
}

To use this factory, you need to update the configureFetchData() method of a balance sheet module as follows:

//Set the fetch data object from Factory.
public void configureFetchData(String type){
this.fetchDataObj = FetchDataFactory.getFetchData(type);
}

For export data, you need to create a separate factory as per the following snippet:

public class ExportDataFactory {

public static IExportData getExportData(String type){
IExportData exportData = null;
if("TO_HTML".equalsIgnoreCase(type)){
exportData = new ExportHTML();
}else if("TO_PDF".equalsIgnoreCase(type)){
exportData = new ExportPDF();
}else {
return null;
}
return exportData;
}
}

If a new fetch data or export data type is introduced, you need to change it in its respective factory class only.

Inversion of object creation through service locator

The service locator pattern works more or less the same as to the factory pattern. The service locator can find the existing object and send it to the client rather than create a new one every time, as with the factory pattern. Instead of getting into detail, we will just look briefly at how the service locator works to create objects. The flow of the service locator can be described as per the following diagram:

  • Client is relying on Service Locator to find services. Here, service means any kind of dependency
  • Service Locator takes the name of the service, and returns the object of service back to the client

If our balance sheet module uses the service locator, the code of the configureFetchData() method would be like the following snippet:

//Set the fetch data object from ServiceLocator.
public void configureFetchData(String type){
this.fetchDataObj = FetchDataServiceLocator.Instance.getFetchData(type);
}

Similar to fetch data, you need to design a separate service locator for export data. For any new fetch data or export data type, the changes need to be done in the service locator.

Another way of inverting the object creation is DI.

You have been reading a chapter from
Java 9 Dependency Injection
Published in: Apr 2018
Publisher: Packt
ISBN-13: 9781788296250
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime