Java 9 Dependency Injection
上QQ阅读APP看书,第一时间看更新

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.