DEV Community

Cover image for S.O.L.I.D. ???
ahmedgaafer
ahmedgaafer

Posted on

S.O.L.I.D. ???

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:

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';
}

Enter fullscreen mode Exit fullscreen mode


/*
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

Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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;
}

Enter fullscreen mode Exit fullscreen mode

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);

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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);

Enter fullscreen mode Exit fullscreen mode

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();

Enter fullscreen mode Exit fullscreen mode

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;
}

Enter fullscreen mode Exit fullscreen mode

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");

Enter fullscreen mode Exit fullscreen mode

Refrences

Top comments (0)