This post is part of a nine post series about the SOLID principles and code architecture. You can check all the posts here:
- Software Architecture in Game Development
- The Single Responsibility Principle
- The Open Closed Principle
- The Liskov Substitution Principle
- Conceptual Meaning Of Interfaces
- The Interface Segregation Principle
- The Dependency Inversion Principle
- SOLID: How to use it, Why and When
- SOLID For Unity Monobehaviours
Introduction: What is The Single Responsibility Principle (and what it isn’t)
I will start this post with what is not the SRP. The Single Responsibility Principle does not say that every class should do just one thing. I’ve read this definition and watched so many videos saying that the SRP says that every class should be responsible for doing only one thing that I believe before writing what the SRP is, I should quote the first paragraph from the SRP chapter from Clean Architecture by Bob Martin:
“Of all the SOLID principles, the Single Responsibility Principle (SRP) might be the least understood. That’s likely because it has a particularly inappropriate name. It is too easy for programmers to hear the name and then assume that it means that every module should do just one thing” (Robert C. Martin, Clean Architecture, Pearson Education Inc., 2017)
This actually makes sense. How can we define “one thing” ? Either it will be subjective or eventually each class will have only one method with one statement. I’ve heard arguments that with the SRP, our code eventually will be full of small classes that have many dependencies between them and that this will make our code a mess, hard to read, hard to follow the logic of the whole system between the dependencies of our classes and rigid to any change. These arguments would actually be true if the SRP was saying that each class should be doing only one thing, but the SRP is actually described in a different way:
A module should have one, and only one, reason to change.
A module in OOP is usually a class, but what is defined as a reason? Without a definition of the reason we face the same problem: A reason will be subjective to each programmer.
Below, I will describe what is a reason to change, how the SRP is helpful in our architecture, why the same class that has the same code can follow the SRP or not depending on the requirements of our program and I will give some examples that describe the separation of our classes given certain requirements. But before all that, it will be helpful to describe how the SRP originated.
Origins of the SRP
Conway’s law, is a law that describes how the communication structure of organizations relates to the systems they design. It says:
Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure
Our program is a system that its structure is defined by our classes. Those classes must reflect the structure of the organization that uses our code. We can see that the SRP is a direct analogy to Conway’s law for software.
From the above we can understand that any change that may happen in a class, will have a source and that source is directly related to the users of our program. Each class will have a cohesion that is related to the department of the organization that uses that part of our code. The users that form a department define the methods that should be included in our classes.
What is defined as a reason to change
A reason to change is influenced by the people that have a certain role in the organization that uses our program. This implies that a certain class can still violate the SRP, even if it is composed by behaviours that are useful only to the people of a certain department, if the people in this department serve more than one role. A reason to change is defined by the the different roles that exist from groups of people that use our program.
So a reason to change, is not defined by the code but is defined by the requirements. Who are the people that use our program, for what purpose and what needs they have. When we have a clear idea of who are the groups of people that will use our program in an organization, what roles these people serve and why each group that serves one or more roles needs our program, then we design our classes in a way that represent those groups and roles. This is why when we see a single class without context, when we don’t know who are the users of this class, we cannot conclude if this class violates the SRP or not.
Of course there are certain systems in a program that we know that they have different responsibilities. For example the UI and the business rules or the system that is responsible for the saving/loading from the database and the business rules, but when it comes to the design of classes that are responsible for the core logic of our code, without a deep knowledge of who are the users of our program, we cannot know if our classes violate or not the SRP.
An example of the SRP
Let’s see an example of requirements that will allow us to design our classes in a way that they don’t violate the SRP.
Let’s suppose we are responsible for making a program that holds employees. It is a simple program, that will have the employee name, the job that he has been currently assigned to do and his salary. We can add or remove employees, assign the different jobs and have a report that prints the employees, their current job and their current salary.
Up until now, we don’t have enough information to design our classes in a way so that they don’t violate the SRP.
If we learn that this program is going to be used by a company that has a human resources department which is responsible for hiring and firing employees, a management department that is responsible for assigning jobs and an accounting department that is responsible for paying the salaries and calculating the total costs of those salaries, then we have an idea who are the users of our program and how each group of users will be using it.
With this information, we design our classes in a way that each class will have only one reason to change. The methods that are responsible for changing and calculating the salaries should be in a different class from the methods that are responsible for assigning jobs. This is also true for the methods that will be responsible for adding or removing employees.
Whenever a change is needed in our program, that change will serve a department but also will happen in a class that is responsible for that department only.
Benefits of the SRP
By isolating the reasons to change, we make our code easy to change. Not only we have a clear understanding of where we have to go to make the change we need, but we can also be confident that this change won’t affect other parts of our program.
If a change is needed from the human resources department for example, we can be confident that this won’t affect the way the salaries are calculated because those methods exist in a different class and they don’t share data and behaviours with the class that adds and removes employees.
Even if we find ourselves in a scenario where a bug is introduced by our changes, that bug will be confined in the functionality that is needed by the human resources department. If we had violated the SRP this bug could also affect a different department.
In fact there is an even worse situation that may happen. If we don’t follow the SRP, we can actually correctly code the new functionality but introduce a bug in a different seemingly unrelated part of our program. These bugs are hard to find, because usually we are concentrated coding the new behaviours we need, for a certain group of users and the bug will be discovered by a different group of users, that although the program for them may seem the same as they don’t have a use for the new functionality, after the update it does not behave as before.
An example of SRP that depends on roles
Because the SRP is related to the users and their roles, we can find that a program we have written may suddenly start violating the SRP, even if no new behaviour is needed.
For example, we may have coded a program, that will be used by one person, has a description of expenses and a number that represents the cost of those expenses. Our user wants to be able to add/remove expenses and have a report show on screen or printed on paper. We have separated our classes, in a way that a class has the responsibility of creating the report and two classes that take a report as a parameter. One has the methods that are responsible for showing that report on screen and the other has the methods that are responsible for using the printer to print that report on paper.
If we learn that our user decided that he will be using the printed report not only for him, but also for his accountant, then suddenly, our program doesn’t follow anymore the SRP. The class that is responsible for the printing is now responsible for two different groups of people that serve different roles. This class now has two reasons to change, one is if our user needs any changes for himself and the other is the accountant. Although the needs of the user and the accountant may be the same at the beginning, nothing guarantees that those needs won’t diverge in the future.
Our class that is responsible for printing the report should be separated into two classes, one that is responsible for printing the user report and one that is responsible for printing the accountant report. Creating two methods in the same class, one responsible for printing the report for the user and one responsible for printing the report for the accountant would also be a violation of the SRP.
The important thing here is that although a change in the behaviour of our program wasn’t asked, a change in our architecture is needed because the groups of people that use our program has changed.
SRP and game development
In game development, the users of our program are not only the players of the game. The different groups of game designers, artists, animators, musicians, the people responsible for creating the narrative, even the people responsible for localization should be considered different departments.
Classes should be made in a way that have only one reason to change. If a change is needed in our code that will affect the gameplay mechanics, this change should not be in a class that is also responsible for the animation, sound fx, or even showing dialogs on the screen.
Even if the game is made by a small team, or even a solo developer, these people serve different roles. As long as the classes follow the SRP, changes are easier to code and any bugs will stay isolated to the relevant department. This will make any future changes that we may need, to be implemented faster, but will also remove any fear of change of our code that may exist, from the fact that this change will lead to changes or bugs to unrelated parts of the program.
Conclusion
The Single Responsibility Principle is related to modules that in C# are represented by classes, but the whole idea of the separation of responsibilities is not constrained in classes only. Our components that are composed from many classes and whole systems that are composed from many components should also follow the same logic but at a higher level. Although the SRP is part of the SOLID principles which are principles that describe the architecture at the lower levels of our code, the idea of one reason to change can be found at the higher levels with different names, like the Common Closure Principle and the Axis of Change.
Thank you for reading and as always, if you have any questions or comments you can use the comments section or contact me directly via the contact form or by email. Also if you don’t want to miss any of the new blog posts, you can always subscribe to my newsletter or the RSS feed.