[SOLID] Single Responsibility Principle in ruby

Thiago Lima
3 min readJan 5, 2024

--

The Single Responsibility Principle (SRP) is the first of the five SOLID principles. It states that each software module should have only one reason to change.

You may have heard someone advocating that a module should only perform a single task. This is not incorrect. When refactoring functions, we always need to break them down to do the minimum possible. However, this concept does not fully reflect the idea of SRP.

A more accurate way to think about it is that responsibility is not about the tasks of the module, but the module is responsible for someone, whom we will now call an actor. An actor can be a group of users, another module, or even another software that makes use of it.

Let’s understand this concept more clearly:

A module should be responsible for one, and only one, actor. (Robert C. Martin)

Let’s consider modules as classes in Ruby.

Violating the principle:

Note that we have a Payment class with two methods, each responsible for a different actor.

In Ruby, it could look something like this:

class Payment
def calculate_payment
# used by user on checkout
end

def send_to_gateway
# it uses calculate_payment and sends to PaymentGateway
end
end

Notice that the class has two public methods, which raises a concern about potential issues. This happens because the same class is interacting with two different actors.

More cohesion, less coupling:

Here we notice two problems: low cohesion and high coupling. Low cohesion means our class has more responsibility than it should. Responsibility doesn’t necessarily mean the things the class does, but for whom it does them. The more actors a class works for, the greater its responsibility and the lower its cohesion.

The problem with low cohesion is that our principle states it should have only one reason to change. So, in our example, we could encounter the following problem:

We have two actors, the platform’s Checkout, which needs the Payment class, and the Payment Gateway, which needs to perform the same calculation.

One day, the business decides to separate delivery fees. Previously paid through the Payment Gateway, now it will be paid directly to the carrier. However, the Checkout should still display the total amount to be paid.

We have the ‘calculate_payment’ method, which adds the product value and all fees to pay. Now this method will undergo changes. The Payment Gateway team makes changes to the class, removing the delivery fees. However, the Checkout team was not informed of the change, perhaps because they didn’t even know they were using this class. Soon, users start complaining that they didn’t know there was a separate fee from the carrier.

This happens because our Payment class has too many responsibilities and requires more than one reason to change.

It’s natural that low cohesion reflects in high coupling, where a class has many dependencies on others. Therefore, the principle is there to protect us from these negative variables.

Refactoring:

One suggestion to solve the problem is to work with a Facade. This way, we can put the logic of each responsibility into separate classes, but with an interface grouping these classes in a context.

Let’s see how it would look in Ruby:

class Payment
def calculate_payment
payment_calculator = PaymentCalculator.new()
payment_calculator.call
end

def send_to_gateway
gateway_sender = GatewaySender.new()
gateway_sender.call
end
end

class PaymentCalculator
def call
# used by the client to calculate the payment value
end
end

class GatewaySender
def call
# it uses calculate_payment and sends to PaymentGateway
end
end

This is a simple and cost-effective solution to prevent your class from having too many responsibilities.

Reference: Clean Architecture, Robert C. Martin

--

--

Thiago Lima
Thiago Lima

Written by Thiago Lima

Hello! I’m Thiago Lima, I’m maried, I have a son named Isaac. I’m software engineer and I programming in Ruby on Rails.

No responses yet