Object Oriented Design: Understanding the Principle of Single Responsibility
Now having just started to grasp some of the foundational concepts, I am aiming to not only my code to pass tests, but also learn to write functional, readable, and effective code. The idea of designing re-usable and maintainable programs is something that has begun to change the way that I think about writing code.
Object oriented design simply put is a process of creating a system in which various objects will interact with each other to solve problems. It is a complex idea with many principles that help achieve the end goal of solving problems. As I started to do my research on OO design, I came across the acronym SOLID, which was first introduced by Dr. Robert Martin, also known as Uncle Bob, an American software engineer who has written several books about clean coding.
SOLID represents 5 basic design principles that aim to make a programmer create a system that is easy to maintain, evolve, and extend over time:
S: Single responsibility principle
O: Open/closed principle
L: Liskov substitution
I: Interface segregation
D: Dependency inversion
In this post, we’ll focus solely on the single responsibility principle.
The single responsibility principle refers to the idea that any class or module should be responsible for a single functionality of a program. In Dr. Martin’s words, a responsibility is defined as having a reason to change. A class should do one thing and that one thing should be entirely completed by the class itself. We can see how this should be applied to modules, methods, etc. By doing so allows the code to be maintainable and readable. It also allows the code to be used within other methods, classes, etc.
Let’s take a look at this simple example below:
Here we’ve created a class called Orc in which the method
.fight_helms_deep has been defined. This method has two responsibilities:
- Retrieve all of the Orcs who are available
- Select only those Orcs who are of the Uruk-hai type to fight at Helms Deep
While this code works just fine, making modifications to
.fight_helms_deep would require double checking that both tasks are working correctly.
What if we wanted to just get the Orcs that were available to find food (i.e. flesh of any kind, like human, Orc, horse, etc.)? We’d have to yet again select all the available orcs, and then select the Other Orcs to get food.
Instead, let’s write the code such that each method has one responsibility.
Here we define three methods:
.available_orcs: collects all orcs who are available to do tasks
.fight_helms_deep: sends those available orcs who are classified as Uruk-hai to fight Helms-Deep.
.collect_food: sends those available orcs who are classified as Orc to find food.
We can see that by doing this, each method in fact has one clear responsibility. This effectively has made our code easier to test, easier to change, and easier to re-use. The
.available_orcs method focuses on just identifying available orcs. This method is then called in other methods when needed. What if we wanted to create a method that finds all available Orcs who are welders to make weapons for the Uruk-hai army? We could just create a new method called
.available_orcs to get our welders.
This refactoring of code is just one of many principles that will help make our code more effective and efficient. As I continue to grow as a software developer, I hope to learn more techniques to design my code in a cleaner maintainable way, which will help in developing my programs in the long term.
Sources: Toptal.com, OODesign.com, IMGFlip.com