Open-Closed Principle
This is article #2 in a series of articles explaining SOLID principles. You can read the first article here.
Introduction
I've always been wondering and asking myself, How a framework like Laravel provides us with many drivers for many things like Mail, Sessions, WebSockets...etc and we only have to worry about the configuration of the driver. Once you specify which provider should be used by the driver in your configuration file, Then you can go without any code modification!! You get how great this is, right?
I will return to this point later. until then, keep it in your mind.
Open/Close Principle
This represents O in SOLID principles. It states that:
Entities should be open for extension, but closed for modifications.
In other words, Change the behaviour without modifying the source code. but how can we do that? and why we should try to apply this principle?
If we somehow could apply this principle as best as possible, Then you can change the behaviour of your code without ever touching the original source. You don't want to be in a situation when you keep editing the same code until leads to code rot or breaking your code. What you can do instead to prevent all of that is to modify your behaviour through extension rather than modifying the original source. To understand that a bit more. Let's dig into a simple example:
Assume that we have a class LoginController responsible for authenticating the users via email or google. The class looks like this:
This code is a Single Responsibility Principle Violation and an Open/Close Principle Violation. It breaks 2 principles !!
I hope you already know why it is a Single Responsibility Principle Violation and how to fix this since I explained this principle in the previous article. Anyway, let's do it one more time to make sure you have a good understanding of this principle then we will back to apply the Open/Close principle.
Remember: Single Responsibility Principle states that A class should have one reason to change. But our LoginController class responsible for:
- Authenticating the user
- The implementation of authenticating a user via Email
- The implementation of authenticating a user via Google
Our class should be only responsible for authenticating the user by extracting the implementation of both Email and Google authentication to dedicated classes (GoogleProvider class & EmailProvider class):
EmailProvider class looks like this:
GoogleProvider class looks like:
and finally, our LoginController looks like this:
Now we successfully applied the Single Responsibility Principle. But we have broken the Open/Closed Principle ... Why?
Imagine a situation when your business requires that a user can also log in through Facebook what are you gonna do?
You may say: "I will add a new FacebookProvider class and switch on the provider string. If the provider equals facebook, then instantiate FacebookProvider object and finally call loginWithFacebook method".
It will work but the problem with this approach is that you have to edit your original source over and over again which leads to break your code or code rot. Don't do that !!
We want to change the behaviour of the code without ever touching the original source. But how can we do that?
Here's what Uncle Bob says:
When you have a module you want to extend without modifying then Separate the extensible behavior behind an interface, and flip the dependencies.
Let's understand this by doing. Let's take the first part: Separate the extensible behavior behind an interface. This means the first part is to add the interface AuthenticatableProvider. it looks like this:
Now we need to implement the interface for each class:
class EmailProvider looks like:
Class GoogleProvider:
Class FacebookProvider:
We have done the first part of Uncle Bob's advice (Separate the extensible behavior behind an interface).
Let's move the second part: flip the dependencies. We can do that by making the LoginController class depends on the abstraction (AuthProvider Interface) rather than depending on the low-level entities ( EmailProvider class, GoogleProvider class..etc ).
So we change the LoginController class to be like this:
Now, whenever you want to add new providers like GitHub or LinkedIn you can just add a new class that implements the AuthProvider interface and everything will work without changing any code in your LoginController.
I hope it is now clear to you how a framework like Laravel provides us with many drivers for many things like Mail, Sessions, WebSockets...etc and we only have to worry about the configuration of the driver without any code modification. Laravel and other frameworks designed in a way to code to an interface, not to an implementation. So, it allows you to swap out implementations and you can extend your code without changing the deep level of your code. On the other hand, coding to an implementation forces you to change a lot more code if you ever decide to go to another implementation.
Back End Developer @ Squadio | Golang, PHP, Laravel
3ykeep going 🤗
Software Engineer (JavaScript | TypeScript | React Native | NodeJS | NestJS)
3yGreat