This content originally appeared on DEV Community and was authored by Josué Rodríguez
The Dependency Inversion Principle states that entities must depend on abstractions, not on concretions. High-level modules should not depend on low-level modules. Both should depend on abstraction.
High-level modules should not depend on low-level modules. Both should depend on abstraction.
When we talk about high-level modules we are referring to a class that executes an action implementing a tool or library, and when we talk about low-level modules we are referring to the tools or libraries that are needed to execute an action.
The principle allows for decoupling, which means to separate, disengage or dissociate something from something else. This helps us by reducing dependency and allowing for easier implementations of other tools in the future.
Example
Let's imagine that we have a Candy Store and we are developing the checkout process. In the beginning, we only planned to implement Stripe as our payments processor. Stripe needs for the amount to be passed on as cents to make the transaction. Our classes will look something like this:
//Checkout.js
class Checkout {
constructor() {
this.paymentProcessor = new Stripe('USD');
}
makePayment(amount) {
//Multiplying by 100 to get the cents
this.paymentProcessor.createTransaction(amount * 100);
}
}
//Stripe.js
//Custom Stripe implementation that calls the Stripe API
class Stripe {
constructor(currency) {
this.currency = currency;
}
createTransaction(amount) {
/*Call the Stripe API methods*/
console.log(`Payment made for $${amount / 100}`);
}
}
Notice that we created a dependency between our Checkout
class (high-level module) and Stripe
(low-level module), violating the Dependency Inversion Principle. The dependency is especially noticeable when we convert the amount to cents. The Checkout
should not care about which payment processor is being used, it only cares about making a transaction.
To decouple these two modules, we would have to implement an intermediary between the checkout and the payment processor, creating an abstraction so that no matter what payment processor we use, the Checkout
class will always work with the same method calls. The new PaymentProcessor
class will be in charge of adapting everything to payment processor to be used (in this case, Stripe). The intermediary class will have the following code:
//PaymentProcessor.js
class PaymentProcessor {
constructor(processor, currency) {
this.processor = processor;
this.currency = currency;
}
createPaymentIntent(amount) {
const amountInCents = amount * 100;
this.processor.createTransaction(amountInCents);
}
}
As you can see, the createPaymentIntent
on the PaymentProcessor
class is converting the amount to cents. And now we refactor the Checkout class to implement the abstraction:
//Checkout.js
class Checkout {
constructor() {
this.paymentProcessor = new PaymentProcessor(new Stripe('USD'), 'USD');
}
makePayment(amount) {
this.paymentProcessor.createPaymentIntent(amount);
}
}
Now, if we ever need to change our payment processor, we can do so by passing the new processor instead of Stripe on the Checkout
constructor.
Imagine that now we are asked to replace Stripe with another payment processor that does not require for the amount to be converted to cents but on every transaction asks for the currency that's going to be used. The resulting code will be the following:
//Checkout.js
class Checkout {
constructor() {
this.paymentProcessor = new PaymentProcessor(new BetterProcessor(), 'USD');
}
makePayment(amount) {
this.paymentProcessor.createPaymentIntent(amount);
}
}
//PaymentProcessor.js
class PaymentProcessor {
constructor(processor, currency) {
this.processor = processor;
this.currency = currency;
}
createPaymentIntent(amount) {
this.processor.createTransaction(amount, this.currency);
}
}
//BetterProcessor.js
class BetterProcessor {
createTransaction(amount, currency) {
console.log(`Payment made for ${amount} ${currency}`);
}
}
Notice how on the Checkout
we only changed the payment processor to be used on the constructor and the makePayment
method remained untouched. We adapted the intermediary class PaymentProcessor
to the processor needs.
We removed the dependency between Checkout
and the processor used by implementing the intermediary class PaymentProcessor
, following the Dependency Inversion Principle.
This content originally appeared on DEV Community and was authored by Josué Rodríguez

Josué Rodríguez | Sciencx (2021-04-23T00:38:48+00:00) Dependency Inversion Principle. Retrieved from https://www.scien.cx/2021/04/23/dependency-inversion-principle/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.