Single Responsibility Principle is the most important and fundamental of all SOLID principles. It is also the most difficult SOLID principle to follow in practice.
In this post we will discuss Single Responsibility Principle in details, and review a real production code from Android Open Source Project in order to understand its importance.
This post is part of a series that describe SOLID principles of Object Oriented Design. If you have difficulties understanding the examples, please read the introduction article.
Single Responsibility Principle (SRP):
As formulated by Uncle Bob, Single Responsibility Principle states:
A class should have one, and only one, reason to change.
Note that this short quote not only defines what SRP is, but also what “responsibility” is. According to Uncle Bob, responsibility is “reason to change”. In the rest of this post we will interchangeably use these synonyms.
SRP looks simple on the first glance, but it is, probably, the most difficult SOLID principle to follow in practice.
Following SRP is difficult because “reasons to change” become certain only in the future, when requirements actually change. At design time, all we can do is estimate which requirements are likely to change and which are stable. In other words, following SRP inevitably involves some amount of prophetic work.
For inexperienced developers using imagination might be the only way to come up with “reasons to change”. Experienced developers, on the other hand, will probably know about common types of unstable requirements from the past projects.
One effective way of identifying “reasons to change” is to learn about the business domain that is targeted by the application. Since most of the requirements come from business domain, there is a high probability that any instability in that domain will propagate into the requirements. Instability can result from both an intrinsic instability of the domain (e.g. pricing strategy changes), or inability to capture all the business needs into requirements at once. Unfortunately, in practice, developers don’t know much about the business domain they are working in (not necessarily their fault, though).
In addition to being the most difficult to implement, SRP is also the most fundamental SOLID principle. It represents the general idea upon which other SOLID principles expand. Therefore, in discussion of other SOLID principles, we will often notice some overlap between them and SRP.
Single Responsibility Principle in Context:
Let’s now review a real production code from Android Open Source Project through prism of SRP.
Consider these methods of
Method #1 provides access to system services that control specific aspects of OS and device functionality (e.g.
TelephonyManager). By exposing this method,
Context implements Service Locator patter.
Method #2 checks whether the application has a specific permission (Android implements security model in which access to resources can be restricted by permissions). By exposing this method,
Context implements security related functionality.
Method #3 performs lookup of static localized strings bundled with the application. By exposing this method,
Context implements static resources related functionality.
Clearly, the above three responsibilities are completely unrelated – each of them represents different “reason to change”. Therefore,
Context violates SRP. In fact, we saw just three methods, but
Context‘s API contains many more. Based on this API, we could easily identify 10+ different responsibilities.
The aforementioned responsibilities of
Context are very broad. Any class that has reference to a single
Context object can access practically every resource or service on the device. Therefore,
Context is “God Object” – it has too many responsibilities and “knows” too much about the environment.
Consequences of Violation of Single Responsibility Principle:
Below are what I think the most serious consequences of SRP violation by
Context. Keep in mind, however, that this list is probably incomplete due to my limited perspective.
The documentation of
Context and its sub-classes is often dated, inconsistent or simply incorrect. There are probably many reasons for this, but violation of SRP is one of them.
It boils down to the fact that each method in
Context‘s API needs to be documented. Because
Context is God Object, there are 2000+ lines of javadoc documentation in
Context.java alone. It is simply impossible to go over this amount of text in order to ensure that all the relevant parts get updated each time changes occur.
In addition, documentation should be kept consistent between
Context and all its sub-classes.
Activity, for example, has additional 1500+ lines of javadoc documentation. It means that in order to ensure that documentation remains updated and consistent, developers need to go over 3500+ lines of javadoc documentation that is spread across multiple classes.
Individual human beings can’t keep such a huge amount of documentation in sync and up to date. Therefore, documentation becomes less accurate with each additional change.
Documentation problem is annoying and time consuming, but it is just a blip when compared to another consequence of
Context having too many responsibilities.
Context is a God Object, when a class has a reference to
Context, encapsulation breaks. It doesn’t matter anymore how carefully the system was designed – the class with reference to
Context can reach out to any resource or service on the device.
The consequence of encapsulation loss is that classes that have reference to
Context can use whatever methods from
Context‘s API they need. And why not? If framework provides such an easy way to access resources and services, why wouldn’t framework’s users leverage it in order to quickly write their own code?
The problem is that by leveraging multiple
Context‘s responsibilities in a single class, that class immediately breaks SRP by itself. This way,
Context‘s SRP violation intoxicates the new code, and spreads across the entire codebase as a virus.
Encapsulation loss and SRP violation spreading lead to “spaghetti code”.
Service classes extend
Context and are the basic “building blocks” of any Android application.
When Android developer needs to add a screen to the application, he must extend
Activity with screen specific logic. Similarly, when adding background task, developer extends
Service with task specific logic. The ultimate result of this process is that Android applications are composed of many God Objects.
As discussed in the previous section, SRP violations tend to spread. In Android, where practically all application specific logic must be added on top of God Objects, following SRP is practically impossible.
Even experienced developers, who strive to write SOLID code, can’t completely enforce SRP in Android. Inexperienced Android developers, encouraged by the design of Android framework and the existing documentation, usually write “spaghetti code”.
It is not uncommon for big Android projects to have
Services with 500+ (or even 1000+) lines of code. It takes non-trivial amount of motivation, discipline and experience to write code that follows SRP in Android.
I’m curious to know how do Google’s developers feel when they need to make changes in
Context or its sub-classes.
If that would be me, I’d be afraid to touch any of these. There is so much code in these classes, and different responsibilities coupled in such an unexpected ways, that modifying these classes is probably akin to disarming a bomb.
In this post we discussed the most important and fundamental of all SOLID principles – Single Responsibility Principle.
I hope that the review of consequences of Single Responsibility Principle violation helped you in understanding just how important this principle is.