Skip to main content

Section 12.3 Single Responsibility Principle

The Single Responsibility Principle (SRP) is a key concept in software design that states that a function should only do one thing. This means that a function should have a single, well-defined purpose and should not be responsible for multiple tasks or concerns.
A simple example of this principle in action is a function that calculates the area of a rectangle. This function should only be responsible for the area calculation and not for any other tasks, such as printing the result or validating input. By keeping the function focused on a single responsibility, we make it easier to understand and maintain. Other functions should be responsible for different tasks, such as displaying the result or handling user input.
A poorly designed function might look like calculateTriangleArea in this sample:
void calculateTriangleArea() {
    double side1, side2, side3;
    cout << "Enter the length of the first side of the triangle: ";
    cin >> side1;
    cout << side1 << endl; // echo the input
    cout << "Enter the length of the second side of the triangle: ";
    cin >> side2;
    cout << side2 << endl; // echo the input
    cout << "Enter the length of the third side of the triangle: ";
    cin >> side3;
    cout << side3 << endl; // echo the input

    double s = (side1 + side2 + side3) / 2;  // Semi-perimeter
    double area = sqrt(s * (s - side1) * (s - side2) * (s - side3));  // Heron's formula

    cout << format("The area of the triangle to one decimal is: {:.1f}", area) << endl;
}

int main() {
    calculateTriangleArea();
}
That function is doing three different jobs:
This makes the function less flexible. What if we want to calculate the area of a triangle and then use that value to do more calculations? We can’t do that easily because the result is just sent to cout. There is no way for other code to access the value produced inside that function.

Insight 12.3.1.

Getting user input and printing out results are distinct tasks from working with data. They should generally not be a part of functions that perform calculations or data processing.
Only a function that’s responsibility is getting input should use cin. Only a function that’s responsibility is printing output should use cout.
It also makes it harder to test each individual part of the function. There is no way to test that the area calculation is correct without also testing the input and output. If we want to change how the area is calculated, we have to change the entire function, which can introduce bugs in the other parts of the function. In this example, there is an extra complicationβ€”it is much more challenging to build unit tests that work with cin and cout than it is to write ones that check the values returned from functions.
We can fix the calculateTriangleArea by giving it the single responsibility of doing the necessary math:
Listing 12.3.1.
Now, if we want to use the results of calculateTriangleArea for further calculations, we can easily do so because it returns a value. For example, we could use it to help build a program that compares two triangles in order to decide if one is larger than the other.
double areaA = calculateTriangleArea(10, 10, 10);
double areaB = calculateTriangleArea(7, 12, 13);
if (areaA > areaB) {
    ...
We can also write an automated unit test for calculateTriangleArea to ensure it behaves as expected:
Listing 12.3.2.
TEST_CASE("calculateTriangleArea") {
    CHECK(calculateTriangleArea(3, 4, 5) == doctest::Approx(6));
    CHECK(calculateTriangleArea(2.5, 4.1, 6.25) == doctest::Approx(3.20322));
    CHECK(calculateTriangleArea(0, 3, 3) == doctest::Approx(0));
}

Checkpoint 12.3.1.

Which are advantages of functions with a single responsibility?
  • They are more flexible.
  • They are easier to test.
  • They are likely to be shorter.
  • They minimize the number of functions we need to write.
You have attempted of activities on this page.