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.
If we want to be good at this, we must focus on one method at a time. That means we forget all about other methods and just looking at this method. Do we understand what it does? What is the purpose of this method?
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. That is the reason why we have to apply SP first so that we can CLOSE. Only one responsibility so what we change is the implementation, interface is still same. Thus, the implementation should be separated from interfaces.
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:
- Subclass is a superclass?
- 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. Be careful when using inheritance – remember this principle every time using it.
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
Note that DI is the implementation of DIP. DI is DIP but DIP is not DI. DIP is principle, a concept.
Another the way to implement DIP is “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
Thanks for reading!