Single Responsibility Principle in object-oriented design (SOLID)
In a previous post, we saw an introduction to the SOLID Principles in a purely theoretical manner. Today, we will delve into the first principle in the SOLID list, The Single Responsibility Principle. Let's start by providing a definition of this principle.
Definition
The Single Responsibility Principle refers to the idea that a class should have only one job or responsibility. In other words, there should be only one reason to change a class. Classes with multiple responsibilities are difficult to maintain.
Example
Let's see a simple yet practical example in PHP. Suppose we have the classes Square
and SquareCollection
to represent a square and a collection of squares, respectively.
class Square
{
private string $color;
private int $size;
public function __construct(string $color, int $size)
{
$this->color = $color;
$this->size = $size;
}
public function getColor(): string
{
return $this->color;
}
public function getSize(): int
{
return $this->size;
}
}
class SquareCollection
{
/**
* @var Square[]
*/
private array $squares;
public function getSquares(): array
{
return $this->squares;
}
public function addSquare(Square $square): void
{
$this->squares[] = $square;
}
}
Now let's say we want to iterate over the squares in ascending order of size. For this, we have used the bubble sort algorithm, so our final code looks like this:
class SquareCollection
{
/**
* @var Square[]
*/
private array $squares;
public function getSquares(): array
{
return $this->squares;
}
public function addSquare(Square $square): void
{
$this->squares[] = $square;
}
/**
* @return Square[]
*/
protected function getOrdered(): array
{
return $this->bubbleSort($this->vegetables);
}
/**
* @param Square[] $squares
* @return Square[]
*/
private function bubbleSort(array $squares): array
{
$size = count($squares);
for ($i=0; $i<$size -1; $i++) {
for ($j=0; $j<$size - 1 - $i; $j++) {
$square = $squares[$j];
$nextSquare = $squares[$j+1];
if ($nextSquare->getSize() < $square->getSize()) {
$squares[$j+1] = $square;
$squares[$j] = $nextSquare;
}
}
}
return $squares;
}
}
Although it works, we can see that the SquareCollection
class has more than one responsibility or, in other words, several reasons to change. The first reason would be the storage of Square
objects, including adding, replacing, deleting, etc. The next reason is the implementation of the sorting algorithm for these objects. In the future, we may not want to sort using bubble sort; perhaps we would like to sort in descending order or make adjustments to the current search algorithm.
Work Delegation
The solution to this issue is, of course, work delegation. Let's separate the bubble sort algorithm implementation from the collection.
class ArrayHelper
{
/**
* @param Square[] $squares
* @return Square[]
*/
public static function bubbleSort(array $squares): array
{
$size = count($squares);
for ($i=0; $i<$size -1; $i++) {
for ($j=0; $j<$size - 1 - $i; $j++) {
$square = $squares[$j];
$nextSquare = $squares[$j+1];
if ($nextSquare->getSize() < $square->getSize()) {
$squares[$j+1] = $square;
$squares[$j] = $nextSquare;
}
}
}
return $squares;
}
}
Now let's use this helper class in our collection class.
class SquareCollection
{
/**
* @var Square[]
*/
private array $squares;
public function getSquares(): array
{
return $this->squares;
}
public function addSquare(Square $square): void
{
$this->squares[] = $square;
}
/**
* @return Square[]
*/
protected function getOrdered(): array
{
return ArrayHelper::bubbleSort($this->vegetables);
}
}
This way, the responsibility of sorting the collection using bubble sort has been delegated to another helper class and is not directly in the collection class anymore.
Although this code is good, we can go a step further and use the iterator pattern to completely remove the getOrdered
method from the collection. I invite you to check out the following GitHub repository with a similar implementation: