Another SOLID article, the SOD.
Intro
These principles should be taken as a guide, should have a place in your thinking as a suggestion and not as a religious dogma. In this article I am going to discus 3 of the 5 principles, hence the SOD.
Why the SOD? Because these are the principles I find most applicable in my day to day tasks.
The S-O-D from S-O-L-I-D
— Single responsibility principle
— Open closed principle
— Dependency Inversion principle
I. Single responsibility principle
A class should have one and only one reason to change, meaning that a class should be concerned with one single "thing".
Let's look at the next pseudo code class.
class GameDifficultySettings {
constructor (difficulty, speed) {
this.botDifficulty = difficulty;
this.botSpeed = speed;
}
changeBotDifficulty(difficulty) {
...
}
changeBotSpeed(speed) {
...
}
changePlayAvatar() {
//..
}
}
The class GameDifficultySettings implements three methods changeBotDifficulty, changeBotSpeed and changePlayAvatar.
The changePlayAvatar method should not be part of the GameDifficultySettings class, this method should be implemented by a User or PlayerSettings class.
Given that GameDifficultySettings class is dealing with game difficulty settings and also with changing the player settings, it violates the very first principle.
II. The Open-Closed Principle
The open-closed Principle says that: “A class or module should be open to extension but closed to modification.”
This means that we should not have to modify our code to make it work for further functionality. Every class should have a base set of functionality that is streamlined for every use case.
In simpler terms, we should be able to introduce new features without changing the present code by risking to break any already existing functionality.
Let's take the example above which is familiar to us.
class GameDifficultySettings {
constructor (difficulty, speed) {
this.botDifficulty = difficulty;
this.botSpeed = speed;
}
changeBotDifficulty(difficulty) {
...
}
changeBotSpeed(speed) {
...
}
}
Look at changeBotSpeed(speed)
method.
Let's say the developers want to provide a speed value and a multiplier to it for various applications.
changeBotSpeedWithMultiplier(30, 5);
Changing the name of the method changeBotSpeed()
to changeBotSpeedWithMultiplier()
would break any instances used
in the application unless we change it. We can risk to miss a place or may break the code for another application using this class.
Instead we can create a new method allowing both methods to coexist.
This way any code relaying on the changeBotSpeed()
will still be functional and the class is Open for extension but Closed for modification.
III. Dependency Inversion Principle
An example where this principle is violated is when one class is used inside another class. As a result, our class is reliant on another. The code should be based on abstraction rather than implementation.
Low-level modules should not be relied upon by high-level modules. Abstractions should be used in both cases.
Let's look at a pseudo code example.
The bad way.
class BillsManager {
constructor () {
this.anglianWaterSupplier = new AnglianWater();
}
... code
}
In the example above the BillsManager class depends on the AnglianWater() supplier, implicitly depending on AnglianWater() provider. What if we want to change the water supplier?
The good way.
class BillsManager {
constructor (waterSupplier, ...) {
this.waterSupplier = waterSupplier;
...
}
... code
}
later
const waterSupplier = ... any supplier here even AnglianWater
const billsManager = new BillsManager(waterSupplier, ...);
Thank you for following so far.