Enumerable Blog
About coding, web development, and fun to be had in tech

Learning to Program: The Single Responsibility Principle

Last Updated:

A concept that is difficult for new programmers to grasp is object oriented design (OOD). Many tutorials cover the concepts of objects and have you build them but it is difficult for the new programmer to see where these concepts will be useful. Here I give some pointers to young programmers who have finished the tutorials but want a better understanding of OOD. Today, I'm focusing on a foundation of OOD: The Single Responsibility Principle.

Single Responsibility Principle

According to Wikipedia:

In object-oriented programming, the single responsibility principle states that every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility.

The Single Responsibility Principle (SRP) makes our code more valuable by making it easy to understand and reuse. By forcing our classes to have just one responsibility, it becomes easier to quickly figure out what various functions and other bits of code do because we already know that it must fit inside the class's responsibility.

The code is also more reusable. If a class has one specific job to do then you can use it whenever you need that job performed. If it has multiple jobs on the other hand, you are forced to either re-write code or suffer additional overhead when you want to perform one of the jobs.

Example: Hello.rb

Learning object oriented design is best through practice! Let's walk through an example and then I want you to try using SRP yourself.

Let's start with a simple program that asks for a user's name and says hello:

puts "Please enter your name:\n"
name = gets.chomp
puts "Hello #{name}"

To make this code reusable let's put it in its own class. Name the new class by the responsibility it will have. I've decided to call my new class Greeter:

class Greeter
  def greet
    puts "Please enter your name:\n"
    name = gets.chomp
    puts "Hello #{name}"
  end
end

Greeter.new.greet

Good, this code yields the same result and follows the SRP (the Greeter class's one responsibility is to greet). Unfortunately, it doesn't seem like a big improvement. Instead of applying the SRP to just classes, let's apply it to methods. Applying the SRP to Greeter's greet method, I can see two distinct responsibilities: Getting the name and saying hello. Let's separate those into new methods.

class Greeter

  def greet
    introduce
    say_hello
  end

  def introduce
    puts "Please enter your name:\n"
    @name = gets.chomp
  end

  def say_hello
    puts "Hello #{@name}"
  end
end

Greeter.new.greet

This is looking much better. The Greeter class has one responsibility and each of it's methods has a single responsibility (the responsibility of the greet method now is performing the Greeter's actions). However, there are some small changes that will make it a little better.

Think about the introduce and say_hello methods. These are actions that the Greeter performs but are they actions that you'd expect to ask the greeter to do or just something the Greeter knows how to do? It's arguable but in my mind it doesn't make sense to ask the Greeter to introduce or to say_hello. In this case, I'll make those methods private. Other programmers that use Greeter won't have to know what the extra methods are, they'll just have to know that the Greeter object can greet.

Secondly, I don't like the instance variable, @name. Think of instance variables as something the object has. "a greeter knows a @name" doesn't make as much sense here as "a greeter knows a @guest_name".

class Greeter

  def greet
    introduce
    say_hello
  end

  private

  def introduce
    puts "Please enter your name:\n"
    @guest_name = gets.chomp
  end

  def say_hello
    puts "Hello #{@guest_name}"
  end
end

Greeter.new.greet

I'm happy with this as far as SRP goes. Every piece of code has a specific purpose that is made clear by its title (Greeter greets, introduce introduces the user, say_hello says hello). Using the Greeter class in other contexts will now be simple and fixing bugs in it will be straight forward.

Decisions, decisions...

Perhaps you decided on a different class name like UserIO or NameCollector. That is okay. While making design decisions, you should follow your best impression for now and not overanalyze. Analyzing design decisions now doesn't protect you from the risk of change. You'll also get better with practice.

Exercise: FizzBuzz

Now you try! Refactor the FizzBuzz program keeping the single responsibility principle in mind. A FizzBuzz program takes a number and returns a value depending on if the number is a multiple of 3 or 5 like this:

Input Output
Multiple of 3 "Fizz"
Multiple of 5 "Buzz"
Multiple of 3 and 5 "FizzBuzz"
Any other number, n n

Here is a quick and dirty implementation (Hard to read! Hard to debug! Hard to extend! Don't write code like this unless you need to):

n = gets.chomp.to_i
puts (n % 3 == 0 && n % 5 == 0) && 'FizzBuzz' || (n % 3 == 0) && 'Fizz' || (n % 5 == 0) && 'Buzz' || n

Email me your solution at nathan at nathanfeaver.com and I'll give you some feedback and share my solution.

Concluding Remarks

Sandi Metz gave me a good method to determine if a class is following the Single Responsiblity Principle: Ask it what it does! Say it out loud: "Mr. Greeter, what do you do?" If its answer is a simple and short; "I greet people", then it's correctly designed. However, if the answer has any commas, 'and's, or 'or's then the class probably needs to be split into two or more classes.

It might be strange at first creating new classes. The grouping of computer tasks is not always intuitive and won't map exactly to real world objects. Keep at it, and don't be afraid to build classes like PostBuilder or TimeChecker.

JOIN THE DISCUSSION
comments powered by Disqus