Refactoring under Single Responsibility Principle in OOP
By: Ara Cho
Tags: Programming, Ruby Programming, Object Oriented

I have been reading Sandi Metz’ Practical Object Oriented Design, which is considered one of the best books for learning Object-Oriented Programming (OOP) in Ruby.

One of the first chapters of the book highlights the importance of Single Responsibility Principle, which is one of the five SOLID principles in OOP. It is the idea that a class or method should do the smallest possible useful thing. This makes its implementation easy and flexible for future changes.

I have decided to put my learning to practice by refactoring one of my projects. I had previously made a Tic Tac Toe game while completing the Odin Project’s Ruby Programming Course.

While reviewing the source codes I had written a few weeks ago, I could spot at least a couple of methods that were not in line with the Single Responsibility principle.

For instance, I wrote the following #add method whose function is to (1) prompt the user for input, (2) validate it, and (3) update the game board. There are too many things going on here.

I therefore decided to break this method into the following distinct methods:

Prompting the player for input

Validating the input

Updating the output (game board) based on the input

I got many useful references from this article for working on this.

Prompting the user for input

My old code was asking the player to choose the next spot in the form of two numbers (row number, column number). Then it attempts to map it into two separate integers.

In order to refactor it, I carved out the first part into the following:

This new method’s sole purpose is to get an input from the user. Whether the input is valid or not will be the responsibility of the two other methods it redirects to.

2) Validating the input

Now, let’s have a look at the #validate method below. The method is responsible for checking whether the input is acceptable or not.

I used the #detect method to check whether the input matches any of the spots in the 3x3 board (I also refactored my board into a simple array so that each spot is designated by a single digit number instead of needing a pair of row and column). If the input is acceptable, it will be stored to the @spot variable.

However, if the input is rejected because it is invalid, this will invoke the following #try_again method.

The single responsibility of the this method is to prompt the player to provide another valid input. As you can see, it invokes the #get_valid_input method we saw earlier.

3) Updating the output (game board) based on the input

Now, let’s assume that this time the user provided a valid input. We need to update the game board to mark the spot.

Take a look at the #update_board method above. It uses the @spot variable to mark the board (in this case with ‘X’). It then invokes #display_board method which prints out the board in a formatted way.

As an example, when the user selects ‘3’, then the update_board method will mark @board[3] and display the board like following, tata!

We have successfully broken down the ambiguous #add method into #get_valid_input, #validate, #try_again and #update_board methods, each of which has one clear single responsibility.

Summing up, designing the program according to the Single Responsibility Principle definitely forced me to think more carefully about how to write and design my codes in a structured way.

While this exercise took a lot of my time and efforts, it will be easier to maintain the codes in the future and also for other people to follow. I intend to keep practising with this principle in mind so that it becomes a second nature to me.