Sunday, December 10, 2017

What is SOLID principle? Explained with the Ruby Example

SOLID principle, along with the GRASP, is often seemingly difficult subject to understand especially for beginners. I just have made the good example using Ruby code.


Single Responsibility Principle(SRP)

SRP means a single class has to have only one reason to change. This means a class needs to be clear such that what it does and what it is responsible for.

def EmployeeInfo
  def age; end
  def sex; end
  def hobby; end
  def hobby_name; end
  def hobby_type; end
  def payment_water; end
  def payment_laundry; end
end

For instance the above Ruby code shows a class, intentionally or not, contains methods which is not that directly related to the class definition. Employee, though it can have hobby, but the hobby detail hobby_name and hobby_type is not directly related. So for instance, if we are going to change hobby_type and if it does effect hobby_name, that causes verbosity since it is the modification of HOBBY. This is just an simple example, but often voilerplate coding enhances too much methods and params in a single class, which makes class hard to read, hard to define what is going on, and such non-modularity is going to harm the entire process if we need to add/modify something in it. This means, the responsibility in each code block is not clear. So I rather do this:

-----

def Employee
  def age; end
  def sex; end
  def hobby
    Hobby.new(@key)
  end
end

def Hobby
  def initialize(key)
    @key = key
  end
  def name
  end
  def type
  end
end
...

This case, since Hobby has bunch of its attribute those are specific to that "Hobby" object, now it has been separated as a single class. With this definition, if something inner attribute or function in Hobby object altered, it does not affect Employee anymore, since these two are low-coupled and mutually independent, and connect with each other via the interface.

Open/Closed Principle

Open-Closed Principle is "Open for extension, Closed for modification." For instance,

def Car
  def body; end 
  def tire; end
  def shaft; end # stable and less likely to change
  def gear; end # stable and less likely to change
end

This case, we no longer need to expose shaft and gear to public, since these two are already stable and it is less likely that those shaft and gear is going to change so far.

def Car
  def body; end 
  def tire; end

  private

  def shaft; end # stable and less likely to change
  def gear; end # stable and less likely to change
end

With this in mind, we can hide which is not necessary to expose for modification. Also, those classes need to be easy to be extended.

def Wagon < Car
  def body
    RectangledAndBigger
  end 
  def tire
    LittleBitBigger
  end
end

Then just an addition of inherited class realized the requirement of new Wagon, which is the business need. If your codebase is not that open for addition and if all of the components(or non-component) are not clear whether it could be altered or not, developers are going to be messed up.

Liskov Substitution Principle

This is so easy to show an example. Under Liskov Substitution Principle, the parent class and child class (or the classes of the same Level inheriting from the same class, of course) needs to be INTERCHANGEABLE such that those interfaces(I mean, methods and values) are the same. For instance, the class String in Ruby has to_i (in general toInt) method to convert the value's type into integer.

irb> "moomin".to_i
=> 0

irb> 99.to_i
=> 99

irb> 99.99.to_i
=> 100

This is possible since these String, Integer, Float inherit from the same Object class.

Interface Segregation Principle

Just let's look at the example:

def SoftDrink
  def sugar; end
  def water; end
  def can; end
  def size; end
  ...
end

So that

def Pepsi < SoftDrink
end

If the SoftDrink is Pepsi, this makes sense and the class SoftDrink deserves to be its parent class to inherit from. However, how about this???

def NonSugarGreenTea < SoftDrink
  def leaves; end
end

It is obvious that we do not need SUGAR of SoftDrink class to inherit from. This is not nice since NonSugarGreenTea is compelled to have sugar as its interface. Also, if the SoftDrink is not contained in Can but PetBottle, also same issue happens. So:

def SoftDrink
  def water; end
  def size; end
  ...
end

def Pepsi < SoftDrink
  def can
  end
end

def NonSugarGreenTea < SoftDrink
  def leaves; end
  def pet_bottle; end
end


Now intefaces has been segregated and they only potentially have what they only necessary. This is ISP.

Dependency Inversion Principle(DIP)

DIP means Concrete class only should be dependent on the Abstract class and vice versa is not accepted. Since anti-pattern code example explains better, I will show you the bad example below:

def SoftDrink
  def some_method
    Pepsi.omake
  end
end

def Pepsi < SoftDrink
  def self.omake 
  end
end

Just don't do this shit.


So the SOLID Principle is the main principle such that coders keep its codebase clean, less-couple, and easy to change.

No comments:

Post a Comment