Single Responsibility Principe is simple enough to understand and is easy to follow.
But in my work I often encounter a violation of this principle. In this article, I collected the sickest of those ways to break the SPR that I met.
First way: Singleton
Singleton - implies that in addition to its core responsibilities, the class also deals with controlling the number of its own copies, which violates the Single Responsibility Principle.
Very easy to create a singleton. It is enough to copy a couple of lines of code from Wikipedia into your favorite IDE. Singleton is everywhere, you don’t need to bother with transferring the object to the right place and you don’t have to manage the memory either, there is only one instance, and it’s also eternal. This lightness, perhaps, is the reason why Singleton is used for other purposes.
Singleton increases code connectivity
Singleton dependency is not visible in the public interface. You can get and use the copy anywhere, no matter what.
')
Complicated dependencies, and even more hidden ones (those that are not visible in the public interface), lead to the appearance of unexpected effects in parts of the application that, at first glance, are not related to those parts where the code is modified. This leads to unexpected bugs. In addition, such bugs may not be found immediately after making changes, due to the non-obvious scope of functionality affected by the change.
A singleton instance cannot be replaced without a tambourine dance
Singleton makes polymorphism against yourself impossible. So, autotests are impossible. Changing Singleton's behavior during execution is difficult. The need to translate a component into a special mode that requires a different behavior of Singleton causes pain and suffering.
Singleton can store its state
This leads to a change in behavior when changing the order of calls to class methods. Since Singleton is everywhere, it’s almost impossible to control the order of calls. This can lead to a variety of artifacts. This problem exacerbates the negative effects of increasing code connectivity and generates even more sophisticated bugs.
There are more secure spawning patterns.
Increasing the connectivity of the code and the impossibility of replacing its instance successfully solves IoC. Implementing this principle, for example using Dependency Injection, takes the responsibility of controlling the number of class instances and makes the dependencies more explicit.
Despite all the horrors described above, there are places where it is appropriate to use Singleton
A singleton is appropriate in cases where more than one instance of an object cannot logically exist. For example, NSApplication.
In ready-made libraries, using Singleton is justified in situations when there is no need or expensive to customize or replace something.
The second way: mixing architectural layers
There are a huge number of ways to mix architectural layers. Each of the ways is laying out its own rake.
Business logic in the model.
Placing business logic in the model object, we add to it, in addition to the primary responsibility - data storage, additional responsibilities associated with the processing of this data. As a rule, with such an approach, the majority of operations that can be performed with this object accumulate in the model object. What is a multiple SRP violation.
The presence of business logic in the model objects makes it impossible to reuse part of the business logic for another model object, makes it difficult to expand the model, it predisposes to the implementation of polymorphism through copy-paste. When creating a similar object, a part of the code with business logic is copied with minor changes. This approach makes it almost impossible to safely change the part of the logic that is in the model.
To detect such a violation is quite simple, by the presence of any methods in the model object.
Often there are methods for storing the model object in the device’s memory or methods for updating data from a remote resource.
Incorrect use of MVC, MVP, MVVM patterns
All these patterns are quite similar, it can be seen even by abbreviations. Everyone has a View and Model. They differ in the way View interacts with the Model via the intermediate layer.
But each of these patterns has one common drawback - the proliferation of Controller, Presenter, or View-Model. The essence of the problem lies in the misunderstanding of the View component and the component storing business logic (Controller, Presenter or View-Model).
The view must contain logic to display the Controller, Presenter, or View-Model user interface must contain business logic. The MVC iOS SDK deserves the use of words. It imposes the use of MVC. But the UIViewController is not a MVC controller, since in most cases it contains the logic to display the user interface.
This fact makes it almost impossible to implement the correct MVC. At best, the UIViewController becomes part of the View, the business logic is moved to other layers.
On the other hand, after the separation of the logic required to display the interface, in the Controller, Presenter, or View-Model, there may still be too much code left. This is very often due to the fact that a single object containing business logic is created for a single screen, and it can implement several user scripts, which again breaks SRP.
Each object containing business logic must implement no more than one user script.
As a rule, many user scripts can consist of the same operations, saving and retrieving data from local storage, requests to a remote server, etc.
In this case, all the logic that does not relate to the user scenario must be transferred to the infrastructure layer.
The solution to the problem of “expanding controller” will be the competent use of architectural patterns. If you stick to SRP when writing code and take out of Controller, Presenter or View-Model and all code that is not its primary responsibility, the logic will become more transparent, user scripts will be clearer and easier to read.
Third way: NSNotificationCenter
IOS notifications are a great way to connect everything with everything!
NSNotificationCenter is a private, but quite a bright representative of Singleton. Despite the fact that notifications are an interaction pattern (Communication Pattern), and Singleton is a generating pattern (Creational Pattern), notifications retain all the disadvantages of Singleton.
By starting to observate a notification that is not related to the main responsibilities of the class, we are breaking the SRP.
The main problems that arise when using notifications:
Enhancement of the code connectivity is achieved due to the fact that absolutely any class in absolutely any part of the application can send a notification and receive it.
If the notification is sent, we can neither intercept nor replace it.
If several objects are subscribed to one notification, then it is impossible to control the order of receipt of notification.
The solution to the problems described is to abandon the use of notifications in favor of more convenient mechanisms.
An exception may be the use of notifications for notifying about a change in the global state of the application, for example, a change in the state of a network connection or the departure of an application to the background and exit from the background.
Formal signs of SRP violation:
- One of the most noticeable signs is the proliferation of the size of a class or method.
- Class inheritance from more protocols (subject to ISP).
- Using singleton-s can indicate a violation of SRP.
- Transmission of information on the state change of a specific object using the NSNotificationCenter (it is possible to notify about the change of the global state)
- Accumulation in objects of utility methods.
- A larger number of public class methods may also indicate a violation of the SRP.
- A greater number of private methods that can be divided into groups. In this case, each group most likely has its own area of ​​responsibility.