S.O.L.I.D – The Single Responsibility Principle (SRP)




Abstract

This article is the 3rd of a series of seven articles I wrote (described later on) about The Principles of Object Oriented Design, also known as SOLID.

This post describes the Single Responsibility Principle (SRP).


As a prerequisite, please read the first 2 articles:
            2.     Software Design Smells


As architects and solution designers we’d like to design and write robust & clean solutions.

We’d like our code to be stable, maintainable, testable and scalable for as long as our product lives.

We’d like to implement and integrate new features as smoothly as possible in our existing (already tested and deployed) code, without any regression issues, and by maintaining dead-lines and production deployments.

In addition, we’d like our code to perform according to standard performance KPIs, and eventually, to serves its purpose.

Such desire has many obstacles, especially as our application evolves and our product become more complex.

More often than we’d like, we are experiencing these obstacles which could be described as software design smells.

The purpose of the SOLID principles is to achieve these desires and to prevent software design smells.

In this article series we’ll emphasize the importance of well-designed applications.

We’ll show how, by maintaining the SOLID principles in our code, we’ll create better robust solutions, provide easier code maintenance, faster bug fixing and the overall impact on the application performance, especially while adding new features to existing design, as the application evolves with new requirements.



The complete series:
           
Intro:
               1.      SOLID - The Principles of Object Oriented Design
               2.      Software Design Smells

S.O.L.I.D
               3.      The SingleResponsibility Principle (SRP)
               4.      The Open-ClosePrinciple (OCP)
               5.      The LiskovSubstitution Principle (LSP)
               6.      The InterfaceSegregation Principle (ISP)
               7.      The DependencyInversion Principle (DIP)




Content

  1. Introduction
  2. Case Study
-        Requirements
  1. Implementation that does NOT conform to the SRP
  2. Implementation that conforms to the SRP
  3. Summary




Introduction

The single responsibility principle states that:

A class should have only one responsibility and only one reason to change.

This means that when we are writing a class its members and behavior should represent only one responsibility, otherwise, we could experience undesired coupling between the class different responsibilities.

As young developers, we studied object oriented programing, and one of the first thing we learned was Encapsulation. We were encouraged to add everything that conceptually relates to a class to its members and functionality.
Meaning, we needed to encapsulate all responsibilities in that class.

Such design usually smells as Rigidity & Fragility, since when a class has more than one responsibility, it would also have more than one reason to change (as the requirements grow), and modifications to one responsibility could unexpectedly affect the others.

I’ll illustrate this with a simple Employee class.




Case Study

Consider the following requirements and different implementations.


Requirements:

We need to create an ‘Employee’ class with the following capabilities:

           1.      Computes the employee’s expected pension (based on its salary).
           2.      Presents the employee’s titles and different positions in the company (over the years).

We could argue that conceptually both responsibilities are related to the ‘Employee’ class, thus we need to encapsulate all of those functionalities in one place.

However, these responsibilities are needed in different modules (or even in different applications):

          1.      Finance module (application) – computes the Employee’s pension.
          2.      Graphical module (application) – presents the Employee’s details.
          (E.g. in the company’s portal in an organizational structure)

Every change in one responsibility could cause a change (and different potential problems) in the other.

If for example, we modified the computation of the Employee’s pension (one or more methods and members in the Employee’s class), then beside building the Finance module (application), we would also have to build, retest and redeploy the Graphical module (application) as well, or otherwise we could experience unexpected behavior in that module.

In addition, if one responsibility would require additional assemblies (DLLs), for instance when computing the Employee’s pension, we need to use additional Finance related modules (DLLs), then these modules would also be required in the Graphical module (application).

Meaning to be recompiled and redeployed with the Graphical module (application), even though they are not used in that module at all.




Implementation that does NOT conform to the SRP

using System;

namespace DesignPrinciplesExamples.BOs
{   
    // The Employee class comprises 2 responsibilities:
    //     1. Computes the employee’s expected pension.
    //     2. Presents the employee’s titles and different
    //        positions in the company (over the years).
    public class Employee
    {
        #region Public properties

        public int Id { get; set; }
        public float Salary { get; set; }       
        public DateTime HiredYear { get; set; }

        // TODO: Add relevant data members...

        #endregion Public properties

        #region Public methods
       
        // This method computes the Employee's pension.     
        public float? ComputePension()
        {
            // TODO: Implementation...!
            
            return null;
        }
       
        // This method prints the Employee's titles.
        public void PrintTitles()
        {
            // TODO: Implementation...!
        }

        #endregion Public methods
    }
}




Implementation that conforms to the SRP

In order to create a design that conforms to the SRP, we’ll extract these responsibilities into 2 classes.

One potential implementation could be the following:

          1.      Employee class
          2.      EmployeeFinanceManager class


using System;

namespace DesignPrinciplesExamples.BOs
{
    /// The Employee class.
    public class Employee
    {
        #region Public properties

        public int Id { get; set; }
        public float Salary { get; set; }
        public DateTime HiredYear { get; set; }

        // TODO: Add relevant data members...

        #endregion Public properties

        #region Public methods
     
        /// This method prints the Employee's titles.
        public void PrintTitles()
        {
            // TODO: Implementation...!
        }

        #endregion Public methods
    }
}


namespace DesignPrinciplesExamples.BOs
{
    public class EmployeeFinanceManager : IEmployeeFinance
    {
        #region Public methods
       
        /// This method computes the Employee's pension.
        public float? ComputePension(Employee employee)
        {
            // TODO: Implementation...!

            return null;
        }

        #endregion Public methods
    }
}


Remark:

This is a very simple example to emphasize the importance of the Single Responsibility Principle, naturally when designing & implementing a real solution, we’ll add additional relevant properties, methods and objects as needed. (E.g. a Pension class)

In addition, any other solution that conforms to the SRP is better than adding those 2 responsibilities to one class.




Summary

I would say that it’s quite easy to understand the Single Responsibly Pattern, its importance, how it prevents ‘design smells’ and the general effect on our codebase, with respect to the ‘separation of concern’ concept, whether we are designing a method, a class, or a module.

However, it’s not always easy to fully separate responsibilities, since we are sometimes eager to implement and encapsulate all our requirements in the same class (or module), without considering future implications.

So, as an incentive to practice the SRP (and the rest of the SOLID principles), I would suggest to maintain the ‘set of mind’ to always remember the negative effect of software design smells, on our application, development-time, bugs, performance and on the entire product life-cycle.

In addition, a good practice would be to perform proper unit tests and simulate all potential clients to our implementation.
This way it would be much easier to discover the different responsibilities according to the requirements.


---
Next in the SOLID series is the Open-Closed Principle, which describes how to implement new features without modifying old existing code (which was already tested and deployed), only by extending our code, in such a manner that would prevent software design smells.




The End

Hope you enjoyed!
Appreciate your comments…

Yonatan Fedaeli


No comments: