SOLID Principles in iOS Development 
Tags: swift, ios, engineer, solid
Published: 2021-11-06 10:23:18

---
title: SOLID Principles in iOS Development 
published: true
description: Design principles to build better softwares
tags: #swift #ios #engineer #solid
//cover_image: https://user-images.githubusercontent.com/34096743/140605926-0c8a7c72-a545-4f2a-84a8-3ffe86cc95c8.jpeg
---

##SOLID Principles in iOS Development 

## 😩 Ok, you probably already hear this term, but in practice, what's mean? When uses? How uses? Keep reading for *FINALLY LEARN*

![Alt Text](https://media.giphy.com/media/AsGnrla1K6FN7ajonc/giphy.gif)



## First of all, SOLID is a s a mnemonic acronym for five design principles intended to make software design more understandable, flexible, and maintainable 

## In all tutorial, i will show SOLID Principles in action and codes that don't follow SOLID

## **So, let's go 😁**

*****

## *S* - Single Responsibility Principle 

### A classe should have one reason to change ( to exists ) 

    
#### ***Tips to apply***

* Ask yourself for every ***entity*** ( classes / functions ) : ***this entity, does more than one thing?***

* You shouldn't have use the word ***and*** when have to talk the responsibility to entity 

### 💢 ***NOT*** Single Responsibility Principle apply 

```swift
class Handler_NOT_SOLID {
    
    func handle() { 
        
        let data = requestDataToAPI()
        guard let dataReceive = data else { return }
        let array = parse(data: dataReceive)
        saveToDB(array: array)
        
    }
    
    private func requestDataToAPI() -> Data?{
        // send API request and wait the response
        return nil
    }
    
    private func parse(data:Data)->[String]?{
        // parse the data and create an array
        return nil
    }
    
    private func saveToDB(array:[String]?){
        // save array in a database
    }
}
```

***Can you see?***


#### ***Handler_NOT_SOLID*** class  have several responsibilities 

1. Send request to API
2. Create an array with data receive
3. Save array in a database

### 👍 Single Responsibility Principle ***apply***

```swift
class Handler_SOLID {
    
    let apiHandler: APIHandler
    let parseHandler: ParseHandler
    let dbHandler: DBHandler
    
    init(apiHandler: APIHandler, parseHandler: ParseHandler, dbHandler: DBHandler) {
        self.apiHandler = apiHandler
        self.parseHandler = parseHandler
        self.dbHandler = dbHandler
    }
}


class APIHandler {
    
    func requestDataToAPI() -> Data?{
        // send API request and wait the response
        return nil
    }
}

class ParseHandler {
    func parse(data:Data) -> [String]?{
        // parse the data and create an array
        return nil
    }
}

class DBHandler {
    func saveToDB(array:[String]?){
        // save array in a database
    }
}
  
```

***Now, each entity have just one responsibility***

****

## *O* - Open/Closed Principle 

### A software entity should be open to extension, but closed for modification.

#### ***Tips to apply***

1. If you want to modify a class every time a new behavior is added, something isn't quite right
2. If/else/switch statements don't be used to modify a behavior 

### 💢 ***NOT*** Open/Closed Principle apply 

```swift
class Vehicles_NOT_SOLID {
    
    func printData() {
        let cars = [
            Car_NOT_SOLID(name: "Batmobile", color: "Black"),
            Car_NOT_SOLID(name: "SuperCar", color: "Gold"),
            Car_NOT_SOLID(name: "FamilyCar", color: "Grey")
        ]
        
        cars.forEach { car in
            print(car.printDetails())
        }
        
        let buses = [
            Bus_NOT_SOLID(type: "School bus"),
            Bus_NOT_SOLID(type: "Minibus"),
            Bus_NOT_SOLID(type: "Minicoach")
        ]
        
        buses.forEach { bus in
            print(bus.printDetails())
        }
    }
    
}

class  Car_NOT_SOLID {
    let name:String
    let color:String
    
    init(name: String, color: String) {
        self.name = name
        self.color = color
    }
    
    func printDetails() -> String {
        return "name : \(name) color :\(color)"
    }
    
}


class Bus_NOT_SOLID {
    let type:String
    
    init(type: String) {
        self.type = type
    }
    
    func printDetails() -> String { 
        return "bus type : \(type)"
    }
}
  
```


***Can you see?***

### When **printData** receive other type of object, we have to add more rules to work 

***If you want to add the possibility to print also the details of a new class, we should change the implementation of printData every time we want to log a new class***


### 👍 Open/Closed Principle ***apply***


```swift
protocol Printable {
    func printDetails() -> String
}

class Vehicles_SOLID {
    
    func printData() {
        let cars:[Printable] = [
            Car_SOLID(name: "Batmobile", color: "Black"),
            Car_SOLID(name: "SuperCar", color: "Gold"),
            Car_SOLID(name: "FamilyCar", color: "Grey"),
            
            Bus_SOLID(type: "School bus"),
            Bus_SOLID(type: "Minibus"),
            Bus_SOLID(type: "Minicoach")
        ]
        
        cars.forEach { car in
            print(car.printDetails())
        }
    }
}

class Car_SOLID:Printable {
    let name: String
    let color: String
    
    init(name: String, color: String) {
        self.name = name
        self.color = color
    }
    func printDetails() -> String {
        return "name : \(name) color :\(color)"
    }
    
}

class Bus_SOLID: Printable {
    let type: String
    
    init(type: String) {
        self.type = type
    }
    
    func printDetails() -> String {
        return "bus type : \(type)"
    }
}
```

***We don't need change behavior of printData, just make a layer between printData and the class***



***

## *L* - Liskov Substitution Principle

### Objects should be replaceable with instances of their subtypes without altering the correctness of that program.

 
#### ***Tips to apply***

1. Instead of one monolithic interface, break an interface up based on what implementers should be doing
2. Keeps consumers from having to much power

### 💢 ***NOT*** Liskov Substitution Principle apply 

```swift
class Rectangle_NOT_SOLID {
    var width: Double = 0
    var height: Double = 0
    
    var area: Double {
        return width * height
    }
    
   
}

class Square_NOT_SOLID: Rectangle_NOT_SOLID {
    override var width: Double {
        didSet {
            height = width
        }
    }
}

// MARK: - Implementations 
 func printArea(of rectangle: Rectangle_NOT_SOLID) {
     rectangle.width = 10
     rectangle.height = 4
     
     print(rectangle.area)
 }
 
 let rect = Rectangle_NOT_SOLID()
 printArea(of: rect) // 40
 let square = Square_NOT_SOLID()
 printArea(of: square ) // 40

```

***See that printArea(of rectangle:Rectangle_NOT_SOLID) return the same result with different types instead return specific value of each class***



### 👍 Liskov Substitution Principle ***apply***

```swift
protocol Polygon {
    var area :Double { get }
}

class Rectangle_SOLID: Polygon {
    
    let width:Double
    let height:Double
    
    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }
  
    var area: Double {
        return width * height
    }
    
}

class Square_SOLID: Polygon {
    let side:Double
    
    init(side: Double) {
        self.side = side
    }
    
    var area: Double {
        return pow(side, 2)
    }
}

/// MARK: - Implementations  

 func printArea(of polygon:Polygon){
     print(polygon.area)
 }

 let rect = Rectangle_SOLID(width: 10, height: 40)
 printArea(of: rect) // 400.0
 let square = Square_SOLID(side: 10)
 printArea(of: square) // 100.0

```


***

## *I* - Interface Segregation Principle


### (M)any client-specific interfaces are better than one general-purpose interface

#### ***Tips to apply***

1. Instead of one monolithic interface, break an interface up based on what implementers should be doing
2. Keeps consumers from having to much power


### 💢 ***NOT*** Interface Segregation Principle apply 


```swift 
//MARK:- Fat Interface (Protocol)
protocol GestureProtocol {
    func didTap()
    func didLongPress()
    func didSwipe()
}

class RichButton_NOT_SOLID: GestureProtocol {
    
    func didTap() {
        print("tap button")
    }
    
    func didLongPress() {
        print("long press")
    }
    
    func didSwipe() {
        print("swipe")
    }
    
}

class PoorButton_NOT_SOLID: GestureProtocol {
    func didTap() {
        print("tap")
    }
    
    func didLongPress() {}
    
    func didSwipe() {}
    
}
```

***See that PoorButton_NOT_SOLID class have methods that not are usable***


### 👍 Interface Segregation Principle  ***apply***


```swift
protocol TapGesture {
    func didTap()
}

protocol LongPressGesture {
    func didLongPress()
}

protocol SwipeGesture {
    func didSwipe()
}

class RichButton_SOLID: TapGesture, LongPressGesture, SwipeGesture{

    func didTap() {
        print("tap button")
    }
    
    func didLongPress() {
        print("long press")
    }
    
    func didSwipe() {
        print("swipe")
    }
    
    
}


class PoorButton_SOLID: TapGesture {
    func didTap() {
          print("tap button")
      }
}

```

***Now we remove all unnecessary methods***


*** 

## *D* - Dependency Inversion Principle

### High-level modules should not depend on low-level modules. Both should depend on abstractions.

#### ***Tips to apply***

1. We’re used to this : High level ***->*** (uses) Low level
2. High level ***->*** (expects) interface ***<-*** ( fulfills ) low level


### 💢 ***NOT*** Dependency Inversion Principle apply 

```swift
class SaveData_NOT_SOLID {
    
    let filesSystemManager = FilesSystemManager_NOT_SOLID()
    
    func handle(data:String){
        filesSystemManager.save(data: data)
    }
}

class FilesSystemManager_NOT_SOLID {
    
    func save(data:String){
        // save data
    }
}
```

***With this, we have just a way to save Data. If we want use any DataBase?***


### 👍 Dependency Inversion Principle ***apply***

```swift

protocol Storage {
    func save(data:Any)
}

class SaveData_SOLID {
    let storage:Storage
    
    init(storage: Storage) {
        self.storage = storage
    }
    
    func handle(data: Any){
        self.storage.save(data: data)
    }
}

class FilesSystemManager_SOLID: Storage {
    func save(data: Any) {}
}

class MySQLDataBase: Storage {
    func save(data: Any) {}
}
```

***Now, we can use any storage method***


***👊 Thanks for read!! I hope that you have understand all principles about SOLID and how apply in your day by day for build a better software. In doubt, please put your comment below and i will try to help.***

[Github Project](https://github.com/mkilmerr/SOLID_Principles_Swift)