SOLID Ruby: Single Responsibility Principle

Share this article

Key Takeaways

  • The Single Responsibility Principle (SRP) is a core principle of SOLID Ruby, stating that a class should have only one responsibility. This promotes high cohesion and guards against fragmentation, making code easier to manage, understand and debug.
  • Implementing the SRP involves identifying the responsibilities of your classes and ensuring each class only has one. If a class has more than one responsibility, it should be split into multiple classes, each with a single responsibility.
  • While the SRP is important, it’s crucial not to be extremist. Overthinking the principles can lead to too many levels of indirection and code that is as hard to maintain as a version thrown together without consideration for the principles.
We make use of BDD and object oriented programming techniques to obtain clear and elegant code, right? Actually, these are outcomes of the main goal: to create code with low maintenance cost, code that doesn’t demands a lot of time and people for fixes and improvements. There’s a group of guidelines and principles to help us achieve that goal. It’s called SOLID and was first described by Robert Martin (aka Uncle Bob) more than a decade ago. SOLID isn’t a set of rigid rules nor is the only guideline group you should use, but it’s a very good starting point and should change the way you design and code software right away. Here are the principles described by SOLID:
  • Single Responsibility Principle
  • Open-Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle
The goal of these principles is to make code changes as side-effect free as possible. This means that fixes or improvements should cause the code to change in as few places as possible. We’re effectively diminishing the maintenance cost through a design made to accommodate changes. I urge you not to fall prey to the easy thinking of “this is Java BS, we don’t need it here”. Good object oriented design applies to any object oriented language. The form may change (tip: Ruby makes it ridiculously easy to implement those principles), but the idea and the fundamentals are the same. Another obligatory disclaimer is: there isn’t a single way to implement all these principles. Try some techniques and chose what makes sense for you based on your style and way of thinking. I’ll usually show one option, but keep in mind there are a lot more. If you don’t agree, please feel free to express your point of view in the comments. In this article I’ll talk about the Single Responsibility Principle (SRP). It’s the easiest one to understand and it’s the base for all good object oriented code. Note: I’ll often use the term “class” in this article but, most of the time, the principles can be applied to any “entity”, be it a class, a module, a method and so on. The SRP states that a class should have only one responsibility and should fully execute it – that responsibility shouldn’t be splattered among more than one class either. This is a way to achieve high cohesion, a highly desirable trait in OO software. A cohesive class fully executes its responsibility, guarding it against fragmentation and hiding the implementation details. One way of thinking about what is a responsibility is to think of it as a reason for a class to change. So, we can define this principle as: a class should have only one reason to change. Okay, time to code! Here’s an hypothetical ActiveRecord class representing a video-game: [gist id=”2690968″] This class’ main responsibility is to take care of business logic related to the game state – validations and relationships in this case. But, as we can see, it’s also responsible for consulting a web service to get the game price and for outputting the game information in a pre-defined format. This implementation has low cohesion, it has more than one reason to change: – Validation and relationships (we’ll talk more about ActiveRecord breaking the SRP later) – Price information – Output formatting A nice way to solve this problem it to split this class and write two additional classes: [gist id=”2690982″] Now we have a class focused on each responsibility previously identified. To set the game’s price you could use an “orchestration” object that gets the game and applies the price fetched from the web service. Another option is to make this transparent through the Game class: [gist id=”2690990″] I don’t like to call external services from ActiveRecord models, but this is a way to do it. The code still has some flaws and we’ll improve it in the next articles (tip: we’re coupling our objects to concrete implementations instead of roles). A real example I always like to bring up is the Creditcard class from Spree. You can see that, besides the “default” ActiveRecord behavior (validation and relationships), this class is responsible for authorizing a card, processing the payment of a purchase and a lot of related behavior. This is obviously bad. We have a class with more than one responsibility with other responsibilities partially implemented by it. It lacks cohesion and is highly coupled (it depends on other parts to fully execute its responsibility). If you take a look at the current class
(master branch) you’ll note that it has been refactored and a lot of the behavior has been extracted from it. It’s a great example of how things can get better by using the SRP. What about ActiveRecord? Does it violate the SRP? Yes it does, and it’s by design. If you read the description of the Active Record pattern (do not confuse it with ActiveRecord, the framework), you’ll note how this is a conscious trade-off: we’re exchanging a greater level of isolation for a more direct, quick approach. I’m fine with that as long as ActiveRecord models don’t do more than finders and boolean checks (predicate methods). Keep in mind that being extremist (on either side) is bad. Do not over think the principles or you’ll end up with too many levels of indirection and code that is as hard to maintain as the “thrown together” version.

Here’s a list of tips to avoid breaking the SRP:

  • Your class has too many comments explaining why something is there;
  • You’re using too many instance variables and often use them as separated groups throughout the methods, like each one was a data structure (each group most likely belongs in a distinct class);
  • You’re passing too many parameters on method calls or constructors. There isn’t a “magic number” here, but I start to look for a better abstraction after four parameters;
  • The use of private/protected methods often (but not always) means that chunk of code doesn’t belongs there and should be extracted;
  • Most of the methods of your class makes heavy use of conditionals.
Here’s a gist with additional links about SOLID and overall good OOP code. So, that’s it for the Single Responsibility Principle. Next stop is Dependency Inversion Principle.

Frequently Asked Questions (FAQs) about SOLID Ruby and Single Responsibility Principle

What is the Single Responsibility Principle in SOLID Ruby?

The Single Responsibility Principle (SRP) is one of the five principles of SOLID Ruby. It states that a class should have only one reason to change. This means that a class should only have one job or responsibility. This principle is crucial in maintaining code that is easy to manage and understand. When a class has more than one responsibility, it becomes coupled, and a change to one responsibility may affect the other. This can lead to unexpected bugs and difficulties in testing and debugging.

How does the Single Responsibility Principle improve code quality?

The Single Responsibility Principle improves code quality by promoting high cohesion and low coupling. High cohesion means that the responsibilities of a given class are closely related. Low coupling means that the class is independent and does not rely on other classes. This makes the code easier to read, understand, and maintain. It also makes it easier to test and debug, as each class can be tested independently.

How can I implement the Single Responsibility Principle in my code?

Implementing the Single Responsibility Principle involves identifying the responsibilities of your classes and ensuring that each class only has one responsibility. If a class has more than one responsibility, it should be split into multiple classes, each with a single responsibility. This can be achieved through refactoring techniques such as Extract Class or Extract Method.

What are the other principles of SOLID Ruby?

Besides the Single Responsibility Principle, SOLID Ruby includes four other principles: Open-Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, and Dependency Inversion Principle. These principles provide guidelines for designing software that is easy to maintain, understand, and extend.

What is the Open-Closed Principle in SOLID Ruby?

The Open-Closed Principle states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This means that you should be able to add new functionality to a class without changing its existing code. This principle promotes code reusability and reduces the risk of introducing bugs when modifying existing code.

What is the Liskov Substitution Principle in SOLID Ruby?

The Liskov Substitution Principle states that if a program is using a base class, it should be able to use any of its subclasses without the program knowing it. This principle ensures that a subclass can replace its superclass without affecting the correctness of the program.

What is the Interface Segregation Principle in SOLID Ruby?

The Interface Segregation Principle states that clients should not be forced to depend on interfaces they do not use. This means that a class should not have to implement methods it does not need. This principle promotes code that is easy to understand and maintain.

What is the Dependency Inversion Principle in SOLID Ruby?

The Dependency Inversion Principle states that high-level modules should not depend on low-level modules. Both should depend on abstractions. This principle promotes code that is loosely coupled and easy to modify and extend.

How can I learn more about SOLID Ruby?

There are many resources available to learn more about SOLID Ruby. You can read books, take online courses, or attend workshops and seminars. You can also practice implementing the SOLID principles in your own code to gain a deeper understanding of them.

Why is it important to follow the SOLID principles in Ruby?

Following the SOLID principles in Ruby helps to create code that is easy to understand, maintain, and extend. It promotes high cohesion and low coupling, which are key characteristics of good software design. It also makes the code easier to test and debug, leading to higher quality software.

Lucas HúngaroLucas Húngaro
View Author

I'm a 27 years old software developer living in São Paulo, Brazil. I've been working with all kinds of platforms and languages since 2001 to finally fall in love with Ruby in 2007 and never look back. I'm currently working as Senior Software Developer at Elo7.

SOLID
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week