DEV Community

Cover image for Single Responsibility Principle for beginners (1/2)
Angus Bower-Brown
Angus Bower-Brown

Posted on • Edited on

Single Responsibility Principle for beginners (1/2)

When learning Ruby it's almost impossible not to come into contact with the concepts of object-oriented programming (hereafter OOP!).

The maxim that 'everything in Ruby is an object' 🤔 shows just how integral object-oriented design is to the language. For me, it's been a great experience to learn about one as I learn about the other.

I thought a some blogs exploring key tenets of OOP, would help improve me as a Ruby programmer and I hope it's of use to beginners out there or for people looking refresh themselves on the fundamentals of OOP theory 🤓

This blog will explore SRP, how to check if our classes are following it and to think about create classes that are more likely to do so from the get-go!

What is the Single Responsibility Principle (SRP)?

  • The SRP is one of the SOLID principles of Object-Oriented Design

  • It's a guiding convention that a class should only have one responsibility or job

  • This is often extrapolated to 'a class should have only one reason to change'

What does this mean in practice? Let's explore the idea with an example...

Example- A new bookshelf

We're the owner of a bookshop and would like to put up a new shelf. We want to design an app that will keep track of the books that go on that shelf.

To make an app that follows the SRP, we'll start by thinking carefully about what classes to create in the first place.

Choosing which Classes to Create

  • A great way to start thinking about what classes need creating is to look at the nouns in our brief

  • When considering each noun, ask if it could represent data or behaviour that would help you in your app's goal

  • For instance, we might think that a Bookshop class would be a good thing to make, but it's unclear how making one would help us in our goal of keeping track of books that go on a particular shelf (if there were many shelves to keep track of, we could return to this idea)

  • In our example, if we think about the data and behaviour we need, we'll probably need a Book and Bookshelf class

  • We can use a Book class to represent the books we add to the shelf as objects. It will need to be able to create objects that represent our books, that bundle data specific to each book

  • We can use a Bookshelf class to keep track of the books currently on that shelf. It will need to have the behaviour of saving our book objects to a variable within itself and adding or removing books from that variable

We've decided what nouns we want to represent as classes and what behaviour and data each class is responsible for.

Our code could end up looking something like this:

class Book
  attr_accessor :title, :author

  def initialize(title, author)
    @title = title
    @author = author
  end
end

class Bookshelf
  attr_accessor :books

  def initialize
    @books = []
  end

  def add_book(book)
    books << book
  end

  def remove_book(book)
    books.delete(book)
  end
end
Enter fullscreen mode Exit fullscreen mode

So far, so good. This code looks sensible enough, but does it follow the Single Responsibility Principle?

How can we be sure either way?

Checking if a Class has a Single Responsibility

Not all examples are as simple as the one we're dealing with. It can be necessary for a class to have multiple methods to manage its responsibility and, as a project's complexity grows, it's important to have a way of deciding whether a object's method falls within it's "responsibility".

For me, the best way that I found to check this, I got from Sandi Metz's 'Practical Object Oriented Design' (a book I can't recommend enough, if you find this topic interesting!).

The technique goes like this:

  • Personify the class in your mind

  • Rephrase the method as a question

  • Ask yourself if it makes sense to ask this class this question

In our example, our Book class is initialised with an 'author' method and a 'title' method.

We could rephrase these methods as "Hi Book, what's your title?" or "Hi Book, who's your author?".

A cartoon book, smiling with a thumbs up

Similarly, we can put the Bookshelf class's methods as something like "Hey Bookshelf, what books are on you right now? Can I add this one? Can I take this one?"

A cartoon bookshelf

All of these questions seem perfectly valid to ask our personified classes. We can feel safe then, that each of these methods fall within its class's 'responsibility'.

A Counter-example

That being said, a good way to learn to do something right is to do something wrong on purpose. With that in mind, let's create a Book class that's responsible for everything and see if it passes our test!

Such a class might look like this:

class Book
  attr_accessor :title, :author

  @@bookshelf = []

  def self.remove_book(book)
    @@bookshelf.delete(book)
  end

  def initialize(title, author)
    @title = title
    @author = author
    @@bookshelf << self
  end
end
Enter fullscreen mode Exit fullscreen mode

If we were to rephrase the .bookshelf method as a question, it might sound like "Book, what instances of you have been added to the shelf so far?". The .remove_book method might be put something like "Book, can you remove an instance of yourself from the shelf?".

A cartoon book, looking confused

These are pretty strange, abstract questions to be asking a book (personified or otherwise)... We can intuitively feel that this isn't something that a 'book' should be doing and that's a hint to us that this class has too many responsibilities.


Summing up

Choosing initial classes that are likely to follow the SRP

  • When deciding on which classes to start with, think about the "objects" (often nouns) in an application's main goal or brief

  • Ask yourself, which of these objects can represent data or behaviour that will help achieve your app's goal?

  • The objects that occur to you are a good candidates for becoming classes

Checking if a class's method falls under its responsibility

  • Personify the class and rephrase the method as a question

  • If the question seems fair to ask this particular class, it likely falls under its responsibility


As a beginner, it may seem strange to read phrases like 'occur to you', 'seems' and 'likely' when reading about something as seemingly technical as programming. There's nothing particularly concrete about whether a question 'feels right' to ask a class.

However, as I've been reflecting lately, a huge part of developing as a programmer though, is developing your decision making process. The old joke goes: the key to being a senior developer are the words "it depends".

Next Time

This blog has focused on the practicals of SRP: how to design classes that are likely to follow it from the ground up and how to check whether a class is doing too much.

But why is Single Responsibility Principle important to our coding decisions? What does it offer our code, or make available to us down the development pipeline? Do our classes always need to have one and only one responsibility?

These are the questions will be explored in my next blog, I hope you enjoyed this one and keep your eyes peeled til then!

Top comments (0)