Что такое SOLID на примерах Swift

SOLID - это акроним, представляющий пять основных принципов объектно-ориентированного программирования и проектирования. Вот они:

  1. S - Принцип единственной обязанности (Single Responsibility Principle):
    • Класс должен иметь только одну причину для изменения. Это означает, что класс должен быть ответственен только за один аспект функциональности программы.
  2. O - Принцип открытости/закрытости (Open/Closed Principle):
    • Программные сущности, такие как классы, модули и функции, должны быть открыты для расширения, но закрыты для модификации. Это означает, что код должен быть гибким для расширения новой функциональности без изменения существующего кода.
  3. L - Принцип подстановки Барбары Лисков (Liskov Substitution Principle):
    • Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы. Это означает, что подклассы должны быть взаимозаменяемыми с их суперклассами.
  4. I - Принцип разделения интерфейса (Interface Segregation Principle):
    • Клиенты не должны зависеть от методов, которые они не используют. Это означает, что интерфейсы должны быть разделены на более мелкие и специфические интерфейсы, чтобы клиенты могли использовать только те методы, которые им нужны.
  5. D - Принцип инверсии зависимостей (Dependency Inversion Principle):
    • Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций. Кроме того, абстракции не должны зависеть от деталей, но детали должны зависеть от абстракций.
  • Принцип единственной обязанности (Single Responsibility Principle - SRP): Каждый класс должен иметь только одну причину для изменений. Это означает, что класс должен быть ответственен только за один аспект функциональности программы.

Пример на Swift:

class FileManager {
    func reFile(fileName: String) -> String {
        // Чтение файла
    }

    func writeFile(fileName: String, content: String) {
        // Запись в файл
    }
}

Этот класс отвечает только за чтение и запись файлов, что соответствует принципу единственной обязанности.

  • Принцип открытости/закрытости (Open/Closed Principle - OCP): Программные сущности, такие как классы, модули и функции, должны быть открыты для расширения, но закрыты для модификации. Это означает, что код должен быть гибким для расширения новой функциональности без изменения существующего кода.

Пример на Swift:

protocol Shape {
    func area() -> Double
}

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 Circle: Shape {
    var radius: Double

    init(radius: Double) {
        self.radius = radius
    }

    func area() -> Double {
        return Double.pi * radius * radius
    }
}

Этот код следует принципу открытости/закрытости, так как новые фигуры могут быть добавлены без изменения существующего кода.

  • Принцип подстановки Барбары Лисков (Liskov Substitution Principle - LSP): Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы. Это означает, что подклассы должны быть взаимозаменяемыми с их суперклассами.

Пример на Swift:

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

class Duck: Bird {
    override func fly() {
        // Реализация полета утки
    }
}
  • Принцип разделения интерфейса (Interface Segregation Principle - ISP): Клиенты не должны зависеть от методов, которые они не используют. Это означает, что интерфейсы должны быть разделены на более мелкие и специфические интерфейсы, чтобы клиенты могли использовать только те методы, которые им нужны.

Пример на Swift:

protocol Printer {
    func printContent()
}

protocol Scanner {
    func scanContent()
}

protocol Fax {
    func faxContent()
}

class SimplePrinter: Printer {
    func printContent() {
        // Реализация метода печати
    }
}
  • Принцип инверсии зависимостей (Dependency Inversion Principle - DIP): Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций. Кроме того, абстракции не должны зависеть от деталей, но детали должны зависеть от абстракций.

Пример на Swift:

protocol Switchable {
    func turnOn()
    func turnOff()
}

class Light: Switchable {
    func turnOn() {
        // Включение света
    }

    func turnOff() {
        // Выключение света
    }
}

class Fan: Switchable {
    func turnOn() {
        // Включение вентилятора
    }

    func turnOff() {
        // Выключение вентилятора
    }
}

Это позволяет легко добавлять новые устройства без изменения кода в классе, который управляет ими.

Вот примеры нарушения принципов инверсии зависимостей (DIP) и разделения интерфейса (ISP):

Пример неверного подхода к DIP:

class Switch {
    private var light: Light

    init(light: Light) {
        self.light = light
    }

    func press() {
        if isLightOn() {
            light.turnOff()
        } else {
            light.turnOn()
        }
    }

    private func isLightOn() -> Bool {
        // Проверка состояния света
        return false
    }
}

В этом примере класс Switch зависит от конкретной реализации Light, что нарушает принцип инверсии зависимостей. Если в будущем потребуется добавить другие устройства, такие как вентиляторы или телевизоры, придется изменять класс Switch.

Пример неверного подхода к ISP:

protocol MultiFunctionDevice {
    func printContent()
    func scanContent()
    func faxContent()
}

class AllInOnePrinter: MultiFunctionDevice {
    func printContent() {
        // Реализация печати
    }

    func scanContent() {
        // Реализация сканирования
    }

    func faxContent() {
        // Реализация факса
    }
}

В этом примере интерфейс MultiFunctionDevice содержит методы для всех функций (печать, сканирование, факс), даже если клиентский код может использовать только одну или несколько из этих функций. Это нарушает принцип разделения интерфейса, так как клиенты могут зависеть от методов, которые им не нужны, и при изменении интерфейса придется изменять и клиентский код.