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.
To be more concrete, for example, we can paraphrase this as
Business logic should not depend on implementation details
So, let’s look at an example, and refactor it iteratively.
The first iteration:
The main flaw of this example is the usage of hard-coded dependency, thus it violates both Open/Closed principle and Dependency Inversion Principle.
The business logic is simple - deliver messages to selected user accounts. But the business logic here depends on implementation details -
call method ‘has knowledge’ about which exact service class will be performing message delivery. MobileMessageService contains implementation
details, which we should replace by an abstraction. Also, this would allow us to introduce, for example, WebMessageService in the future releases,
which will contain another low-level implementation of this business logic. To refactor this example
to ensure usage of dependency inversion, we should replace hard-coded class by an abstraction.
Now we have replaced MobileMessageService by a variable, which we pass to the PushNotification class in call
method. Now we can pass to this class any other performer class, as long as it responds to deliver message with given signature.
This example illustrates, how implementation details can be replaced by abstraction.
So, let’s move onto the third iteration, where we introduce a couple of new performer classes with the help of dry-container
Here we initialized Dry::Container instance and registered our performer classes. Now we can refer to them using resolve method:
And we can go even further and provide access to the app’s all classes as small, component-like building blocks, using dry-auto_inject gem:
In this example, we have made our way from simple, but unconfident ruby class to the scalable code, consisting of loosely coupled, small building blocks. Of course, examples of code are all short and way contrived, but with the further growth of application, the necessity of decoupling using dependency inversion will become more and more obvious.