SOLID Series — 1/5 — Single Responsibility Principle

Ioannis Papikas
6 min readApr 24, 2017

--

This article is part of the SOLID series which describes in depth all the S.O.L.I.D. principles, including examples, good practices, what are the common violations and what are the benefits for the developer and the product itself.

I consider the Single Responsibility Principle to be one of the most important principles in software development, however it can be the easiest to apply and follow.

Disclaimer: no specific structure or architecture is going to be presented here

Definition

Let’s provide the full definition of this principle:

The single responsibility principle states that every module or class should have responsibility over a single part of the functionality (provided by the software), and that responsibility should be entirely encapsulated by the class.

Or in other words:

“A class should have only one reason to change.”

Robert C. Martin

The above reason to change should be fully aligned with the class’ purpose, reason of existence. In other words, we have to answer to a single question first:

Why does this class exist?

Apart from implementing a known design pattern (where all principles should be applied), some of the common answers to the above question are the following:

  • Provide business functionality to an object (like a service that uses the Data Access Layer)
  • Sends emails/SMS/notifications etc. (there should be separate class for each one)
  • Saves files to a specific system
  • etc.

Some of the common answers that are considered wrong:

  • Manages all the objects of a package
  • Sends notifications (what kind? all from one class?)

Extra: In general when there is the word Manager in the name (it’s common practice not to use the Manager suffix for simple objects, ex. Use Company instead of CompanyManager)

As soon as you find the reason why your class exists, you should stick to it and don’t allow it to extend with more functionality and more “responsibilities”, defend this principle.

Exceptions

Factory classes can be an exception to the above description, as Factories can change according to the classes/objects that they create, so they somehow have more than one reasons to change (as many as the classes that they instantiate).

Common pitfalls and bad practices

Defining the principle was the fast and easy part. The real challenges appear when we have to actually apply this principle and protect ourselves from violating it.

This chapter will present some common bad practices that violate the SRP, causing a number of situations (like the butterfly effect) where changing one file/class necessitates changes to multiple classes:

  1. Managing multiple objects
  2. Encapsulation and hidden functionality
  3. God classes

1. Managing multiple objects

A common pitfall and bad practice is when we are creating service classes that are responsible for managing a database object (using Domain Driven Architecture). This common pitfall can be created for two reasons:

  • Manage a family of objects that look the same
  • Add a “child” object to the manager class of the parent object

The first part happens when the class is managing multiple database objects or multiple common interfaces because they look alike.

Example 1

This is a bad example of the class car, which creates multiple different car type classes. The class Car should break and create different classes for each type of car, allowing polymorphism.

Example 2

This is a bad example of the class car, which creates cars and also child objects, and a good example which simply connects child objects to the car. The following example(s) present the case where a class creates child objects. In solid_srp_pitfall2_a_db.php the class BadCar has more than one reasons to change, it has one reason for every object that it manages.

If the Door implementation changes, the BadCar has to change. If the Car implementation changes, the BadCar has to change again.

We solve this issue by splitting the class into two (or more) classes that follow the SRP.

Example 3

This is a bad practice of a notification class. It displays a similar bad practice of a class managing multiple objects or interfaces. In this case, a simple notification class sends notifications using different channels. The side effect is that every time a channel changes, this class has to change as well, violating the SRP.

Moreover, adding more channels into the above class violates also the Open/Closed Principle, but this is a subject that will be discussed on a different article of this Series.

For a more complete way of developing notifications you can take a look at a sample code that I’ve built here.

As a conclusion, try to avoid the following approaches:

  1. Multiple object types from one class. Prefer inheritance.
  2. Child objects in parent classes. If this is absolutely necessary, then your design has some flaws.
  3. Multiple interfaces being managed by one class.

2. Encapsulation and hidden functionality

Sometimes encapsulation seems to make our lives easier by simply hiding or writing together functionality that shouldn’t be there. This happens when a function hides some functionality which is not in the class’ responsibility.

Example

This is a bad example of the class car, which hides the creation of a door inside the function addDoor(). Although the class BadCar seems to be doing fine, the function addDoor() creates a new door and adds the door to the car.

This example presents an excellent opportunity to emphasize that SRP may also apply to the method level, as well.

Although this might seem like a good practice at first, saying to yourself “nice, this way I know what kind of door I’m adding to the car each time”, this violates the SRP by simply causing the class car to change whenever the create() function of the Door changes. Moreover, it’s difficult (if not impossible) to test the addDoor() function of the BadCar class without using this specific door.

3. God Classes

A final case of a common pitfall where the SRP is violated completely is when a class, which handles some generic behavior, is growing uncontrollably over time. We call these classes, God classes/objects, because they not only have too much responsibility but they are doing almost anything they can do.

There are multiple ways to avoid creating God classes:

  • Start from the beginning and maintain the SRP in your project. This includes peer review of the code, considering the SRP, thus do not approve PRs that violate it. Developers can easily fall in this trap mostly by shipping features fast (because the customers need them). Checklists can help a lot when you review a PR, they can help you keep on track without loosing code quality.
  • Fix it before it’s too late. This is when you started working on a project which has already such classes. The best way to fix such issues is to take a step back and try to see the full picture. In these cases usually developers have re-design/refactoring sessions where they propose how and break classes (and sometimes bigger packages/components) into smaller objects in order to apply existing principles. Keep in mind that 1 hour of refactoring session now can save you many hours of debugging later.

Conclusions

This principle is simple and easy by definition but it’s also very easy to violate and don’t realize it at all (or very late). It’s also one of the most important principles that will keep your code organised, structured and separated in correct classes (and packages).

The SRP can be a subjective decision and depends on the experience of the developer/architect and the maturity of detail of the project. Something that might look like it’s good in the same class might need refactoring and breaking in the near future. Also the amount of detail on the project is important, in order to put your limits of breaking your code into smaller pieces.

Mastering this principle can change your way of (development) thinking.

Thanks for reading!

If you liked the article let me know by pressing the Recommendation button or even leave a comment to discuss. Feel free to share and discuss your approaches :-)

--

--