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

Есть ли паттерны которые нарушают SOLID?

2.3 Middle🔥 202 комментариев
#Архитектура и паттерны

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

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

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

Паттерны проектирования и SOLID: конфликты и компромиссы

Да, безусловно. Хотя многие паттерны проектирования были созданы для решения распространённых проблем и часто неявно следуют принципам SOLID, существуют ситуации, когда применение определённых паттернов или их реализация может вступать в конфликт с одним или несколькими принципами SOLID. Это не делает паттерны «плохими», но требует от разработчика понимания компромиссов и контекста использования.

Важно различать:

  1. Внутренний конфликт в самом паттерне.
  2. Некорректную реализацию паттерна, ведущую к нарушению SOLID.
  3. Ситуации, где сознательный компромисс в пользу паттерна оправдан.

Ключевые примеры паттернов с потенциальными нарушениями SOLID

1. Singleton и принцип единственной ответственности (SRP)

Классический Singleton часто нарушает SRP, потому что он решает две задачи:

  • Управление своим жизненным циклом и обеспечение единственности экземпляра.
  • Выполнение своей основной бизнес-логики.
// Классический Singleton, нарушающий SRP
class AppSettings {
    static let shared = AppSettings()
    private init() {}

    // Задача 1: Хранение настроек (основная ответственность)
    var theme: String = "Light"

    // Задача 2: Управление созданием (скрытая ответственность)
    // ... логика инициализации, потокобезопасность и т.д.
}

Более SOLID-подход — использовать внедрение зависимостей (DI), передавая единственный экземпляр как зависимость, а ответственность за жизненный цикл делегировать контейнеру DI.

2. Factory Method и принцип открытости/закрытости (OCP)

Паттерн Factory Method создан для расширяемости, но его базовая реализация может нарушать OCP, если для добавления нового продукта требуется модифицировать код фабрики.

protocol Product {
    func operation()
}

class Creator {
    // Factory Method
    func createProduct(type: Int) -> Product { // Нарушение OCP: при добавлении типа меняется метод
        switch type {
        case 1: return ConcreteProductA()
        case 2: return ConcreteProductB()
        default: fatalError("Unknown type") // Закрыто для расширения
        }
    }
}

Решение — использование реестра или конфигурации, которые можно изменять, не трогая код фабрики.

3. Decorator и принцип интерфейса (ISP)

Decorator должен реализовывать тот же интерфейс, что и декорируемый объект. Если этот интерфейс слишком «толстый» (имеет много методов), то конкретному декоратору, который добавляет функциональность только к одному методу, придётся реализовывать все остальные методы «пустышками» или делегированием, что является прямым нарушением ISP.

protocol DataService {
    func fetchData()
    func saveData()
    func deleteData()
    func generateReport() // Метод, который нужен не всем декораторам
}

class LoggingDecorator: DataService {
    private let wrapped: DataService

    init(_ service: DataService) { self.wrapped = service }

    func fetchData() {
        print("Fetch started")
        wrapped.fetchData()
        print("Fetch ended")
    }

    // Нарушение ISP: Декоратору логирования не нужны эти методы,
    // но он вынужден их реализовывать.
    func saveData() { wrapped.saveData() }
    func deleteData() { wrapped.deleteData() }
    func generateReport() { wrapped.generateReport() }
}

Исправление — разделение «толстого» интерфейса на более мелкие и специфичные.

4. Visitor и принцип открытости/закрытости (OCP)

Паттерн Visitor позволяет легко добавлять новые операции (открыт для расширения), но затрудняет добавление новых типов элементов в иерархию (закрыт для расширения по элементам). Для добавления нового класса ElementC придётся изменить интерфейс Visitor и все его конкретные реализации, что нарушает OCP.

protocol Visitor {
    func visit(element: ElementA) // При добавлении ElementC нужно менять
    func visit(element: ElementB) // этот интерфейс и всех посетителей.
}

Принцип инверсии зависимостей (DIP) и Абстрактная Фабрика / Фасад

Некорректное применение таких паттернов, как Abstract Factory или Facade, может привести к нарушению DIP, если высокоуровневые модули начинают зависеть от конкретных классов фабрик или фасадов, а не от их абстракций.

// Нарушение DIP: Высокоуровневый модуль зависит от конкретной фабрики
class Client {
    let factory: ConcreteFactory // Зависимость от конкретного класса!

    init() {
        self.factory = ConcreteFactory() // Прямое создание
    }
}

// Следование DIP
class ClientSOLID {
    let factory: AbstractFactoryProtocol // Зависимость от абстракции

    init(factory: AbstractFactoryProtocol) { // Внедрение зависимости
        self.factory = factory
    }
}

Вывод и рекомендации

  • Контекст решает. Нарушение SOLID не всегда является ошибкой. В небольшом, стабильном проекте или в рамках определённого слоя (например, в фабрике создания объектов в Composition Root) эти компромиссы могут быть допустимы.
  • Реализация важнее названия. Чаще нарушает SOLID не сам паттерн, а его неправильная, упрощённая реализация. SOLID — это ориентир для создания гибкой реализации паттерна.
  • Баланс и прагматизм. Проектирование — это поиск баланса между принципами (SOLID), практиками (паттернами) и конкретными требованиями проекта (производительность, скорость разработки, сложность).
  • Осознанный выбор. Ключевой навык senior-разработчика — понимать эти компромиссы и уметь аргументировать, почему в данном конкретном случае выбранный паттерн и его реализация, возможно, слегка отступают от «чистых» принципов, но являются оптимальным решением для задачи.

Таким образом, вопрос не в том, «какой паттерн плохой», а в том, как, когда и зачем мы его применяем. Идеального, универсального решения не существует, и SOLID служит прекрасной системой координат для оценки этих решений, а не жёстким набором догм.

Есть ли паттерны которые нарушают SOLID? | PrepBro