SOLID Design Principle using Swift
The SOLID principles are the foundation for the implementing/creating effective, maintainable, scalable and loosely coupled system.
It doesn’t matter that you are just begun writing software or have been doing so for years. If you start following these principles while implementing your system it will be an appreciated by every individual contributor in the system.
SOLID stands for what?
Let’s go one by one in SWIFT way,
💎 Single Responsibility Principle 💎
Class should have only one reason to change. It can be changed multiple time over the period of time but within the context of that class. In simple word, it should have the contextual reason to change in the class.
💎 Open and Closed Principle 💎
In simple term, Open for extension but closed for modification. Classes should be open for extending the functionality of the class but should not allow to touch/modify the existing functionality.
Let’s try to understand little bit more from the below use case,
We do have the requirement of payment integration in our application. We do have different payment methods like cash, card, or using different wallet.
If we don’t follow O principle from SOLID then our class implementation might be,
We will have the payment manager class, which is responsible for handling all the payment functionality. We have created different functions for different payment method and all related functionality around that payment method will be resides in it.
😊 😊 😊 Separations of concern 😊 😊 😊
I know, if there is something related to the payment. I have touch the payment manager class.
Looks GOOD, Working perfectly fine!!!
In future, there might be many other payment systems might come. Now we need to modify/change the existing payment manager class and make a provision for the UPI payment system and every time there is new payment system available, we need to modify the payment manager.
What’s wrong in this?
We are modifying the existing code, there is always chance of breaking existing code unintentionally.
Let’s try to design using O principle from SOLID.
The primary mechanisms behind the Open-Closed principle are abstraction and polymorphism.
Each payment method will have a separate class. To add a new payment method, we will be adding a new class and Payment Manager will only responsible for extending himself and will not touch any existing payment method class, which will make us safe from breaking something unintentionally.
I think, unintentionally we are also following the Single Responsibility Principle 😊 😊 😊
💎 Liskov Substitution Principle 💎
This principle is foundation for building code that is maintainable and reusable. This principle is the extension of Open-Closed principle and it means that we must make sure that the new derived classes are extending the base class without changing their behaviour.
To do this, the following Bird base protocol is created 🐦
Developer can create many different kind of bird classes confirming to the Bird protocol.
But, In second version, we need to add the Penguin class and confirms to the Bird protocol, but there is a problem. “🐧 can’t fly.”
If an override method does nothing or just throws an exception, then you’re probably violating the LSP.
When the app is run, all the flying patterns look wrong because the Penguin objects ignore the setAltitude method. The penguins are just flopping around on the ground. Even though the developer tried to follow the OCP, they failed. Existing code must be modified to accommodate the Penguin class.
While a penguin technically “is a” bird, the Bird class makes the assumption that all birds can fly. Because the Penguin subclass violates the flying assumption, it does not satisfy the Liskov Substitution Principle for the Bird superclass.
How to overcome from this problem?
FUNCTIONS THAT USE POINTERS OR REFERENCES TO BASE CLASSES MUST BE ABLE TO USE OBJECTS OF DERIVED CLASSES WITHOUT KNOWING IT.
Robert Martin has an excellent paper on the Liskov Substitution Principle.
💎 Interface Segregation Principle 💎
The Interface Segregation Principle states that clients should not be forced to implement interfaces they don’t use. Instead of one fat interface many small interfaces are preferred based on groups of methods, each one serving one submodule.
In simple words, Clients should not be forced to depend upon interfaces that they don’t use.
We have 2 types of workers some average and some very efficient workers. Both types of workers work and they need a daily launch break to eat.
But now some robots came in the company they work as well, but they don’t eat so they don’t need a launch break. One on side the new Robot class need to implement the IWorker protocol/interface because robots works. On the other side, the don’t have to implement it because they don’t eat.
This is why in this case the IWorker is considered a polluted interface/protocol.
If we keep the present design, the new Robot class is forced to implement the eat method. We can write a dummy class which does nothing (let’s say a launch break of 1 second daily), and can have undesired effects in the application.
According to the Interface Segregation Principle, a flexible design will not have polluted interfaces. In our case the IWorker interface should be split in 2 different interfaces.
Following it’s the code supporting the Interface Segregation Principle. By splitting the IWorker interface in 2 different interfaces the new Robot class is no longer forced to implement the eat method. Also, if we need another functionality for the robot like recharging we create another interface IRechargeble with a method recharge.
It means no run time surprises. At the compile time, we know that Robot can’t eat.
NOTE: I have used the ‘I’ in front of the protocol name just for more clarity of Interface way of doing it. I know that, we don’t use /follow this naming conventions/notations in the Swift language.
Above example is taken from oodesign and converted into swift.
💎 Dependency Inversion Principle 💎
A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
B. Abstractions should not depend on details. Details should depend on abstractions.
Suppose, we are building a company management system in which we have the Management class which is a high-level class, and we have low level class called Employee.
We need to add a new module to our application to model the changes in the company structure by the employment of new specialized employee. We created a new class Employer for this.
Let’s assume the Manager class is quite complex, containing very complex logic. And now we have to change it in order to introduce the new Employer.
Let’s see the disadvantages:
1. We have to change the Management class (remember it is a complex one and this will involve time and effort to make the changes).
2. Some of the current functionality from the management class might be affected.
3. The unit testing should be redone.
All those problems could take a lot of time to be solved and they might induce new errors in the old functionality. The situation would be different if the application had been designed following the Dependency Inversion Principle.
It means we design the Managment class, an IEmployee interface and the Employee class implementing the IEmployee interface. When we need to add the Employer class all we have to do is implement the IEmployee interface for it. No additional changes in the existing classes.
If you pay little more attention to the concepts explain for the last three characters/alphabets in this SO‘LID’ principle, you will realize we are talking about Protocol Oriented Programming.
Read more on POP
- Introduction to Protocol Oriented Programming in Swift by Bob Lee
- Optional Protocol Method by Santosh Botre
- Protocol Oriented Programming in Swift by Karoly Nyisztor
Read more on SOLID