SOLID в Swift. Простое объяснение с примерами для начинающих
Принципы SOLID — это набор пяти основных принципов, которые помогают разработчикам создавать более понятный, гибкий и поддерживаемый код. Так же знание этих принципов довольно часто спрашивают на собеседованиях. Давайте рассмотрим каждый из этих принципов с примерами нарушения и соблюдения на языке Swift:
Принцип единственной ответственности (Single Responsibility Principle, SRP): Этот принцип гласит, что у каждого класса или структуры должна быть только одна задача. Например, если у вас есть класс для обработки данных из сети, его не следует использовать для отображения данных на экране.
Нарушение принципа SPR:
// NetworkManager только выполняет сетевые запросы
class NetworkManager {
func fetchData(url: URL) {
// Запрос к API
}
func updateUI() {
// обновляет пользовательский интерфейс
}
}
В этом примере класс NetworkManager
нарушает принцип SPR, потому что он и получает данные из сети, и обновляет UI.
Соблюдение принципа SPR:
// 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): Классы и функции должны быть открыты для расширения, но закрыты для изменений. Это означает, что вы должны быть в состоянии добавить новую функциональность без изменения существующего кода. Это можно сделать с помощью протоколов и расширений в Swift.
Нарушение принципа OCP:
class Animal {
let name: String
init(name: String) {
self.name = name
}
func makeSound() {
if name == "Dog" {
print("Woof")
} else if name == "Cat" {
print("Meow")
}
}
}
Здесь класс Animal
не закрыт для модификации, потому что если мы захотим добавить новое животное, нам придется изменить метод makeSound
.
Соблюдение принципа OCP:
protocol Animal {
func makeSound()
}
class Dog: Animal {
func makeSound() {
print("Woof")
}
}
class Cat: Animal {
func makeSound() {
print("Meow")
}
}
Теперь каждый класс подписанный под протокол Animal
может иметь свою собственную реализацию makeSound
, и протокол Animal
закрыт для модификаций.
Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP): Это означает, что если у вас есть класс B, который является подклассом класса A, вы должны иметь возможность использовать B везде, где используется A, без изменения поведения программы.
Нарушение принципа LSP:
class Bird {
func fly() {
// Реализация полета
}
}
class Penguin: Bird {
override func fly() {
fatalError("Penguins can't fly!")
}
}
let myBird: Bird = Penguin()
myBird.fly() // Приведет к ошибке во время выполнения
В этом примере Penguin
нарушает LSP, потому что он не может летать, в то время как класс Bird
предполагает, что все птицы могут летать.
Соблюдение принципа LSP:
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()
Теперь каждый подкласс Bird
может иметь свою собственную реализацию move
, не нарушая поведение базового класса.
Принцип разделения интерфейса (Interface Segregation Principle, ISP): Это означает, что классы не должны зависеть от методов, которые они не используют. В Swift это можно сделать с помощью протоколов.
Нарушение принципа ISP:
protocol Worker {
func work()
func eat()
}
class Robot: Worker {
func work() {
// работает
}
func eat() {
fatalError("Robots can't eat")
}
}
Здесь Robot
нарушает ISP, потому что он вынужден реализовать функцию eat
, которую он не может использовать.
Соблюдение принципа ISP:
protocol Worker {
func work()
}
protocol Eater {
func eat()
}
class Robot: Worker {
func work() {
// работает
}
}
Теперь Robot
реализует только функции, которые он действительно может использовать.
Принцип инверсии зависимостей (Dependency Inversion Principle, DIP): Это означает, что классы верхнего уровня не должны зависеть от классов нижнего уровня. Оба они должны зависеть от абстракций.
Нарушение принципа DIP:
class LightBulb {
func turnOn() {
// включает свет
}
}
class Switch {
let bulb: LightBulb
init(bulb: LightBulb) {
self.bulb = bulb
}
func toggle() {
bulb.turnOn()
}
}
Здесь класс Switch
напрямую зависит от конкретного класса LightBulb
, что нарушает DIP.
Соблюдение принципа DIP:
protocol Switchable {
func turnOn()
}
class LightBulb: Switchable {
func turnOn() {
// включает свет
}
}
class Switch {
let device: Switchable
init(device: Switchable) {
self.device = device
}
func toggle() {
device.turnOn()
}
}
Теперь класс Switch
зависит от абстракции, а не от конкретного класса, что соблюдает DIP.