Single Responsibility Principle in Swift

Rodrigo Maximo
Movile Tech
7 min readMar 18, 2020

--

First article of the series of five about SOLID and its use in Swift. The next article of this series can be found here, and it's about the Open Closed Principle.

Introduction

If you are a developer and you have an interest for Software Engineering area, probably you have already heard about a concept called SOLID.

In case you have never heard about it, do not worry, this article is going to introduce a little bit about this acronym and its relationship with the software development. Besides, this article is also going to focus in how to apply it in iOS development, using Swift language code.

What does SOLID mean?

The SOLID term is an acronym for five Programming Design Principles, that has the purpose of facilitating the understanding and the development and also increasing the flexibility and maintainability of a software.

The theory of these principles was introduced by Robert C. Martin (also known as Uncle Bob), an american instructor and software engineer, in his paper Design Principles and Design Patterns in 2000.

If you do not know, or even has never heard about Uncle Bob, he is the author of lots of books related to Software Engineer and good practices of programming and software development. I strongly recommend to search some of his books and papers and also that you read them, since with absolutely sure they are going to change the way of thinking and writing code.

As mentioned before, SOLID is an acronym, that is, each one of their letters corresponds to a initial letter of each one of the principles in which this acronym concerns.

The letter “S” means Single Responsibility Principle, the letter “O” denotes Open-Closed Principle, “L” corresponds to the term Liskov Substitution Principle, “I” refers to the term Interface Segregation Principle, and finally the letter “D” is referring to Dependency Inversion Principle.

In this series of articles it is going to be detailed what each one of these principles means, how to implement them, how to identify when an implementation does not follow one of those principles and also their advantages. It is going to be used some simple examples, with the purpose of facilitating the understanding and the explanation, but be aware that these principles apply to large scale projects and complex codes, where it is really possible to note the importance in following them.

In this first article it is going to be presented the first principle of SOLID, the Single Responsibility Principle.

Single Responsibility Principle (SRP)

The Single Responsibility Principle says that “A class should have just a unique reason to be changed, or in other words, a class should have a single responsibility”.

But after all, what is a responsibility and how to know whether or not a class has just one? A responsibility can be considered as a role that a class is responsible for executing, but a better definition when talking about of this principle would be that a responsibility basically is a reason for changing. This means that if it is possible to think in more than one reason to change a specific class, it is because this class is assuming more than one responsibility.

Example in Swift

For example, considering the following context, where it is shown a class called Square, a class GeometricGraphics and a protocol Persistence.

Firstly, a protocol Persistence can be observed, that is just an abstraction of an eventual implementation of a class that is going to be responsible for saving a Square type object. In this example we are not going to need to implement the concrete class that performs this logic, since this is not the article purpose.

Then, it is presented a concrete implementation of Square class, that is nothing more than the representation of a square. This class has an initialization method which received a persistence object and also an integer that represents the square edge value. Moreover, it contains three methods, one that calculates the square area, another that saves the square with the persistence help, and other that saves the square only if its area is greater than 20.

Finally, there is a class GeometricGraphics, that is responsible for drawing a square in a visual interface, but again here we are not going to implement this logic, since it is not the example purpose.

Problems when not respecting the SRP

This example above demonstrates a case where SRP is violated, since the Square class has more than one responsibility, calculating the area, a geometric responsibility, and at the same time a persistence responsibility, saving an object of its type. Based on the definition presented before, the Square class does not respect the single responsibility principle by the fact that it has two axes of changes.

The first problem of this double responsibility is that an eventual change in the persistence structure, for example in the signature of method save(_ square: Square) in protocol Persistence, will impact in a modification also in the Square class. Since the GeometricGraphics structure depends on Square, after the changes have been done, this structure is going to have to be rebuilt, and in some cases also retested. This is bad because this would happen even the GeometricGraphics class not depending on nothing related to Square persistence.

This may not look like a serious problem in the case of this example, and actually it is not. However, think in a case where the structures are extremely complex modules instead, and with a lot of implementations. It would not be nice have to rebuild and retest a module for nothing, would it?

A second problem arises from the fact that these two responsibilities are also coupled. The saveAccordingToArea() method persists an object depending on its area. This coupling is bad due to the fact that changes in one of the responsibilities can affect the other, and vice versa, and the same problem already mentioned of rebuilding and retest can happen again. Furthermore, this coupling of responsibilities is very common and easy to happen when you have more than one responsibility within a class.

Finally, a third and not less important problem is that having more than one responsibility is going to hinder the testability of this structure, in addition to impairing the reading and understanding of its code.

These identified problems in the example above, although referring to the context of the example, they are generic problems, and therefore identified in any implementation that contains non-unique responsibilities.

Respecting the SRP

There are countless ways to refactor the above code so that it respects the single responsibility principle.

One of them would be to separate the Square class into two new classes: SquareGeometrics and SquarePersistence. The first one would be responsible for containing the area() method and the second one would be responsible for containing the save() method. In addition, the saveAccordingToArea() method would not be present in any one of them, and it would be the responsibility of a third class, or the structure itself that was previously calling this method.

Another way would be to create two protocols instead of two concrete classes, SquareGeometricsProtocol and SquarePersistenceProtocol. These two protocols would contain the area() and save() methods, respectively. Finally, the Square class, which would still exist, would be responsible for implementing these two protocols, and therefore implementing both methods. This approach would solve the problem, given that it would only be possible to import/use one of the protocols, in case a structure only used one of the responsibilities, which is the case below with the GeometricGraphics class, that only uses the SquareGeometrics protocol. Basically doing this we have an approach very close to a well-known Design Pattern: Facade.

Certainly it could be done a little better, separating the concrete implementations. Instead of creating a single Square class, it could be created two different classes, each one implementing each one of the protocols SquareGeometrics e SquarePersistence.

Final Considerations

Although this is the simplest principle in theory, it is one of the most difficult to put into practice. This happens because it is really difficult to identify what a responsibility is, and when a class has more than one of them. With time and practice, and also observing and studying good implementations that follow this principle, it is possible to improve the use of this principle.

It should also be noted that in some cases, a class has two axes of change, that is, two responsibilities, but even so, the separation is not necessary. This scenario can occur when it is concluded that a change in only one of these axes will very rarely occur, or that even if it eventually does, it would not cause a rebuilding or alteration effect in other structures that use it and that only depend on the unchanged responsibility. Thus, separating these two responsibilities would be an unnecessary complexity, and therefore not recommended.

Finally, an always valid and recommended exercise is trying to reserve a small amount of time before or after developing a code, and reflect if the SRP was violated. If it was, it is really healthy to think if there is some advantage in doing this in relation to the advantages that the principle provides, and try to measure if this trade-off is valid or not.

Overview SRP

  • This principle says that classes should have a single responsibility, that is, a unique changing axis.
  • Ensure this condition for a class is positive because:
  1. This is going to avoid unnecessary rebuilding, retesting and refactoring, that may happen when a class has double responsibility and one of them needs to be changed. If there is another class the uses this class before, but only uses the unchanged responsibility, it will have to be rebuilt and retested with this change. What would not happen if the responsibilities were separated.
  2. This is going to help to keep a software with more legibility and testability, where is possible to find and understand easily the responsibility of each code unit.
  • This principle is not so simple to apply. Also, it is not so easy to note when it is being violated. The time and experience for sure are going to help to develop a keen eye and take the best design decisions in a project in terms of its use.

References

  1. What is SOLID
  2. Who is Uncle Bob
  3. Robert C. Martin. Design Principles and Design Patterns, 2000

This was the first article of the series of five about SOLID and its use in Swift. I hope you have enjoyed and feel free to leave feedbacks, suggest improvements or event send me some messages.

--

--

Rodrigo Maximo
Movile Tech

Lead Mobile Engineer at Nubank |  iOS Engineer