Open-Closed Principle in Swift

Rodrigo Maximo
Movile Tech
9 min readApr 14, 2020

--

Second 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 Liskov Substitution Principle.

Introduction

If you are a developer and you are interested in Software Engineering, you have probably already heard about a concept called SOLID.

In case you have never heard about it, do not worry, the first article of this serie has an introduction here.

Open Closed Principle (OCP)

The Open Closed Principle says that “Software Entities (classes, modules, functions, etc) should be open for extension, but closed for modification”.

This may seem a little bit weird, since it is not an absurd to think that it is necessary to modify a specific code when intending to extend one of its behaviors, which then seems contradictory. With the following examples it will be possible to understand better and see that this even makes sense.

First Example in Swift — Abstraction

Consider the following example, where we have the classes Person and House, which is basically a class that stores all its residents, which are objects from Person type.

This example does not respect the Open Closed Principle, because if it is desired to create a new type of Person, for example creating a new structure called NewPerson, for whatever reason, the class House would have to be modified. Therefore, since an extension of this type would cause a modification, the principle would be broken.

A possible alternative would be to change the Person class to suit exactly the desired changes, and that would make the House class not need to be changed, right? Yes, this really would not cause changes in the House class. However, doing this would also violate the principle, since in this case the Person class would be modified.

Problems in breaking the OCP

Before talking about the solution for this example, however, it may be important to better understand why violating this principle is something bad to a software design.

Considering the assumption of extending the behavior of the Person class, having to change the House class in case that a new NewPerson class is created is bad simply because it does not necessarily need to happen. This can be made clear if this code was designed in another way.

This example may be not that clear, but think of a large scale project, with lots of modules. Certainly it would be much more advantageous if changes in a determined module would not impact nor obligate changes in other modules, right? Well, this is the same case of the above example, nevertheless on an infinitely larger scale. Still, the problem in both cases is exactly the same, the only difference is the size of the effort that is going to be required to obtain the desired extension.

Now, consider the approach where the Person class itself is changed instead of creating a new structure. This also can be inappropriated, since the application may use a Person class in many places, besides the fact that this class code (already in production) securely works and is correct. So, what is the point of changing it, with the possibility of impacting its job and also of generating bugs throughout the application, when this is not necessary and there are other ways to achieve the goal?

Respecting the OCP

A possible way to make first example to respect the OCP is presented next.

Basically, a Resident protocol is created and the Person class is modified to conform to this protocol. Besides, the House class does not depend anymore on Person structure, but on this newly created protocol. This turns the House class closed for modification, in case it would be desired to extend a Person behavior.

In this way, if it is necessary to create a new structure NewPerson (which in Swift could be also a struct, and not necessarily a class) with the purpose of extending the Person bahavior, it is just needed to conform this structure to the protocol, and this will allow the extension to happen without obligating a modification in House.

Second Example in Swift — Enum

Consider now the following second example, where the two presented classes represents deeplinks, HomeDeeplink and ProfileDeeplink, that are nothing more than structures used to present a screen. Each of these two structures contain a type property, of DeeplinkType type. This last structure is an enum responsible for containing each application's screen. Besides that, the two structures HomeDeeplink and ProfileDeeplink implement a Deeplink protocol, that obligates them to have a type property.

Finally, and not less important, there is a Router class, which is responsible for receiving a deeplink and executing it, that is, presenting its associated screen.

This example does not respect the OCP, since if for some reason it is needed to create a new deeplink (that is, to extend the deeplinks behaviors), it will be necessary to modificate the Router execute(_ deeplink: Deeplink) method. This is going to happen by the simple fact that it will be necessary to add a new case in the DeeplinkType enum, what is already a modification, and also because it will also be needed to add the treatment for this new case in the Router method, another modification. This demonstrates that these two structures (Router and DeeplinkType) are not closed for modifications when considering this extension.

Below follows the exemplification of what was mentioned above, that is, the modifications in DeeplinkType and in Router, and also the creation of the new deeplink structure, SettingsDeeplink.

Problems in breaking the OCP

It will not be required a so detailed explanation as in the first example, because basically the problems are really the same, that is, it should not be necessary to change the Router structure to extend another component behavior. If it is not necessary and there is another way to do this, there is no much sense in keeping this approach in mind.

Besides that, it is valid to pay attention to the enum structure. It is not in every situation, but in most of them, when opting to use an enum, probably that the developed code will become something close to what was shown above, that is, a code with a lot of probabilities to break the OCP. This can be a huge problem, since it may exist lots of switch-cases or if-else’s distributed by all the source code associated to this enum. This means the creation of a new case will make all of these places need to be changed, what can be dangerous since it is possible to forget to change some of them, or even it is possible to find really complex and unknown implementation logics, what may not be a good idea to change.

Obviously there will be contexts where enums are necessary, and that is ok, as long as well planned. An alternative would be to isolate the use of the switch-case associated to this enum in a single structure, in order to avoid repetition of code and also to avoid that many modules or classes have to be changed, rebuilt or retested because of an extension.

Respecting the OCP

A possible way to make that second example code to respect the OCP is presented below, with the remotion of the enum and through a default implementation of the execute() method, the method which executes a deeplink.

With that in mind, in case it is desired to add a new deeplink (SettingsDeeplink, for example), it is just necessary to create a new class. It will not be necessary to change the Router execute(_ deeplink: Deeplink) method, nor changing the enum, which does not even exists anymore.

Closure strategy

Something extremely important to let clear to the reader is that it is almost impossible, or maybe impossible, to make a code be 100% closed for modifications. There will always be unthinkable or non addressed scenarios that will lead to a given piece of code to be changed for a new extension to occur.

With that in mind, a really valid and coherent strategy is to try to choose smartly which measures should be taken to achieve the major and the most reasonable possible cases where this code become closed for modifications.

Knowing which are those measures is acquired with time, experience and practice in software design. Something that really helps with that is the experience in the development industry and the contact with customers and users, what makes a developer to acquire repertoire and to be able to predict some of the recurring changing scenarios, what really helps to turn their code closed for those kind of recurrent changes.

Some Heuristics and Conventions

Some heuristics and conventions arose precisely because of this principle. Two of them will be briefly mentioned: Make all variables in a class private and Do not use global variables. Here it is assumed that is is not worth going into too much detail, in order to avoid to escape of the article main subject.

In short, making all the properties of a class private helps to respect the OCP, as it ensures that other classes will not unduly access those properties. This ensures that extensions in this class does not imply in changes in other classes that could be using those properties. In Object Oriented Design we call this encapsulation.

Also, another point to mention here is that properties being private allows a better control of the class in question over all its states. For example, if a property is public, this will allow other classes to use and modify its state at any moment, what can generate some unexpected behaviors throughout the whole application, besides the fact of being necessary to deal with concurrency and ensure atomicity for this variable in some cases.

Talking about not using global variables, the argument is really close to the previous one. No module that depends on a global variable can be consider closed, since any other module can use this variable and change its state. The negative effect of this is exactly the same mentioned before, unexpected behaviors and the necessity of changing classes that use this global variable when some extension be required.

Finally, it is also important to say that these conventions should not be taken so seriously, since there may be scenarios where it makes sense to make a variable public or even to use global variables. This article only presents some arguments that in the most of cases are valid, but that are not necessarily a rule. It is always up to the developer to try to analyze and measure the main advantages and disadvantages of each approach.

Final Considerations

This principle is considered the heart of Object Oriented Design, and it makes all sense, since its importance can be noticed mainly in large scale projects that are poorly designed, where a simple change can cause a number of problems and bugs, making it extremely hard or sometimes even impossible to be implemented.

As mentioned in Closure Strategy section, probably the best way to handle with this principle is to try to see the most likely extension/changing axes, as far as possible, and make the code closed to them.

Overview OCP

  • This principle says that software entities should be open to extensions, but closed for modifications.
  • Ensure this condition is positive for the following factors:
  1. This will avoid that changes or extensions in a module imply in changes in other modules, what can generate overwork for developer and also an unnecessary rebuilt and retest of some modules because of that.
  2. This will also avoid that some new bugs and problems arise, besides facilitating a lot the business rules changes of an application, since the extension will not lead the alteration of other modules.
  • Abstractions (Use of protocols in Swift) in general are going to solve the problem of OCP violation.
  • Enums in general are going to disrespect the OCP, so care must be taken with its use. When concluding that is really necessary to use this structure, it is recommended to centralize the switch-case implementation that uses all of those cases. This is going to avoid modifications in lots of pieces of code throughout the application, when an extension is necessary.
  • It is impossible to turn a code 100% closed for modifications, so the best to do is to try to measure the main changing axes and make the code closed for these axes.
  • The use of private variables inside classes is an heuristic derived from this principle and can also be useful to respect it. The same occur to the heuristic that says to not use global variables.

References

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

This was the second 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