Guru on Rails

if you don’t sacrifice for your dream then your dream becomes your sacrifice.
Will Nguyen
SOLID - Fundamental OOP Principles
Wed 22 Aug 2018

S - Single Responsibility

Whenever we create a class or method or even variable, we create each one with only one responsibility. If we create "sing" method, we can't implement "dance" behavior inside the scope.

Benefits of Single Responsibility:

  • Decoupling code (why? Because if a class has too many dependencies, it's hard to make change).
  • If you want to make change on a class or method, there is only one reason to make change. I'm a lazy programmer so that if I have to make change, I want to do less.
  • If you know what is the "single" you might name class or method better. Code is readable and clean.
  • Dividing roles and problems. It make us break big concept into parts. Each part has just one responsibility so that we delegate them all and make problem being simple to brainstorm.
  • Understandable for some time in future when we get back to the code.

O - Open/Closed Principle

Open for extension but closed for modification. 

Because every time we make change, we take risks. Hence, we don't expect to make a bunch of changes on the existing code. However, in order to evolve a system, we need to extend system. System is nothing excepting the source code (I'm talking about software). Source code is nothing except classes, methods and attributes. Hence, we make extra classes, methods, attributes and limit changes on existing ones is our way to extend the system. The existing code should be the references. So every time we write code, we need to think it over. Because we will never change it.

L - Liskov Substitution Principle

This principle says that the subclass must be substitutable for superclass. 

How do we use inheritance in OOP? We often answer the question of "IS A" to use it. However, it's is necessary but not sufficient. Because if A class inherits B class, that means A definitely inherits all of attributes and methods of B. In fact, this principle exists in any object oriented language programming. It makes sure that your code is working correctly. 

If you want to follow this principle, you must answer for two these questions every time you make an inheritance:

  1. Subclass is a superclass?
  2. Subclass is substitutable for superclass?

Example: 

class Table
end
class Chair < Table
end

We have class Chair which extends class Table. A guy who has heavy weight can sit on the table but if he sit on the chair, he will make it broken. Chair can't be substitutable for Table. This inheritance is unacceptable.

I - Interface Segregation Principle

A client should never be forced to implement an interface that it doesn’t use or clients shouldn’t be forced to depend on methods they do not use.

This principle says about breaking a big interface into smaller parts. In Java, if a class implements a interface, it must implement all of methods. There're several methods we might never use.

But how do we use it in ruby? Ruby doesn't have interface or abstraction. In ruby, we encourage to program to the interface. Many subclasses extend the same superclass. They inherits common methods of superclass. In stead of implementing interface method like Java. We do same method name but we export the implementation into other class. Hence, if we have 9 methods which have to be implemented in 4 subclasses, we will export to 9 other classes. Each class takes responsibility for the Polymorphism of each interface method.

JAVA

public interface SayHello {
    public void sayHello();
}

RUBY

class SayHello
   def this_is_common_method do
      # some code
   end
end

class SayHelloable 
   def us_say_hello 
   end
   def vn_say_hello 
   end
end

class USSayHello < SayHello
   def initialize(hello)  
      @hello = hello
   end 

   def say_hello 
      @hello.us_say_hello
   end
end

class VNSayHello < SayHello
   def initialize(hello) 
      @hello = hello
   end 

   def say_hello 
      @hello.vn_say_hello
   end
end

What we made: 

  • We programed to methods which are interface methods in Java. 
  • We made SayHelloable class to take only on responsibility which is "say hello" but using Polymorphism.
  • In each subclass we use Delegation pattern to delegate to SayHelloable.

Benefits:

  • We avoid to implement a bunch of interface methods.
  • We avoid duplicate code by implementing interfaces (implement but they actually do same thing).

D - Dependency Inversion Principle

Entities must depend on abstractions not on concretions. It states that the high level module must not depend on the low level module, but they should depend on abstractions.

Well, you might be a little bit confused :) Because you might be Rubyist and in Ruby we don't have any technique of abstraction or interface. Because you know, ruby encourage us to program into interfaces. In fact, Ruby has interface but it's "implicit interface". In Ruby and Rails we have a ton of implicit things. It makes us somehow to develop faster and make our code shorter. This is respond_to? method, a brief description from apidock.com 

respond_to?(p1, p2 = v2) public

Returns true if obj responds to the given method. Private methods are included in the search only if the optional second parameter evaluates to true.

This method shows you the Duck Typing pattern - if an animal quacks like a duck, walks like a duck and swims like a duck, it is a duck. If we apply Dependency Injection pattern, we can see that Ruby find if that instance has or respond to those behaviors? Hence, it's the "implicit interface" in Ruby. We make the dependencies transparent and take advantage of implicit interfaces by combining Dependency Injection (DI) and Duck Typing patterns. With DI we inject not only the objects but also the class name.

class Engine
   def initialize(engine = YamahaEngine.new)
   end
end
class Engine
   def initialize(engine = YamahaEngine)
   end
end

It is the same with "program to interface, not implementation" pattern in ruby. If superclass can handle, there is no reason to let subclasses do it. We always put the top priority to superclass where we program to interface. Another way is we make another module or class which takes role of interface and implement the polymorphism.

class Startable
   def yamaha_start
   end
   def suzuki_start
   end
   def honda_start
   end
end