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.