by Alejandro Marr, Software Developer --
As a follow up from my previous article about SOLID principles, I’m writing this one to introduce you to the concept of Inversion of Control (IoC) and an example implementation using InversifyJS and Typescript .
What is Inversion of Control?
Inversion of Control (IoC) is a design principle that aims to invert the control flow of an application, in order to achieve loose coupling between its classes.
Normally, our code makes use of a framework and it’s in charge of calling the framework to do what our code wants.
What if we invert this and we tell the framework how to create our classes and how to use them?
We will do this annotating our classes and telling the framework how to create them. Our code will make use of abstractions of those classes and those classes will be resolved in runtime by the framework.
What’s an abstraction?
An abstraction is a general concept or idea, rather than something concrete. Since we’re talking about Object-Oriented Programming, we can think of interfaces as the most basic form of abstraction.
By using interfaces to design our classes, we reduce complexity and avoid relying on concrete implementations.
Inversion of Control and Dependency Inversion Principle
Inversion of Control (IoC) and Dependency Inversion Principle (DIP) are both design principles that aim to achieve loose coupling between classes.
These are two different principles, but I relate them because by inverting the control, we’ll be able to comply with the Dependency Inversion Principle.
Unlike the Inversion of Control and Dependency Inversion principles, Dependency Injection is a design pattern in which an object receives other objects that it depends on. Those objects are called dependencies. It allows us to achieve separation of concerns by separating the creation and the use of objects.
We’ll make use of this pattern to achieve Inversion of Control.
Difference between Design Principle and Design Pattern
These two concepts may seem pretty similar, but the thing is they are not. While design principles provide high-level guidelines to design software, they do not provide any implementation guidelines or details.
On the other hand, design patterns provide low-level solutions to solve commonly occurring problems. They suggest proven implementations for those problems.
In our example, we’ll define a Service class which depends on a Repository class to interact with the database and at the same time, I’ll inject a dummy database to my Repository class (similar to the one on my previous article in the DIP example).
I will focus on my implementation. InversifyJS docs show you how to setup the project.
Now, let’s start working on the example. We’ll start defining our interfaces:
Next, we add an entity class that will describe our posts:
Now we implement our interfaces. As you can see, we’re using the injectable and inject decorators to annotate the classes we want to inject and injecting a dependency into a class, respectively.
Configure service identifiers. InversifyJS recommends the usage of Symbols, though we can also use classes and string literals:
Create and configure our container. Here we’ll bind our abstractions to our implementations. After this, we’ll be able to get an object by using its identifier:
Finally, here we have a simple of example on how we can use our container. I’m getting the PostService using the identifier defined in types.ts .
As you can see, I’m getting the PostService and it got a PostRepositoryinstance passed to its constructor in runtime. Also, the repository class got a MemoryStorage instance.
If you pay attention to our implementations, we always reference abstractions and InversifyJS takes care of resolving our dependencies according to how we defined our bindings.
In the end, we have an example that complies with SOLID principles:
- Single Responsibility Principle (SRP): We have defined classes that take care of one thing only.
- Open/Closed Principle (OCP): Since we are relying on abstractions, we don’t have to make changes on our calling code, if we wish to change an implementation.
- Liskov Substitution Principle (LSP): We can replace any of our objects by another one as long as they implement the same interface.
- Interface Segregation Principle: We created a PostRepositoryInterface that extends RepositoryInterface, and we’re using that one to bind our implementation. That way, we keep our base interface clean and we can add custom methods on our custom interfaces.
- Dependency Inversion Principle: We’re relying on interfaces everywhere. The only place where we’re making use of our classes is on the container.ts file, when defining our bindings.
I hope this article gives you a good insight of IoC and its implementation using InversifyJS.
Also, if you are looking for outsourced software development services or looking to add extra IT talent to your organization, you can reach out us here. Devlane is always looking for new challenges, no matter the industry or the sizing of your company.