Code Review: Single Responsibility Principle

Iain Cambridge | August 16, 2021

Single Responsibility Principle (SRP) is probably one of the most well-known principles from SOLID. At its core is a desire to prevent classes from becoming overwhelming and bloated. While enabling the ability to change how a single thing works by only changing a single class. So the benefits of the Single Responsibility Principle are that you have an easier codebase to maintain since classes are less complex and when you wish to change something you only have to change a single class. In this blog, I will go through some ways to try and help avoid breaching the Single Responsibility Principle while doing code review.

The Manager/Service

A manager or service is a class which has been created to deal with everything related to a certain thing. Generally, these are created to enable Single Responsibility Principle, however, in the majority of cases they generally end up being the main source of non-compliance of Single Responsibility Principle.

In discussion with other developers about a newly created

As an example I will create an InterviewManager:

<?php

namespace App\Interview;

class InterviewManager {
	public function createInterview(Interview $interview) {}
	
	public function deleteInterview(Interview $interview) {}

	public function scheduleInterview(Interview $interview) {}
	
	public function findAllInterviews(Person $person) {}

	public function isInterviewValid(Interview $interview) {}

	public function automaticallyAssignInterview(Interview $interview) {}

	public function postponeAllInterviewsForPerson(Person $person)  {}
}

In the above class there are quite a few methods, while they all relate to interviews there are many responsibilities being held within this class. The following responsibilities can be found within this class:

  • Data Storage
  • Validation
  • Scheduling

Data Storage

So within this class, we have three methods are for just storing data. This is when we create, delete, or read data. This responsibility could change and all the other logic within this class would not have to change.

Validation

We have a method isInterviewValid, again this isn’t related to any other method in this class other than they all work on Interview.

Scheduling

We have three methods that relate to scheduling interviews, those being scheduleInterview, postponeAllInterviewsForPerson, and automaticallyAssignInterview. With automaticallyAssignInterview arguably being another responsibility since I could schedule an appointment without it being assigned to anyone, however, it could also be considered scheduling since it schedules it for the other party.

Solution

<?php

namespace App\Interview;

class InterviewRepository {
	public function createInterview(Interview $interview) {}
	
	public function deleteInterview(Interview $interview) {}

	public function findAllInterviews(Person $person) {}

	public function findInterview($interviewId) {}
}

class InterviewScheduler {
  public function scheduleInterview(Interview $interview) {}

	public function automaticallyAssignInterview(Interview $interview) {}

	public function postponeAllInterviewsForPerson(Person $person)  {}
}

class InterviewValidator {
	public function isValid(Interview $interview);
}

In this solution, I’ve just split the Manager class into three separate classes: InterviewRepository, InterviewScheduler, InterviewValidator. Thus allowing for the proper encapsulation of the responsibilities in each class.

From Usage

Sometimes we can spot Single Responsibility Principle isn’t being adhered to by the usage of the code. One of the ways to identify these cases is when a return value from one method is being used as a parameter for another method in the class.

Example code:

<?php

class InterviewController {
	public function assignInterview($interviewId) {
		$interview = $this→interviewManager→getInterview($interviewId);
		$this→interviewManager->automaticallyAssignInterview($interview);
		return [“status” => “Success”];
	}
}

Like in the previous scenario, we’ve got an overloaded Manager class, however, this can happen with many types of classes. So the responsibilities of the lines of code are:

  • Data storage
  • Interview Assignment

The responsibility overload of data storage and interview assignment in the same class is quite clear, however, what I think isn’t so clear is that there is also a responsibility of saving the data. Somehow automaticallyAssignInterview has to save the data since we’re not seeing any call to a save method. This means the automaticallyAssignInterview somehow has to save it. This means that method even if just moved to another class would be helping breach the Single Responsibility Principle.

Solution

In this solution, we create two classes and call the repository to fetch the data and then assign the interview and then save the data. While this does increase the number of lines within the controller this allows for a more clear understanding of what is happening within the method, as before it wasn’t explicit that the data was being saved.

<?php
class InterviewController {
	protected $repository;

	protected $interviewAssigner;

	public function __construct(InterviewRepository $interviewRepository, InterviewAssigner $interviewAssigner) {
		$this→repository = $interviewRespository;
		$this→interviewAssigner = $interviewAssigner;
	}
	public function assignInterview($interviewId) {
		$interview = $this→repository→findInterview($interviewId);
		$interview = $this→interviewAssigner->automaticallyAssignInterview($interviewId);
		$this→respository→save($interview);	
		return [“status” => “Success”];
	}
}
class InterviewAssigner {
	public function automaticallyAssignInterview($interviewId) {}
}

class InterviewRepository {
	public function findInterview($interviewId) {}

	public function save(Interview $interview) {}
}

Conclusion

Hopefully, these examples will help you to better understand methods of detecting and correcting Single Responsibility Principle infractions during code review.

Author Details
alt
Iain Cambridge

August 16, 2021

Writer

Founder and Creator of the Parthenon Symfony bootstrap. With over 10-years of experience building PHP applications using techniques such as BDD, DDD, SOLID, and TDD just to name a few.

He currently spends his time working on Parthenon to help developers build their Symfony applications without having to deal with the generic functionality that all sites need.

  • FOLLOW ME