SOLID Principles in Ruby

The world of development business, especially with OO languages, is full of design principles. It’s hard to keep track of them all, and sometimes it is impossible to follow them all. But as with many things in software area, you should use these principles as guidelines, not rules.

The most popular OO design principles is known by an acronym, SOLID. It stands for: Single responsability(SRP), Open/Closed principle(OCP), Liskov substitution principle (LSP), Interface segregation principle(ISP), Dependency inversion principle(DIP).

 

Single responsability principle

The most well known and well used principle, and one that should  be tried to adhere to most of the time. The goal of this principle is: A class should have only one responsability. For example:

class AuthenticatesUser
  def authenticate(email, password)
    if matches?(email, password)
     do_some_authentication
    else
      raise NotAllowedError
    end
  end

  private
  def matches?(email, password)
    user = find_from_db(:user, email)
    user.encrypted_password == encrypt(password)
  end
end

 

The AuthenticatesUser should have only the responsability of authenticating the user, but it does have two responsabilities, one to authenticate the user and other to check if the email and password match the ones in the database. Let’s organize the class following the Single responsability principle:

class AuthenticatesUser
  def authenticate(email, password)
    if MatchesPasswords.new(email, password).matches?
     do_some_authentication
    else
      raise NotAllowedError
    end
  end
end

class MatchesPasswords
  def initialize(email, password)
     @email = email
     @password = password
  end

  def matches?
     user = find_from_db(:user, @email)
    user.encrypted_password == encrypt(@password)
  end
end

By now, both classes has one single responsability.

 

Open/closed principle

In object-oriented programming, the open/closed principle states “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification“; Meyer, Bertrand

 

class Ticket
  def body
     generate_ticket
  end

  def print
     body.to_json
  end
end

This code violates OCP, because if the format of the ticket changes, the code of the class need to be changed. Based on the OCP principle, this class should look like this:

 

class Ticket
  def body
     generate_ticket
  end

  def print(formatter: JSONFormatter.new)
     formatter.format body
  end
end

This way changing the formatter is as easy as:

ticket = Ticket.new
ticket.print(formatter: XMLFormatter.new)

Thus the functionality was extended without modifying the code. In this example was used the technique called Dependency Injection, but many others could apply.

Liskov substitution principle

Likov’s Substitution Principle states that if a program module is using a Base class, then the reference to the Base class can be replaced with a Derived class without affecting the functionality of the program module. Here’s an example of inheritance that break this principle:

class Animal
  def walk
     do_some_walkin
  end
end

class Cat < Animal
  def run
    run_like_a_cat
  end
end

Based on the principle as Bob Martin puts it: “Subtypes must be substitutable for their base types“.

So, they must have the same interface. But Ruby does not have abstract methods, the proper way of using it like so:

class Animal
  def walk
     do_some_walkin
  end

  def run
    raise NotImplementedError
  end
end

class Cat < Animal
  def run
    run_like_a_cat
  end
end

 

Interface segregation principle

In simple words:

No client should be forced to depend on methods it does not use.

For example:

class Jet
  def open
  end

  def start_engine
  end

   def change_engine
   end
end

class Pilot
  def drive
    @jet.open
    @jet.start_engine
  end
end

class Mechanic
  def do_stuff
    @jet.change_engine
  end
end

In this example Jet has an interface that’s used partially by both the Pilot and the Mechanic. This interface can be improved like so:

class Jet
  def open
  end

  def start_engine
  end
end

class JetInternals
   def change_engine
   end
end

class Pilot
  def drive
    @jet.open
    @jet.start_engine
  end
end

class Mechanic
  def do_stuff
    @jet_internals.change_engine
  end
end

Now the clients are not forced to depend on a method that they never use.

 

Dependency inversion principle

The main idea of this principle is simple: High level modules, which provide complex logic, should be easily reusable and unaffected by changes in low-level modules, which provide utility features.

Back to the first example on the OCP with a few changes:

class Ticket
  def body
     generate_ticket
  end

  def print
     JSONFormatter.new.format(body)
  end
end

class JSONFormatter
  def format(body)
     ...
  end
end

The problem here is that the print method from the Ticket class is dependent from the Report JSONFormatter. Since the Report is a more abstract(high-level) concept than the JSONFormatter, the principle was broked here.

A solution for avoing breaking this principle is:

class Ticket
  def body
     generate_ticket
  end

  def print(formatter: JSONFormatter.new)
     formatter.format body
  end
end

This way the Ticket class does not depend on the JSONFormatter and can use any type of formater that has a method called format(duck typing).

Conclusion

On this post was covered the five principles that when followed will almost surely improve the quality of the code.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s