SOLID in Swift. Simple explanation with examples for beginners

Principles SOLID is a set of five core principles that help developers create cleaner, more flexible, and more maintainable code. Also, knowledge of these principles is often asked at interviews. Let’s look at each of these principles with examples of violation and compliance in the Swift language:

Single Responsibility Principle (SRP): This principle states that each class or structure should have only one task. For example, if you have a class for processing data from the network, you should not use it to display data on the screen.

Violation of the SPR principle:

// NetworkManager только выполняет сетевые запросы
class NetworkManager {
    func fetchData(url: URL) {
        // Запрос к API
    }

    func updateUI() {
        // обновляет пользовательский интерфейс
    }
}

In this example the class NetworkManager violates the SPR principle, because it both receives data from the network and updates the UI.

Compliance with the SPR principle:

// NetworkManager только выполняет сетевые запросы
class NetworkManager {
    func fetchData(url: URL) {
        // Запрос к API
    }
}

// ViewController управляет отображением данных
class ViewController: UIViewController {
    let networkManager = NetworkManager()

    func updateUI() {
        let url = URL(string: "https://api.example.com")!
        networkManager.fetchData(url: url)
        // Обновить интерфейс пользователя с данными
    }
}

Open-Closed Principle (OCP): Classes and functions should be open to extension, but closed to change. This means that you should be able to add new functionality without changing existing code. This can be done using protocols and extensions in Swift.

Violation of the OCP principle:

class Animal {
    let name: String

    init(name: String) {
        self.name = name
    }

    func makeSound() {
        if name == "Dog" {
            print("Woof")
        } else if name == "Cat" {
            print("Meow")
        }
    }
}

Here is the class Animal is not closed for modification, because if we want to add a new animal, we will have to change the method makeSound.

Compliance with the OCP principle:

protocol Animal {
    func makeSound()
}

class Dog: Animal {
    func makeSound() {
        print("Woof")
    }
}

class Cat: Animal {
    func makeSound() {
        print("Meow")
    }
}

Now every class signed under the protocol Animal can have its own implementation makeSoundand protocol Animal closed for modifications.

Barbara Liskov Substitution Principle (LSP): This means that if you have a class B that is a subclass of class A, you should be able to use B wherever A is used without changing the behavior of the program.

Violation of the LSP principle:

class Bird {
    func fly() {
        // Реализация полета
    }
}

class Penguin: Bird {
    override func fly() {
        fatalError("Penguins can't fly!")
    }
}

let myBird: Bird = Penguin()
myBird.fly()  // Приведет к ошибке во время выполнения

In this example Penguin violates the LSP because it cannot fly while the class Bird assumes that all birds can fly.

Compliance with the LSP principle:

class Bird {
    func move() {
        print("The bird is flying")
    }
}

class Penguin: Bird {
    override func move() {
        print("The penguin is sliding")
    }
}


let myBird: Bird = Penguin()
myBird.move()

Now each subclass Bird can have its own implementation movewithout breaking the behavior of the base class.

Interface Segregation Principle (ISP): This means that classes should not depend on methods they do not use. In Swift, this can be done using protocols.

Violation of the ISP principle:

protocol Worker {
    func work()
    func eat()
}

class Robot: Worker {
    func work() {
        // работает
    }

    func eat() {
        fatalError("Robots can't eat")
    }
}

Here Robot violates the ISP because it is forced to implement a feature eatwhich he cannot use.

Compliance with the ISP principle:

protocol Workable {
    func work()
}

protocol Eatable {
    func eat()
}

class Robot: Workable {
    func work() {
        // работает
    }
}

Now Robot implements only functions that it can actually use.

Dependency Inversion Principle (DIP): This means that top-level classes should not depend on lower-level classes. Both of them must depend on abstractions.

Violation of the DIP principle:

class LightBulb {
    func turnOn() {
        // включает свет
    }
}

class Switch {
    let bulb: LightBulb

    init(bulb: LightBulb) {
        self.bulb = bulb
    }

    func toggle() {
        bulb.turnOn()
    }
}

Here is the class Switch directly depends on the specific class LightBulbwhich violates DIP.

Compliance with the DIP principle:

protocol Switchable {
    func turnOn()
}

class LightBulb: Switchable {
    func turnOn() {
        // включает свет
    }
}

class Switch {
    let device: Switchable

    init(device: Switchable) {
        self.device = device
    }

    func toggle() {
        device.turnOn()
    }
}

Now class Switch depends on an abstraction, not a concrete class, which DIP respects.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *