Clean Architecture by Robert Martin states, "Any source code dependency, no matter where it is can be inverted." This is known as the Dependency Inversion Principle (DIP), which is illustrated in the first diagram. Martin argues that this is the primary benefit of Object-Oriented Programming.
He states, "It allows the architect to create a plugin architecture, in which modules that contain high-level policies are independent of modules that contain low-level details."
To illustrate this point, we can create two fictional classes:
- PathFollower: A high-level class for directing overall robot motion.
- PIDController: A mid-level class for controlling wheel velocity.
A naive implementation of how we might design these classes is shown in the second diagram.
The dependency-inverted implementation is shown in the third diagram. Notice that PathFollower
does not inherit from PIDController
.
A few consequences of this inversion are:
- Any controller that adheres to the interface (an abstract class) defined by
ControllerInterface
can be swapped in with minimal effort. For instance,MPCController
can be linked to our executable (instead ofPIDController
) without any changes to the existing source code. We just compileMPCController
as a shared library and modify the linking behavior at compile-time viaCMake
arguments. main
andPathFollower
do not need to be recompiled ifPIDController
changes.main
will just be linked with the recompiledPIDController
.- As promised above, "high-level policies (
PathFollower
) are independent of modules that contain low-level details (PIDController
). - Both modules (
PathFollower
andPIDController
) depend on abstractions, not implementation details.
C++ and Python examples of both the Naive and DIP implementations were built using the references linked below.
In C++, an abstract class with virtual methods defines an interface (ControllerInterface
) which specifies a contract that all concrete implementations of that abstract class (PIDController
) must meet. A concrete implementation (ControllerImpl
) of the abstract class is declared and promised within controller_interface.h
. The actual definition of the concrete implementation is provided via linked library at compile-time.
To compile:
cd dependency-inversion
mkdir build && cd build
# Set cmake arguments as you desire
cmake .. -DLINK_PID=OFF -DLINK_MPC=ON
make
To run the naive example:
cd dependency-inversion/build
./naive_approach
To run the dependency inverted example:
cd dependency-inversion/build
./di_approach
In Python, an abstract (ABC
) class with @abstractmethod
methods defines an interface (ControllerInterface
) which specifies a contract that all concrete implementations of that abstract class (PIDController
) must meet.
To run the naive example:
cd dependency-inversion/python
python naive.py
To run the dependency inverted example:
cd dependency-inversion/python
python di_approach.py