This is another one in the series of OOP design principles after Law Of Demeter.
A software design is made of objects and communication between them. Eventually it becomes a mesh of interdependencies between these objects, which combined with business constraints like money or time, drives bad software design.
Robert Martin (pdf) defines bad software design by its three basic attributes.
A piece of software that fulfills its requirements and yet exhibits any or all of the following three traits has a bad design:
- It is hard to change because every change affects too many parts of the system. (Rigidity)
- When you make a change, unexpected parts of the system break. (Fragility)
- It is hard to reuse in another application because it cannot be disentangled from the current application. (Immobility)
Whenever a functionality is implemented, it can be divided in the high level modules and low level modules. The high level modules are usually the representative of the concept of the activity, whereas the low level activities are the deatils of the high level module. e.g., the activity of logging (the high level module) can be divided into:
- formation of message to be logged
- write the message into a target destination
When implemented, the lower level modules can have specific code, like the module for writing the message might write to a flat file. Consider the following algorithm of the logging module:
Later when the logging has to happen to a database, the algorithm for logging module would have to change to:
Here, a change in the low level module has caused change in the high level module. This means that we cannot reuse the logging module without using the implementation of the low level module for writing the message.
Everything was fine at a conceptual level, the problems happened during implementation. Why not include the concept in the design itelf? This can be done by introducing abstractions. Consider the following algorithm:
logging: form_message write_message write_message: write_message_to_disk
We have introduced a level of indirection here, the logging module does not communicate directly with the module for writing message to the disk. It talks to the module for writing message, which can keep all its implementation changes internal to itself. Now the logging module is independent of the implementation of writing the message, and can be reused by itself.
This is what the general formulation of the Dependency Inversion Principle states:
High level modules should not depend upon low level modules. Both should depend upon abstractions.
Abstractions should not depend upon details. Details should depend upon abstractions.
What this really means is that we have separated the concept and the implementation physically by introducing abstraction. This defines a very powerful rule for designing and programming: Design to an interface, not an implementation, i.e., a class that wants to log should be designed against the high-level module and not the low-level module.
This principle also is based on the advantage of loose coupling between objects, which makes the design more flexible and reusable.
- Lateral Thinking and Software Engineering
- Law Of Demeter and Object Oriented Programming
- What Is Interface?
Back to Design Principles.
Copyright Abhijit Nadgouda.