Solid principle in swift with example

The SOLID principles are foundational design guidelines in object-oriented programming, helping developers write clean, flexible, and maintainable code. Here’s a detailed look at each SOLID principle with practical Swift examples for iOS:

1. Single Responsibility Principle (SRP)

Definition:
A class should have only one reason to change, meaning it should only do one job.

class NetworkManager {
    func authenticateUserApi(username: String, password: String) -> Bool {
        // Verify user logic
        return true
    }
}

class RealmManager {
    func storeUserDetails(user: User) {
        // store user details logic
    }
}

Now, each class has a single responsibility, making the code easier to modify, test, and extend.

2. Open/Closed Principle (OCP)

Definition:
Software entities should be open for extension, but closed for modification.

Example:
Suppose you want to calculate area for different shapes.

protocol Shape {
    func area() -> Double
}

class Circle: Shape {
    var radius: Double
    init(radius: Double) { 
     self.radius = radius 
   }
    func area() -> Double { 
        return .pi * radius * radius   
    }
}

class Rectangle: Shape {
    var width: Double
    var height: Double
    init(width: Double, height: Double) { self.width = width; self.height = height }
    func area() -> Double { return width * height }
}

class AreaCalculator {
    func calculateArea(shape: Shape) -> Double {
        return shape.area()
    }
}

Now, to support new shapes, add new classes—no change needed in AreaCalculator.

3. Liskov Substitution Principle (LSP)

Definition:
Subclasses should be substitutable for their base class without affecting the correctness of the program.

protocol CanFlyer {
    func fly()
}

class Bird: CanFlyer {
    func fly() { 
     print("Sparrow can flye")
    }
}

class Elephant { 
    /* Does not conform to CanFlyer */ 
}

Now, only birds that fly conform to CanFlyer, preventing misuse.

4. Interface Segregation Principle (ISP)

Definition:
Clients should not be forced to depend on interfaces they do not use.

protocol Printable {
    func printDocument()
}
protocol Scannable {
    func scanDocument()
}

class SimplePrinter: Printable {
    func printDocument() { /*...*/ }
}

class AllInOnePrinter: Printable, Scannable {
    func printDocument() { /*...*/ }
    func scanDocument() { /*...*/ }
}

Printers implement only the protocols they actually need.

5. Dependency Inversion Principle (DIP)

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

The Two Core Rules of Dependency Inversion
  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. Abstractions should not depend on details. Details should depend on abstractions.
protocol Storage {
    func save(string: String)
}

class FileManager: Storage {
    func save(string: String) { /*...*/ }
}

class DatabaseManager: Storage {
    func save(string: String) { /*...*/ }
}

class Handler {
    let storage: Storage
    init(storage: Storage) {
        self.storage = storage
    }
    func handle(string: String) {
        storage.save(string: string)
    }
}

Handler now depends on an abstraction (Storage protocol), making it flexible and testable.