Let's dive into one of the most important aspect of Object-Oriented Design : the single responsibility principle. The shortcut for this idea is :

Things that change for the same reason should be grouped together and things that change for different reasons should be separated.

I learned this principle in Sandi Metz's excellent book POODR and this article is mainly extracted from my notes on the book.

Organizing code to allow for easy changes

When thinking about code, we have to understand that it should always be T.R.U.E :

  • Transparent : consequences of changes in code should be obvious.
  • Reasonable : cost of change should be proportional to the benefits the change achieves.
  • Usable : code parts should be usable in new and unexepect contexts.
  • Exemplary : code should encourage new developers* to perpetuate those qualities.

*new developers can also mean you 6 months from now.

Why single responsibility matters

Because applications that are easy to change consist of classes that are easy to reuse, and a class that has more than one responsibility is difficult to reuse. You increase your application's chance of breaking if you depend on classes that do too much.

Determining if a class has a single responsibility

A clear way to determine if a class has a single responsibility is to describe the class in one sentence. If the simplest sentence you can come with uses the word and, then it likely has more than one responsibility. And if it uses the word or, it has more than one responsibility and they aren't even related.

Depend on behavior, not data

I think the motto Don't Repeat Yourself is a shortcut for this idea. The foundation of an OO system is the message, but the most visible element is the class, that's why, in my opinion, we always think about classes and methods first instead of thinking about the behavior (i.e what the object really wants).

In Ruby, we can access data in one of two ways : refer directly to the instance variable or wrap the instance variable in an accessor method. Let's say we have a Shop with a single item and a number of orders.

Here is an implementation of Shop referring directly to the instance variable :

class Shop
  def initialize(orders, item)
    @orders = orders
    @item = item
  end

  def total_revenue
    @orders * @item.price
  end
end

A better way of doing this is by always hiding the variables, even from the class that defines them. We do this by wrapping them in methods. Send messages to access variables, even if we think as them as data. Here is the Shop example with instance variables hidden :

class Shop
  attr_reader :orders, :item

  def initialize(orders, item)
    @orders = orders
    @item = item
  end

  def total_revenue
    orders * item.price
  end
end

Methods should also have only one responsibility

We can also apply everything we said about classes to methods. So we can also ask them what they do and try to describe their responsibility in one sentence. Let's try to implement a total_revenues method that, given a list of shops, returns the total revenue of all shops.

Let's see an example on how to separate methods into smaller methods with narrowed responsibility. Begin with this method :

def total_revenues(shops)
  shops.collect {|shop| shop.orders * shop.item.price}
end

Separating into smaller methods :

def total_revenues(shops)
  shops.collect {|shop| total_revenue(shop)}
end

private

def total_revenue(shop)
  shop.orders * shop.item.price
end

Even better, delegating to the shop object the behavior on how to get its total revenue :

def total_revenues(shops)
  shops.collect {|shop| shop.total_revenue}
end

Separating iteration from the action that is being performed is a common case of multiple responsibility.

A last thing I would like to share is about commenting code. I usually try to avoid comments : instead I create a new method to isolate the behavior and the new method name serves the same purpose as the old comment. It forces you to be really explicit about the code you write.

Always remember that classes that have a single responsibility isolate that one thing from the rest of the application. This isolation allows change without consequence and reuse without duplication.

In other words : it increases developers happiness !

Always keep learning smile

#object-oriented #ood #design #ruby #srp