← Назад к вопросам

Что такое SOLID?

1.0 Junior🔥 211 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

SOLID принципы в разработке

Определение

SOLID — это аббревиатура пяти принципов объектно-ориентированного проектирования, которые помогают разработчикам создавать более гибкий, масштабируемый и поддерживаемый код. Были популяризированы Робертом Мартином (Uncle Bob).

Принцип 1: Single Responsibility Principle (SRP)

Одна ответственность — один класс/функция должны иметь одну причину для изменения

// ❌ Плохо: UserManager делает слишком много
class UserManager {
    func registerUser(email: String) { }
    func saveToDatabase(user: User) { }
    func sendVerificationEmail(to: String) { }
    func generateReport() { }
    func validateEmail(email: String) { }
}

// ✅ Хорошо: разделили ответственность
class UserValidator {
    func validateEmail(_ email: String) -> Bool { }
}

class UserRepository {
    func save(_ user: User) { }
}

class EmailService {
    func sendVerificationEmail(to: String) { }
}

class UserRegistration {
    let validator: UserValidator
    let repository: UserRepository
    let emailService: EmailService
    
    func registerUser(email: String) {
        guard validator.validateEmail(email) else { return }
        let user = User(email: email)
        repository.save(user)
        emailService.sendVerificationEmail(to: email)
    }
}

Принцип 2: Open/Closed Principle (OCP)

Классы открыты для расширения, закрыты для модификации

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

// ❌ Плохо: добавлять новый тип платежа нужно менять класс
class PaymentProcessor {
    func processPayment(type: String, amount: Double) {
        switch type {
        case "card":
            processCardPayment(amount)
        case "paypal":
            processPayPalPayment(amount)
        case "apple_pay":  // Нужно добавить новый case
            processApplePayPayment(amount)
        }
    }
}

// ✅ Хорошо: используем протоколы для расширения
protocol PaymentMethod {
    func process(amount: Double) -> Bool
}

class CardPayment: PaymentMethod {
    func process(amount: Double) -> Bool { true }
}

class PayPalPayment: PaymentMethod {
    func process(amount: Double) -> Bool { true }
}

class ApplePayPayment: PaymentMethod {
    func process(amount: Double) -> Bool { true }
}

class PaymentProcessor {
    func processPayment(_ method: PaymentMethod, amount: Double) -> Bool {
        return method.process(amount: amount)
    }
}

Принцип 3: Liskov Substitution Principle (LSP)

Объекты подклассов могут заменять объекты базовых классов без нарушения работы программы

// ❌ Плохо: Bird не может летать, нарушает контракт
class Bird {
    func fly() -> String {
        return "Flying..."
    }
}

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

// Использование нарушает принцип
func makeBirdFly(_ bird: Bird) {
    print(bird.fly())  // Упадет для Penguin
}

// ✅ Хорошо: правильная иерархия
protocol Animal {
    func move() -> String
}

protocol FlyingBird: Animal { }

class Eagle: FlyingBird {
    func move() -> String { "Flying..." }
}

class Penguin: Animal {
    func move() -> String { "Swimming..." }
}

// Теперь все работает правильно
func makeAnimalMove(_ animal: Animal) {
    print(animal.move())  // Работает для всех
}

Принцип 4: Interface Segregation Principle (ISP)

Клиенты не должны зависеть от интерфейсов, которые они не используют

// ❌ Плохо: Worker должен реализовать все методы
protocol Worker {
    func work()
    func eat()
    func sleep()
}

class Robot: Worker {
    func work() { print("Working") }
    func eat() { /* Robot не ест */ }
    func sleep() { /* Robot не спит */ }
}

// ✅ Хорошо: разбили на маленькие интерфейсы
protocol Workable {
    func work()
}

protocol Eatable {
    func eat()
}

protocol Sleepable {
    func sleep()
}

class Human: Workable, Eatable, Sleepable {
    func work() { print("Working") }
    func eat() { print("Eating") }
    func sleep() { print("Sleeping") }
}

class Robot: Workable {
    func work() { print("Working") }
}

Принцип 5: Dependency Inversion Principle (DIP)

Зависимости должны быть от абстракций, а не от конкретных реализаций

// ❌ Плохо: высокоуровневый модуль зависит от низкоуровневого
class EmailNotification {
    func send(message: String) { }
}

class UserService {
    let notification = EmailNotification()  // Прямая зависимость
    
    func notifyUser(message: String) {
        notification.send(message: message)
    }
}

// ✅ Хорошо: используем абстракцию (протокол)
protocol NotificationService {
    func send(message: String)
}

class EmailNotification: NotificationService {
    func send(message: String) { }
}

class SMSNotification: NotificationService {
    func send(message: String) { }
}

class UserService {
    let notification: NotificationService
    
    init(notification: NotificationService) {
        self.notification = notification
    }
    
    func notifyUser(message: String) {
        notification.send(message: message)
    }
}

// Использование
let emailService = UserService(notification: EmailNotification())
let smsService = UserService(notification: SMSNotification())

SOLID в iOS разработке

Architecture Pattern Example: MVVM + DIP

// Абстракция для API
protocol APIClient {
    func fetchUsers() async throws -> [User]
}

// Конкретная реализация
class HTTPClient: APIClient {
    func fetchUsers() async throws -> [User] { }
}

// Mock для тестов
class MockAPIClient: APIClient {
    func fetchUsers() async throws -> [User] {
        return [User(id: 1, name: "Test")]
    }
}

// ViewModel зависит от абстракции
class UsersViewModel {
    let apiClient: APIClient
    @Published var users: [User] = []
    
    init(apiClient: APIClient) {
        self.apiClient = apiClient
    }
    
    func loadUsers() async {
        do {
            users = try await apiClient.fetchUsers()
        } catch {
            print("Error: \(error)")
        }
    }
}

// Тестирование легко
let mockClient = MockAPIClient()
let viewModel = UsersViewModel(apiClient: mockClient)

Практические выводы

Преимущества SOLID:

  • Гибкость — легко добавлять новые функции
  • Тестируемость — проще писать unit-тесты
  • Переиспользуемость — компоненты могут использоваться в разных местах
  • Maintainability — код легче понимать и изменять
  • Уменьшение связанности — компоненты независимы друг от друга

Когда применять:

  • В крупных проектах с множеством компонентов
  • Когда требования могут измениться
  • Когда код пишет команда из разных разработчиков

Когда НЕ применять:

  • В маленьких скриптах и one-off решениях
  • Когда требует слишком много абстракций (YAGNI)
  • Если усложняет код без реальной пользы

Помни о KISS

СОЛИД полезен, но не переусложняй! Баланс между простотой и архитектурой — ключ к хорошему коду.