SOLID Principles
Tags: programming, architecture, oop, ruby
Published: 2020-12-21 09:42:53

# SOLID PRINCIPLES

SOLID Principles is a coding standard that all developers should have a clear 
concept for developing software in a proper way to avoid a bad design. 
It was promoted by **Robert C Martin** and is used across the object-oriented design spectrum.
When applied properly it makes your code more **extendable, logical and easier to read**.

## Solid stands for

* S - Single responsibility principle
* O - Open-closed principle
* L - Liskov substitution principle
* I - Interface segregation principle
* D - Dependency Inversion principle

---

## 1. Single Responsibility Principle

> ### A class should have one and only one reason to change, meaning that a class should have only one job.

For example, say we have some shapes and we wanted to *sum all the areas of the shapes*.

Let's take a look at the following code:

```ruby
class Circle
  attr_reader :radius

  def initialize(radius)
    @radius = radius
  end
end

class Square
  attr_reader :length

  def initialize(length)
    @length = length
  end
end
```

First we create our shapes classes and have the constructors setup the required parameter. Next, we move on by creating the **AreaCalculator** class and then write up our logic to sum up the areas of all provided shapes.

```ruby
class AreaCalculator
  def AreaCalculator(shapes)
    @shapes = shapes
  end

  def sum
    # logic to sum the areas
  end

  # Disobey SRP, implement the SumCalculatorOutputter
  def output
    sum
  end
end
```

To use the AreaCalculator class, we simply instantiate the class and pass in an array of shapes, and display the output at the bottom of the page.

```ruby
shapes = [Circle.new(2), Square.new(4), Square.new(5)]
areas = AreaCalculator.new(shapes)
puts areas.output
```

The problem with the output method is that the **AreaCalculator handles the logic to output the data**. Therefore, what if the user wanted to output the data as json or something else?

All of that logic would be handled by the AreaCalculator class, this is what SRP frowns against; the AreaCalculator class **should only sum the areas of provided shapes, it should not care whether the user wants json or HTML**.

So, to fix this you can create an SumCalculatorOutputter class and use this to handle whatever logic you need to handle how the sum areas of all provided shapes are displayed.

```ruby
class SumCalculatorOutputter
  def initialize(areas)
    @areas = areas
  end

  def JSON; end

  def XML; end

  def HTML; end
end

output = SumCalculatorOutputter.new(areas)
puts output.JSON
puts output.XML
puts output.HTML
```

Now, whatever logic you need to output the data to the user is now handled by the **SumCalculatorOutputter** class.

## 2. Open-closed Principle

> ### Objects or entities should be open for extension, but closed for modification.

This simply means that a class should be **easily extendable without modifying the class** itself. Let’s take a look at the **AreaCalculator** class, especially it’s **sum** method.

```ruby
def sum
  area = []
  @shapes.each do |shape| # disobey the Open-closed principle
    if shape.instance_of? Square
      area << shape.length**2
    elsif shape.instance_of? Circle
      area << Math::PI * shape.radius**2
    end
  end

  array_sum(area) # arr.reduce(0) { |a,b| a+b }
end
```
If we wanted the sum method to be able to sum the areas of more shapes, we **would have to add more if/else blocks** and that goes **against** the Open-closed principle.

A way we can make this sum method better is to *remove the logic to calculate the area* of each shape out of the sum method and *attach it to the shape’s class*.

```ruby
class Square
attr_reader :length

  def initialize(length)
    @length = length
  end

  def area
    length**2
  end
end
```

The same thing should be done for the Circle class, an area method should be added. Now, to calculate the sum of any shape provided should be as simple as:

```ruby
def sum
  area = []
  @shapes.each do |shape| 
    area << shape.area
    end

    array_sum(area) # arr.reduce(0) { |a,b| a+b }
end
```
Now we can create another shape class pass it in calculating the sum without breaking our code.

## 3. Liskov Substitution Principle

> ### Every derived/child classes must be substitutable for their base/parent class.

Basically, it takes care that while coding using interfaces in our code, we not only have a contract of input that the interface receives but also the *output returned by different Classes* implementing that interface; they **should be of the same type**.

Let's take an example of a Rectangle and Sqaure. In mathematics, a Square is a Rectangle. Indeed it is a specialization of a rectangle. The "is a" makes you want to model this with inheritance. However if in code you made Square derive from Rectangle, then a Square should be usable anywhere you expect a Rectangle. This makes for some strange behavior.

Code for Rectangle
```ruby
class Rectangle
  attr_reader :length, :breadth

  def set_length(len)
    @length = len
  end

  def set_breadth(width)
    @breadth = width
  end

  def get_area
    @length * @breadth
  end
end
```

Code for Square
```ruby
class Square < Rectangle
  
  def set_breadth(width)
    super
    set_length(width)
  end

  def set_length(length)
    super
    set_breadth(length)
  end
end
```

__set_length__ and __set_breadth__ methods on Rectangle class makes perfect sense. However if your Rectangle reference pointed to a Square, then __set_breadth__ and __set_length__ doesn't make sense because setting one would change the other to match it.
In this case **Square** _fails_ the Liskov Substitution Test with **Rectangle**.

## 4. Interface Segregation Principle

> ### A Client should not be forced to implement an interface that it doesn't use.

This rule means that we should **break our interfaces in many smaller ones**, so they better satisfy the exact needs of our clients.

Let's see an example

```ruby
module WorkerInterface
  def work
    raise "Not implemented"
  end

  def sleep
    raise "Not implemented"
  end
end

class HumanWorker
  include WorkerInterface

  def work
    puts "works"
  end

  def sleep
    puts "sleep"
  end
end

class RobotWorker
  include WorkerInterface

  def work
    puts "works"
  end
  
  def sleep
    # No need of this method.
  end
end
```

In the above code, RobotWorker doesn't need sleep method, but the class **has to implement** the sleep method else an error will be raised. This breaks the Interface Segregation Principle.

Here's how we can fix this:

```ruby
module WorkAbleInterface
  def work
    raise "Not implemented"
  end
end

module SleepAbleInterface
  def sleep
    raise "Not implemented"
  end
end

class HumanWorker
  include WorkAbleInterface
  include SleepAbleInterface

  def work
    puts "works"
  end

  def sleep
    puts "sleep"
  end
end

class RobotWorker
  include WorkAbleInterface

  def work
    puts "works"
  end
end
```
By seperating the **WorkAble** and **SleepAble** interfaces we have removed the compulsion to implement the __sleep__ method.

## 5. Dependency Inversion Principle

> ### High-level modules should not depend on low-level modules. Both should depend on abstractions.

> ### Abstractions should not depend on details. Details should depend on abstractions.

> ### To simply put, *Depend on Abstractions not on concretions.*

This principle allows for decoupling, let's see an example.

```ruby
class Newsperson
  def broadcast(news)
    Newspaper.new.print(news)
  end
end

class Newspaper
  def print(news)
    puts news
  end
end

laura = Newsperson.new
laura.broadcast("Some Breaking News!") # => "Some Breaking News!"
```

This is a functioning broadcast method, but right now it’s tied to the Newspaper object. What if we change the name of the Newspaper class? What if we add more broadcast platforms? Both of these things would cause us to have to change our Newsperson object as well. Here we have a dependency on the type of broadcasting our newsperson which is a High level module and it depends on low level module(Newspaper).

Now let's do it using Dependency Inversion Principle.

```ruby
class Newsperson
  def broadcast(news, platform = Newspaper)
    platform.new.broadcast(news)
  end
end 

class Newspaper
  def broadcast(news)
    puts "do_something_with news"
  end
end

class Twitter
  def broadcast(news)
    puts "tweets news"
  end
end

class Television
  def broadcast(news)
    puts "live_coverage news"
  end
end 

laura = Newsperson.new
laura.broadcast("Breaking news!") #do_something_with "Breaking news!"
laura.broadcast("Breaking news!", Twitter) #tweets "Breaking news!"
laura.broadcast("Breaking news!", Television)
```

As you can see, we can now pass any news broadcasting platform through the broadcast method whether that’s Twitter, TV, or Newspaper. We can change any of these classes and we won’t break the other classes. The higher level Newsperson class does not depend on the lower level classes and vice versa.

## References
---

1. [S.O.L.I.D: The First 5 Principles of Object Oriented Design
](https://www.digitalocean.com/community/conceptual_articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design#:~:text=S.O.L.I.D%20stands%20for%3A,O%20%2D%20Open%2Dclosed%20principle&text=I%20%2D%20Interface%20segregation%20principle,D%20%2D%20Dependency%20Inversion%20Principle)

2. [SOLID Principles - simple and easy explanation](https://github.com/nahidulhasan/solid-principles)

3. [Example of the Liskov Substitution Principle?](https://stackoverflow.com/questions/56860/what-is-an-example-of-the-liskov-substitution-principle)

4. [The Power of Interfaces in Ruby](http://morningcoffee.io/interfaces-in-ruby.html)

5. [DEPENDENCY INVERSION PRINCIPLE IN RUBY](http://www.getlaura.com/dependency-inversion-principle-in-ruby/)