S.O.L.I.D.
S.O.L.I.D. is a set of guidelines for object-oriented programming that was introduced by Robert C. Marten around 1995 in response to some issues he saw in Object Oriented code
What does it fix?
- RIGIDITY:
A change in the code might affect other parts of the code
- FRAGILITY:
A change might lead to things breaking in unrelated places
- IMMOBILITY:
Not being able to use the code outside of it's original context
It's an abbreviation for the following:
- " S " Single responsibility principle
- " O " Open closed principle
- " L " Liskov substitution principle
- " I " Interface segregation principle
- " D " Dependency inversion principle
Single responsibility principle :
A Class should have only one reason to change
// We have those two classes "Circle" and "Square"
function Circle(raduis){
this.raduis = raduis;
}
function Square(length){
this.length = length;
}
// And we have another Class that has method "sum"
AreaCalculator.prototype.sum = function(shapes){
// returns the sum of all areas of the shapes provided
}
// Now we want to return some value to the user
//Does this function belong here "In the AreaCalculator class"???
AreaCalculator.prototype.output = function(){
return `The sum of all shapes is ${this.sum()}.`
}
//NO
//What we should do is reformat this code and make a class that is responsible for handling the return
function CalculatorOutput(areas){
this.areas = areas;
}
CalculatorOutput.prototype.good = function(){
return `The Area of the shapes is ${this.areas.sum()}`;
}
CalculatorOutput.prototype.evil = function(){
return 'I blocked the output Muahahaha';
}
/*
So what we now understand is that the class should only be responsible for only one function
and if we find our selves over-complicating a class or making it do more than it should.
We must refactor our code to make it cleaner.
*/
var circle = new Circle();
var square = new Square();
var shapesArray = [circle, square];
var areas = AreaCalculator.sum(shapesArray)
var output = CalculatorOutput(areas)
output.good();
output.evil();
// We can now add more output styles without altering the AreaCalculator Class
Open closed principle :
You should be able to extend a class without modifying it
Let us revisit the sum method in the array calculator
// How many times have you found your self using long if..else statements
// Does this logic belong to the area calculator ??? well, it probably doesn't
AreaCalculator.prototype.sum = function(shapes){
var areaArray = shapes.map(shape => {
if(shape instanceof Square){
return Math.pow(shape.length, 2);
}
else if(shape instanceof Circle){
return Math.PI * Math.pow(shape.raduis, 2);
}
});
return areaArray.reduce((acc, current) => acc + current)
}
What we can do is refactor the code so that each shape has it's own area function
Square.prototype.area = function(){
return Math.pow(this.length, 2);
}
Circle.prototype.area = function(){
return Math.PI * Math.pow(this.raduis, 2);
}
Now as long as it follows the shape interface our "sum" function should look like this
AreaCalculator.prototype.sum = function(shapes){
var areaArray = shapes.map(shape => {
return shape.area();
});
return areaArray.reduce((acc, current) => acc + current)
}
Now we can add more shapes extinding the use of the area calculator whoithout changing any of the code.
Liskov substitution principle :
Drived classes must be substituable for their base classes
// Let us assume we have a Rectangle Class
function Rectangle(width, height){
this.width = width;
this.height = height;
}
Rectangle.prototype.area = function(){
return this.width * this.height;
}
Now you want to make a square class and you know it's basically a rectangle right.
function Square(length){
Rectangle.call(this, length, length);
}
Square.prototype = Object.create(Rectangle.prototype);
After a while you want to make a diffrent type of a rectangle and since you know that Square inherits from Reactangle you decied to inherit from Square
function BetterRectangle(width, height){
Square.call(this, width, height);
}
BetterRectangle.prototype = Object.create(Square.prototype);
var newRectangle = new BetterRectangle(2, 3);
newRectangle.area();// RETURNS 4 instead of 6
So what happen here is it's behaving like a Square would which is not what we wanted
The solution to this is just to make Square it's own class
Interface segregation principle :
Make fine-grained interfaces with specific methods
To understand this lets look at the following example
function Shape() {};
Shape.prototype.area = function() {/* ... */}
Shape.prototype.volume = function() {/* ... */}
function Triangle(base, height){
this.base = base;
this.height - height;
}
Triangle.prototype = Object.create(Shape.prototype);
Did you notice what is wrong...
Triangle is a 2D shape so it has no volume so we shouldn't just keep the function like that what we should do is
split the area and the volume into two seperated classes to differentiate between the 2D shapes and 3D ones
function 2DShape() {};
2DShape.prototype.area = function() {/* ... */}
function 3DShape() {};
3DShape.prototype.volume = function() {/* ... */}
var Triangle = new 2DShape();
var Cube = new 3DShape();
Dependency inversion principle :
Depend on abstractions, not on cocretions
Assume that we have a Picture class
function Picture(color, bg){
this.Shape = new Shape();
this.color = color;
this.background = bg;
}
As you can see we made a new object of Shape inside Picture
making Picture totaly dependant on Shape
take this as a general rule if you are making a new instance of some other constructor inside the defention of your constructor then you should consider refactoring.
A solution for this is to pass what you need as a parameter
function Picture(shape, color, bg){
this.Shape = shape;
this.color = color;
this.background = bg;
}
var square = new Square(3);
var picture = new Picture(square, "red", "blue");
Top comments (0)