The Single responsibility principle
3rd Aug 2020 by Aneesh Mistry

Key Takeaways
• Introduction to the SOLID principles of design.
• Understand how the principle of Single Responsibility can be applied to individual classes.
• Review the benefits of the SRP.

The SOLID principles of design

This blog post will be the first in a series of 5 posts that cover the SOLID principles of design. The SOLID principles were introduced by Robert C. Martin in his paper Design Principles and Design Patterns. The SOLID acronym was later defined by Michael Feathers.

The SOLID principles encourage the creation of maintainable software that prevents code rot from accumulating within object orientated programming. As software applications scale in size, the design principles from SOLID become more and more valuable to prevent complex issues from arising and to enable a more efficient process for application enhancement and bug fixes.

The SOLID acronym is defined accordingly:
S - Single responsibility principle
O - Open-closed principle
L - Liskov substitution principle
I - Interface segregation principle
D - Dependency inversion principle

This blog post will focus upon the single responsibility principle (SRP).


Single responsibility principle

The single responsibility principle asserts that any one class must have just one responsibility, and therefore just one reason to change.
Classes that use the SRP will be able to track each of their behaviours back to the single responsibility. Alternatively, classes with multiple responsibilities may contain many reasons to change. As we will later discuss, an update to a class with multiple responsibilities may cause unrelated side effects to the function of the class that can cause further problems within a complex application.

The SRP creates classes that are described as being cohesive and robust. A sign that a class has a well defined purpose and singular responsibility can arise when you are naming the class against the responsibility. If you find it difficult to define a name of a class based upon its responsibility, it is likely the class has many responsibilities or is not clear to a single purpose.


Class cohesion

The word cohesion describes the action of forming a united whole. In object orientated programming, classes are cohesive when they can be used together without conflict. Class cohesion is achieved through the design and implementation of a class to a single purpose. Class cohesion facilitates greater potential for class reusability and implementations of the application.

The implementation of class cohesion can be visually demonstrated through the blocks below:

Non Cohesion

Cohesion

While the blocks are only rotated on the x axis, it is visible that when classes consume just a single responsibility, they become more modular and can be combined with different classes to form different implementations.


Implementing SRP by design

The benefits of SRP will be demonstrated by realising side effects from changing a class with multiple responsibilities.

In our example, the application will be used for renting a car. The class Car exists as an entity to be stored within a database of all cars to be rented. The class will also return properties of the car such as brand, colour and engine size.
The Car class will also include a method called "rent" which sends a request to the database to rent the car.

The Car class has more than one responsibility. It is responsible for holding information about each car instance, but it is also responsible for managing the rental of the car with the database.

public class Car {

    private String brand;
    private BigInteger price;
    private String colour;
    private String key;
    private InventoryService inventoryService;
    
    public void rent(){

        if(inventoryService.getInventory(key) > 0){
            
            ...

        }

    }

}

The rent() method will verify with the Inventory class (not demonstrated in this post) that a car is available before it connects with the database using object to relational mapping to record the update in rental.

The following changes are made to the application:
1. The number of cars has increased in the inventory.
2. The renter must verify their credentials before renting a car.

Increasing the car inventory

The Car class uses the InventoryService to check how many cars are available for rent. When the number of inventory is increased, the Car class, which is responsible for holding information about a car instance, should not be effected. After all, the number of instances of a car does not relate to the properties of a car. As a result, the InventoryService, which is responsible for managing the inventory of the car instances, will be updated. This example demonstrates how SRP creates a single change in a single class (InventoryService) that is responsible for a property (Inventory).

Verify renter credentials before renting

The verification of user credentials is a pre-requisite before enabling a car to be rented. As a result, the Car class must use another service to access the credentials during the rent() method. The Car class, which is meant to be responsible for the car properties information, will now use the RenterDetails class to access information about the renter.

The result of verifying credentials of a renter has created unexpected growth and unrelated dependency of the Car class upon the RenterDetails class. The additional responsibility of the rent() method in the Car class has created two further dependencies on the RenterDetails and InventoryManager classes that otherwise would be completely unrelated to the Car properties.

Reviewing SRP in the Car class

Before the SRP is applied to the Car class, it has a dependency upon the InventoryManager to accommodate for the rent() method. This dependency expands the single responsibility of providing properties of a single car into a further responsibility in renting an instance of the car with the database.

When the application introduces two further requirements, the InventoryManager is not changed, however the Car class must now depend upon the RenterDetails class to verify the person renting the car. The additional dependency breaks the responsibility of the Car class even further:

Multiple responsibility of car

If the Car class had a single responsibility, it would not contain a rent() method and therefore the InventoryManager would not be injected. The rent() method could belong within a different class, "CarRental", that is responsible for leasing cars. CarRental will use the InventoryManager to obtain inventory levels for each car.

The further requirements of the application can be introduced by updating the inventory in the InventoryManager and by injecting the UserDetails class into the CarRental class. Each class maintains its single responsibility, however the updates made to the classes have not increased their responsibility and kept the functions of the application separate from each other.

Single responsibility of car


The benefits of SRP

In the example above, SRP has been used to separate the responsibilities of the classes. The update and enhancement to the application has been made in expected classes and have not expanded their original responsibility. The SRP version of the application promotes the single reason for change as the enhancements only prompt a single change in the class with the responsibility for handling the inventory and for the user verification.

Debugging
If the application were to experience a bug with inventory handling or user validation, the debugging process would initially begin with the expected classes with the responsibility that handles the functions. Without the SRP, the debugging process would have been slightly more complex as the Car class had the excess dependencies upon the InventoryManager and RenterDetails classes. SRP improved debugging an application as responsibilities are specific and adhered to within each class.

Loose coupling
The Car class that uses the SRP has decoupled the responsibility of rental from the responsibility of obtaining Car details. The individual classes can be reused whenever car details are required or leasing services are needed. They are services for a specific purpose and can be reused more efficiently than a single class with many responsibilities.

Testing
Automated testing (unit testing) is typically designed to test the functionality of a class. Each class will have several tests to ensure it is able to complete it's responsibilities. SRP will naturally decrease the collective number of tests required for the application as there are fewer behaviours that can be combined within a single class.


Conclusion

The single responsibility principle asserts that each class will have a single defined responsibility and therefore just a single reason to change.
An application that complies with the SRP will enable the developer to understand the class dependencies and relationships in a more natural sense. The SRP will facilitate the debugging and enhancement of the code as new responsibilities and functionality is introduced.

Series link: Go to the next SOLID principle: Open-Closed Principle


Share this post