SOLID explained with iOS examples

ishouldhaveknown

Tamas Dancsi

Posted on June 20, 2024

SOLID explained with iOS examples

The SOLID principles are a set of guidelines for designing software that is easy to maintain and extend.

1. Single Responsibility Principle (SRP)

A class should have only one reason to change, meaning it should only have one job or responsibility.

// WRONG: A class that handles both user authentication and user data storage
class UserManager {
    func authenticateUser(username: String, password: String) -> Bool {
        // Authentication logic
        return true
    }

    func saveUserDetails(user: User) {
        // Save user details logic
    }
}

// CORRECT: Separate classes for authentication and data storage
class Authenticator {
    func authenticateUser(username: String, password: String) -> Bool {
        // Authentication logic
        return true
    }
}

class UserStorage {
    func saveUserDetails(user: User) {
        // Save user details logic
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Open/Closed Principle (OCP)

Software entities should be open for extension but closed for modification.

// WRONG: A class that needs to be modified to support new types of notifications
class NotificationManager {
    func sendNotification(type: String) {
        if type == "email" {
            // Send email notification
        } else if type == "sms" {
            // Send SMS notification
        }
    }
}

// CORRECT: Extendable notification types using protocol
protocol Notification {
    func send()
}

class EmailNotification: Notification {
    func send() {
        // Send email notification
    }
}

class SMSNotification: Notification {
    func send() {
        // Send SMS notification
    }
}

class NotificationManager {
    func sendNotification(notification: Notification) {
        notification.send()
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Liskov Substitution Principle (LSP)

Subtypes must be substitutable for their base types without altering the correctness of the program.

// WRONG: A subclass that breaks the functionality of the superclass
class Bird {
    func fly() {
        print("Flying")
    }
}

class Ostrich: Bird {
    override func fly() {
        // Ostrich can't fly, so this method should not exist here
        fatalError("Ostriches can't fly!")
    }
}

// CORRECT: Using protocol to define a contract for flying birds
protocol Flyable {
    func fly()
}

class Sparrow: Flyable {
    func fly() {
        print("Flying")
    }
}

class Ostrich {
    // Ostrich does not conform to Flyable because it can't fly
}
Enter fullscreen mode Exit fullscreen mode

4. Interface Segregation Principle (ISP)

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

// WRONG: A single interface with too many responsibilities
protocol Worker {
    func work()
    func eat()
}

class Developer: Worker {
    func work() {
        // Coding
    }

    func eat() {
        // Eating lunch
    }
}

class Robot: Worker {
    func work() {
        // Coding
    }

    func eat() {
        // Robots don't eat, so this method is not applicable
    }
}

// CORRECT: Separate interfaces for different responsibilities
protocol Workable {
    func work()
}

protocol Eatable {
    func eat()
}

class Developer: Workable, Eatable {
    func work() {
        // Coding
    }

    func eat() {
        // Eating lunch
    }
}

class Robot: Workable {
    func work() {
        // Coding
    }
}
Enter fullscreen mode Exit fullscreen mode

5. Dependency Inversion Principle (DIP)

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

// WRONG: High-level module depends on low-level module
class Database {
    func save() {
        // Save to database
    }
}

class UserRepository {
    private let database = Database()

    func saveUser() {
        database.save()
    }
}

// CORRECT: High-level module depends on an abstraction
protocol Storage {
    func save()
}

class Database: Storage {
    func save() {
        // Save to database
    }
}

class UserRepository {
    private let storage: Storage

    init(storage: Storage) {
        self.storage = storage
    }

    func saveUser() {
        storage.save()
    }
}
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
ishouldhaveknown
Tamas Dancsi

Posted on June 20, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

SOLID explained with iOS examples
solidprinciples SOLID explained with iOS examples

June 20, 2024