That's the first time that I'm writing a post in english, so any feedback that you may give to me will be very important. I'll be grateful.
In the recent days some of my study focus are the SOLID principles, Clean Architecture and some releated topics like Design Patterns, and I need to confess that I was using the OOP techiniques in the wrong way since college, I realised that what I was writing was just procedural code separated in different files with classes and methods, there wasn't a single advantage by using OOP like that, so I decided to change.
Every paradigm has its own principles, in the OOP world those principles are the SOLID (there are a few more, but those are the most famous and used). So what this means, these SOLID keywords? They stand for:
- Single Responsability;
- Liskov Substitution;
- Interface Segregation;
- Dependency Inversion;
Basically, if you build your application following all those principles, your codebase will be more flexible, abstracted and maintainable, the evolution of the software will be less painful and costly, giving you more time to expend implementing new things.
The Open-Closed Principle says that our classes must be open for expansions and closed for changes. Basically, we must be able to change the implementation behavior on execution-time, and to reach that, we don't need to change the class codebase, we just need the help of composition.
There is a famous dictation that says that we need to prefer composition over inheritance, and this is really important. There are some issues when we decide to use inheritance, the first is that we're breaking the OOP foundation of encapsulation, because the children knows everything about the parent. The second is the static inheritance, we can't change the behavior of the children even in execution-time, we need to change the codebase itself to be able to change the behavior, breaking the Open-Closed Principle.
When we have composition, we no longer have the "is" relationship (Ex: SavingsAccount is an Account) and we passed to have the "has" relationship (Ex: AuthorizationClient has a HttpClient), so, following the example, AuthorizationClient behaves like a normal HttpClient, but he can change your default behavior, adding an authorization header for example.
Imagine the following scenario, let's say we have a front-end client application written in React, we're consuming an API and we need to pass an authentication token (a jwt for example). We decide to create an interface responsible for send HTTP requests, so, in the data layer we create our HttpPostClient protocol (Only POST requests, following the Interface Segregation principle, subject for another post).
After that, we create an implementation for this protocol based in the axios library.
Now we have our protocol (HttpPostClient) and our implementation (AxiosHttpClient), why we can't just pass the authorization header normally in the method call? We need to think that this header needs to be passed in many requests and will be always the same: Capture the token from the localStorage or from another service and pass to the method that will do the request. If we just copy & paste this implementation, we will be breaking the DRY (Don't Repeat Yourself) principle, so we need to think a smart way to do that. That's where the Decorator Pattern comes in.
The Decorator Pattern is basically a wrapper for an object. This wrapper must have the same type of the wrapped object i.e. implement the same interface and because of that, they can be exchanged in a way that the client class don't notice that change (Liskov Substitution).
The goal of this pattern is to add a behavior to the decorated object.
Going back to our example, we need a Decorator that implements the HttpPostClient interface and add the desired behavior to our AxiosHttpClient without changing the class implementation.
This Decorator class will be called AuthorizationHttpPostClientDecorator.
Some important things to notice:
- Our Decorator class has the same interface of the decorated object.
- He receives the wrapped object on the constructor (Dependency Inversion principle)
- Runs some logic and them calls the method from the decorated object
That's an example of how we can add behavior to an class without change your implementation code.
Whenever we choose a new program paradigm to work with, we need to be aware of his foundations and principles, only with that knowledge we will be able to deeply understand how to use that paradigm correctly and get the most of it.
In this example I tried to demonstrate the Open-Closed principle in an easy way, with an easy example, so that you can realize its importance. I choose the decorator pattern because your application of the Open-Closed is one of the most common, however I could also implement the Proxy Pattern, he is also an structural one and work in a very similar way.