"D" - Dependency Inversion Principle. This principle says: high level modules should not depend on low level modules; both should depend on abstractions. Abstractions should not depend on details. Details should depend upon abstractions.
As usual, this description could be unclear, so let's try to understand this principle with examples.
Let's imagine we want to create logic for working with movie data. Let's create a simple Movie class:
Later we understand that we want to save information about video in local storage and we, following the single responsibility principle c will create another class for this purpose:
Everything is good, and our logic is used by many files.
Then, we want to save not to local storage, but to the file system. No problems, let's create a class for this logic:
And now, we need to changes places from local storage, to file storage:
Now, it seems not a problem, just 4 lines need to be deleted and replaced by another. But, as we discussed early, imagine if you used local storage methods many times, in many files. It will be really hard to find all places and change them properly. By the way, if you wrote tests for this, all tests also should be changed.
Ok, we changed and now we can continue working. Our system becomes bigger and bigger, and later, we decide to change our logic from file system to DB storage, like mongo DB or SQL, what we will do? Following the"Single responsibility principle", we will create another class for DB.
And now, we faced the same problem, we should go across all files, places, where our previous logic with file storage and change it for new, for DB logic. We should change the methods' name, signature, in all places, in all files. And of course, if we have tests, we should fix them.
The more places with our previous logic, the harder it is for us to change them. And of course, it could be a cause of bugs in our application.
I really hope you understand the problems, and now, we can see how to avoid these problems. Remember, high-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details. Details should depend upon abstractions?
Let's start refactoring peace by peace. And let's start from abstraction. Let's create a class, "MovieStorage". This class will be our "abstractions". And abstractions should not depend on details. How can we achieve it? Simple. We create methods for our "MovieStorage" class, which will be used instead of MovieDBStorage, MovieFileStorage.
This class will be like an interface. All our code will use these methods, which will not change their names. It means that our high-level modules (places, where our "abstraction" uses) will not depend on our internal logic.
Next, we will create separate classes for each store, as we did before, but for this time all of them will be using the same names, params (signature) as our "abstract" class:
The last step, adapt our "abstraction":
Now, our "abstraction" doesn't depend on details. MovieStorage receives an instance of any storage, that follows our interface. Then, we can use it:
And if sometime later we decide to change from file storage to cache storage, local/session storage, mongoDB, sql etc. we just should prepare a new specific storage class (for mongo, redis, sql), which should implement methods with the exact name as in "abstraction" and pass the instance in the constructor:
We changed just param from
new MovieStorage(new MovieFileStorage())
and that's all. We don't need to go through all files and change them. We don't need to change our current tests. All our current files will be using the same abstraction. And our abstraction doesn't depend on logic, inside it.
new MovieStorage(new MovieDBStorage())
This is the end of our "SOLID" in JS article series and I really hope you enjoyed them and you can use at least one of them in your practice.
You can use all of them or you can choose just one, for example, single responsibility principle, and go through your code, try to investigate, do all places follow this principle. If not, you can refactor your code.
Next, you can take the "Dependency Inversion Principle" and check your code for this principle. Fortunately, frameworks like "Angular" or "NestJS" follow this principle and if you use these frameworks, you can see them in practice in your project, but if you use something else, you can try to refactor similar places, as we discussed in this article.
Thank you for your time and will meet in the next articles.