Think in SOLID

Abhishek Ravi 🇮🇳
10 min readJan 8, 2023

Objective

How this article is going to be different from other articles on SOLID Principle which are already on internet. So, I always had problem with the relatability factor while reading those articles which were written in difference perspective or different language. Here, I will go through the thought process while writing some daily swift code and how I apply SOLID to it.

Here, I am not going to bring anything different on the table which is already not on medium, but this article is for all iOS Developer (or, mobile developer) which are struggling to think in SOLID.
This is going to be a bit lengthy article, as I am taking you through a write-code-and-then-fix-it-with-solid template.

Anatomy of SOLID

  1. Single Responsibility Principle
  2. Open-Closed Principle
  3. Liskov Substitution Principle
  4. Interface Segregation Principle
  5. Dependency Inversion Principle

Single Responsibility Principal

✏️ def: Every class should have only single responsibility.

This is very fundamental principal for any developer to understand. Each time while writing your code, always ask yourself if this piece of code can be a reusable code or can we outsource this piece of code to a separate class which will own individual responsibility, if yes then turn that piece of code into a class. And make sure, each class should have an objective and it’s purpose defined. There is nothing wrong in creating too many class files 🙋🏻‍♂️ just to follow this principal.
Sometimes, you will ask yourself — why I am creating this class just for a single method. Trust me, it’s a good habit and approach towards an extensible coding practice.

Situation : You were given to write a Network Client Layer (or, APIManager) for your app whose objective is to call Network APIs and parse the JSON response and then send it to the consumer (or, view controller).

Approach : You will create a Network Client class first, and then expose a public method which will call the API. For now, it will accept a /get URL (for the time being) and completion handler with a response model’s object in it. I have kept ResponseModel as optional type just to make it suited to handle error case.
— on Success, your response model will be populated with response data.
— onFailure, your response model will be nil.

Here, ResponseModel is class which will map the JSON to Swift Type (or, you can serialised model class). Objective of this method is to accept a URL from consumer and get a completion handler (or, async closure) which will either have response model or not.

Now, I want to refactor this completion handler signature as, I can’t pass Error object on failure case. My ViewController can’t tell why API got failed. As of now, they will just get a nil object which is a poor way of telling error occurred.

Here, I have introduced Result Type just to have Response Model and Error type in Completion handler. Now, I can pass model object or error in success/failure cases.

From here, we are going to write URLSession code for GET HTTP Request and then, Parse the JSON into a Response Model Type.

Here, I have just create a HTTP get Request and parsed the JSON to ResponseModel.

Here comes the small violation of Single Responsibility Principal. So, we have written the Http Request code and JSON Parsing in the same class. To make it more reusable, we can outsource the JSON Parsing responsibility to a separate class.

Let’s remove the JSON Decoder part to a separate JSON Parser class —

Now, we will use this class in Network Class.

Here, Network Layer has the responsibility of HTTP Network Calls and JSON Parser class has the responsibility of JSON Decoding.

If I ask you what is the advantage of doing this —
Say, in near future you get some new library which will parse JSON Data to Model Object then you just need to update the JSON Parser class, NetworkLayer will have no change since, Parsing Logic is getting changed. Same for the Network Layer, if we are updating URLSession to Alamofire (very popular iOS Network Library) then you will only bother about HTTP network implementation, JSON Parser Code will remain same.

There is something which I need to refactor in JSON Parser, as we have hard code the ResponseModel type, let’s make it generic so that it can support all the Decodable Type.

✅ Conclusion : Each Class should have single responsibility, if there is more than one, then split it into more classes, even if the class has just single method in it.

Open Close Principal

✏️ def: A class is open for extension and closed for modification.

This seems simple, but how an extension and modification word are different from each other. How will I identify, my class is extensible.

So, we will address this with a regular problem statement. Say, you are creating an eCommerce app which has a payment screen which has multiple Payment Modes like Google Pay, PhonePe UPI and Paypal.

Here, I am assuming every payment request need a OrderId and transactionId to initiate.

Now, you got to know that we need to add an another Payment Mode i.e. Stripe Payment Mode for International Payment System. Then, you need to add extra case for ‘stripe’ in your method block.

So, this is a classic case of Modification of Class. Extension will be when you don’t have to change any line of code.

Next, we are going to re-write the same code and made it extensible and smart 🏆

First, we are going to introduce separate class for all the Payment SDK Mode — GooglePaySDK, PhonePeSDK, Paypal SDK and each class will have their separate implementation of their payment.

Yes, we are celebrating Separation of Concern 🎮

Here, I have created for GooglePay and aded the pay method signature.

If You go to PhonePeSDK or PaypalSDK — all of them have the same pay method signature. Whenever such things come to you, always create a Protocol for that and make it mandatory for concrete class to adopt it.

And, PaymentModeProtocol is the result of that observation.

Here I come, I create a protocol named PaymentModeProtocol which will be adopted by all the Payment Methods like GooglePay, PhonePe and Paypal which will enforce them to adopt the pay method with same signature.

After all the effort, its time to refactor your Payment Gateway Class which has switch method.

Before Refactoring —

After Refactoring —

Ah, just one line of code for big switch block.

Here, I have made the PaymentMethod to PaymentMethodProtocol, since all the concrete payment mode class which I will pass in it, will obviously belongs to PaymentMethodProtocol.

Next, I have removed the switch case, as I will rely on on Concrete Objects (i.e. GooglePaySDK or PhonePeSDK or PaypalSDK) and depending on that it will call pay method of that object.

Now, this is an Extensible class. If you add any new class like StripeSDK class and adopt it with PaymentModeProtocol, you are good to go with your existing flow without adding any extra code. Yeah, PaymentGateway is closed for modification, and always open for extension⚡️

Liskov Substitution Principle

✏️ def: child type must be substitute-able by their parent type.

Assume, you are writing an eCommerce app, where you will have different products type. As thumb rules says, we need to identify the common attributes which is going to be in all the Products Type.

Here, I will write a protocol of Product Type with common attributes. And, I have written an Electronic Product type which is adopting the Product Protocol.

So, whenever I need to show to Payable Price to user then, I will call getPayablePrice() method.

Now, there is an another use-case where Clothing Product will going to have a Discounted Price along with the Discount Value.

So, we will write an another class for ClothingProduct —

As, you can see there is another method which will return the Discounted Price — getDiscountPayablePrice()

Now, we will test our class whether they are following Liskov Substitution Principle. As Liskov Substitution says, Child Objects must be replaceable with their parent type. Now, I will add Product type to their references,

As, you can see getDiscountPayablePrice() method is throwing error as it doesn’t belong to the protocol. So, we need to fix this —

First of all, we will remove the getDiscountedPayablePrice() method as it’s not there is protocol, so we’ll try to keep only those methods which are mentioned in Product protocol.
And, we’ll fix the price calculation logic.

As you can see, child classes references are respecting the parent type. Yes, this is following Liskov Substitution Principle.

Interface Segregation Principle

✏️ def: a class should depend on possible interface rather than a big fat interface.

This principle suggest you to create multiple small interface instead a big fat interface which will have all the required method/ property.

For this, I have a very common example which suits this use case —

You always create a BaseViewController class in your project which is inherited by all the UIViewController class. So that, duplicate code can be minimised.

And, your ViewController will inherit with BaseViewController which will expose all the methods to child ViewController.

Now, this is some old school coding practice, now usually dev write a UIViewController extension or Protocol + Protocol Extension to achieve that. I’ll re-write this same BaseViewController in Protocol+Protocol Extension pattern.

And, you will tell all your UIViewController to adopt this BaseViewControllerProtocol.

Yeah, same code , different pattern.

Here, BaseViewControllerProtocol is the Fat Interface what Interface Segregation principle is warning us about.

Principle suggest us to create small interfaces (or, protocol) instead of one big fat interface. Let’s split the fat interface into small interfaces depending upon their objectivity.

For time being, just ignore the Protocol Naming Convention if its triggering you. Here, I have created three separate protocol.

Now you will ask me, why there is need of creating small interfaces ? Seems like an extra work, Right ?

What if, there is ViewController where there is no need of ErrorView or Loadable then, in fat interface you can’t segregate the capability for your ViewController. Here you can —

Dependency Inversion

✏️ def: If two modules of code are communicating with each other, if one module is injecting the dependency to other then it should always be interface or protocol type instead of concrete type.

Imagine you are writing a eCommerce App. There is a list of products and when you tap on one of them then you navigate to a detail screen.
So, we have two modules — List and Detail.

Detail Screen is always dependent on Product List Module’s item. In other words, Details Screen can’t get rendered if there is No ProductId or Product Object. Here, ProductObject or ProductId is a dependency for Detail Module.

Here, Dependency Module’s initialise method is configurator. Next, we will add one more fix by replacing Product Type with the Product protocol. So that, we can create it as a Loosely coupled code.

Conclusion

SOLID is Design Principle Theory not a pattern and you will find different design pattern based on these principles. Always try to deduced your use-case with these principle. Think in SOLID 🤘🏻

--

--