The Dependency Inversion Principle (DIP)

The principle focuses on the following two parts:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend on details. Details should depend on abstractions.

First of all, what is a high-level module and a low-level module? In short, high-level modules contain business rules, and low-level modules contain the implementation details.

According to the first part of this principle, high-level modules should only be concerned with high-level abstraction. This high-level abstraction defines what the components are, what the interactions between these components are, and what business rules these components must follow. Low-level modules should also depend on the high-level abstraction. They should not depend on other low-level modules.

According to the second part of this principle, the high-level abstraction should not depend on implementation details, which change frequently. The impact on the high-level abstraction from changes to the implementation details should be minimized so that the abstraction can stay stable. Also, when details depend on high-level abstraction, they are decoupled from each other. Hence, changes are managed inside.

Now, let's look at an example of a data export application. In this application, the Exporter class contains high-level abstraction. It uses the JdbcRepository class to query records from the database and then uses the CsvGenerator class to save the records into a file of the .csv format. After that, it will call the zip() method of the ZipCompressor class to compress the file.

In the design shown in the following diagram, the Exporter class depends directly on the JdbcRepository class, the CsvGenerator class, and the ZipCompressor class:

Figure 6.9: Rigid data export application

And here is what the Exporter class looks like:

public class Exporter {
public Exporter(JdbcRepository repository,
CsvGenerator genenerator, ZipCompressor compressor) {
// ...
}

public void export() {
List<Item> items = repository.find();
if (items.isEmpty()) {
// Business rules to handle empty result
return;
}
String csvFilePath = genenerator.generate(items);
String compressed = compressor.zip(csvFilePath);
// Business logics to handle the compressed file
}
}

As you can see, the dependencies are injected through the constructor of the Exporter class. The export() method contains the high-level abstraction of this data export application's main logic. There is no implementation detail of how to retrieve records from the database, how to generate the .csv file, or how to compress the file. Those are irrelevant here! The only thing this Exporter class cares about is the high-level abstraction of this data exporting logic.

The problem with this design is that the Exporter class depends directly on the concrete classes, and changes to these dependencies will have an impact on the Exporter class, making it unstable. This is a violation of the DIP. This not only causes the high-level abstraction not closed to change but also makes this Exporter class unable to be extended. For example, it cannot be extended to support exporting data out from MongoDB, and it cannot be extended to support exporting data in XML format. All of these changes require the export() method to be modified.

As the DIP suggest, high-level modules should depend on abstraction. Let's refactor the data export application to use the design, as shown in the following diagram:

Figure 6.10: Dependency inversion applied to the data export application

As you can see, the Exporter class now depends on the Repository interface, the Generator interface, and the Compressor interface. These interfaces are implemented by their corresponding concrete classes. To extend this application to support MongoDB, you just need to add a new implementation of the Repository interface, for example, MongoRepository, so that the Exporter class doesn't have to be modified.

In practice, we can put the Exporter class and the three interfaces in the same package and add subpackages for the implementation details, as shown in the following code:

app.dataexport.Exporter
app.dataexport.Compressor
app.dataexport.Generator
app.dataexport.Repository
app.dataexport.compressor.ZipCompressor
app.dataexport.generator.CsvGenerator
app.dataexport.repository.JdbcRepository

There are other package design options. We will talk about the principles of package design in detail when we implement the TaskAgile application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.26.217