(1)(1)(1) Single Responsibility Principle (SRP) — каждый модуль/класс должен иметь одну причину для изменения (одну ответственность). - Нарушение (реальный пример): класс UserController в веб-приложении одновременно обрабатывает HTTP-запросы, валидирует данные, формирует SQL-запросы и логирует операции. - Последствия: изменения в формате валидации или в СУБД ведут к правкам в одном большом классе; рост ошибок, трудности с тестированием (много интеграционных тестов вместо unit‑тестов), низкая читаемость. - Исправление: выделить обязанности в отдельные классы/слои — Presentation/Controller, Validator, Repository, Logger; применить dependency injection; написать unit‑тесты для каждого слоя. (2)(2)(2) Open/Closed Principle (OCP) — программные сущности открыты для расширения, но закрыты для модификации. - Нарушение (реальный пример): PaymentProcessor с большим switch/case по типу платежа (Card, PayPal, Crypto). При добавлении нового метода нужно менять существующий класс. - Последствия: частые изменения в формате switch приводят к регрессиям, тесты ломаются, сложно добавлять плагины/расширения без правки исходного класса. - Исправление: ввести абстракцию IPaymentMethod и стратегию/обработчики (CardPaymentHandler, PayPalHandler...), регистрировать новые обработчики через фабрику/реестр; существующий код использует интерфейс и не меняется при добавлении нового метода. (3)(3)(3) Liskov Substitution Principle (LSP) — объекты подклассов должны быть взаимозаменяемы с объектами базового типа без нарушения корректности программы. - Нарушение (реальный пример): иерархия Rectangle <- Square, где Square переопределяет setWidth/setHeight так, что меняет семантику (например, вызывает исключение при попытке установить разные стороны). Код, ожидающий поведение Rectangle, ломается. - Последствия: полиморфный код получает неожиданные исключения/некорректные результаты; сложно предсказать контракт методов; баги в коллекциях/алгоритмах, работающих с базовым типом. - Исправление: пересмотреть модель — не наследовать Square от Rectangle; ввести интерфейс IShape с методами, не предполагающими независимую установку обеих сторон; использовать композицию (Square содержит Rectangle) либо отдельные классы с общим интерфейсом, соблюдающим контракт. (4)(4)(4) Interface Segregation Principle (ISP) — клиенты не должны зависеть от методов, которые они не используют; лучше много маленьких специфичных интерфейсов, чем один большой. - Нарушение (реальный пример): интерфейс IMultiFunctionDevice с методами Print(), Scan(), Fax(). Класс SimplePrinter вынужден реализовать Fax() и Scan() (пустые реализации или бросает NotImplemented). - Последствия: ненужные зависимости, «мусорные» реализации, трудности с тестированием и сопровождением, нарушается понятие контракта. - Исправление: разбить интерфейс на IPrinter, IScanner, IFax; клиенты зависят только от нужного интерфейса; при необходимости использовать фасад или комбинированные реализации. (5)(5)(5) Dependency Inversion Principle (DIP) — модули высокого уровня не должны зависеть от модулей низкого уровня; оба должны зависеть от абстракций; абстракции не должны зависеть от деталей. - Нарушение (реальный пример): сервис OrderService внутри себя делает new SqlOrderRepository() и new EmailNotifier(), жестко привязываясь к конкретным реализациям. - Последствия: трудно заменить репозиторий (например, на NoSQL), невозможно мокать зависимости в тестах, код тесно сцеплен, развертывание/конфигурация усложняется. - Исправление: ввести интерфейсы IOrderRepository, INotifier; внедрять их через конструктор (constructor injection) или через DI‑контейнер; в тестах подставлять mock/stub; конфигурировать конкретные реализации в точке компоновки приложения. Краткое практическое правило: выделяйте обязанности, программируйте через интерфейсы, соблюдайте контракт поведения, разделяйте интерфейсы и инвертируйте зависимости — эти меры предотвращают указанные нарушения и облегчают сопровождение и тестирование.
- Нарушение (реальный пример): класс UserController в веб-приложении одновременно обрабатывает HTTP-запросы, валидирует данные, формирует SQL-запросы и логирует операции.
- Последствия: изменения в формате валидации или в СУБД ведут к правкам в одном большом классе; рост ошибок, трудности с тестированием (много интеграционных тестов вместо unit‑тестов), низкая читаемость.
- Исправление: выделить обязанности в отдельные классы/слои — Presentation/Controller, Validator, Repository, Logger; применить dependency injection; написать unit‑тесты для каждого слоя.
(2)(2)(2) Open/Closed Principle (OCP) — программные сущности открыты для расширения, но закрыты для модификации.
- Нарушение (реальный пример): PaymentProcessor с большим switch/case по типу платежа (Card, PayPal, Crypto). При добавлении нового метода нужно менять существующий класс.
- Последствия: частые изменения в формате switch приводят к регрессиям, тесты ломаются, сложно добавлять плагины/расширения без правки исходного класса.
- Исправление: ввести абстракцию IPaymentMethod и стратегию/обработчики (CardPaymentHandler, PayPalHandler...), регистрировать новые обработчики через фабрику/реестр; существующий код использует интерфейс и не меняется при добавлении нового метода.
(3)(3)(3) Liskov Substitution Principle (LSP) — объекты подклассов должны быть взаимозаменяемы с объектами базового типа без нарушения корректности программы.
- Нарушение (реальный пример): иерархия Rectangle <- Square, где Square переопределяет setWidth/setHeight так, что меняет семантику (например, вызывает исключение при попытке установить разные стороны). Код, ожидающий поведение Rectangle, ломается.
- Последствия: полиморфный код получает неожиданные исключения/некорректные результаты; сложно предсказать контракт методов; баги в коллекциях/алгоритмах, работающих с базовым типом.
- Исправление: пересмотреть модель — не наследовать Square от Rectangle; ввести интерфейс IShape с методами, не предполагающими независимую установку обеих сторон; использовать композицию (Square содержит Rectangle) либо отдельные классы с общим интерфейсом, соблюдающим контракт.
(4)(4)(4) Interface Segregation Principle (ISP) — клиенты не должны зависеть от методов, которые они не используют; лучше много маленьких специфичных интерфейсов, чем один большой.
- Нарушение (реальный пример): интерфейс IMultiFunctionDevice с методами Print(), Scan(), Fax(). Класс SimplePrinter вынужден реализовать Fax() и Scan() (пустые реализации или бросает NotImplemented).
- Последствия: ненужные зависимости, «мусорные» реализации, трудности с тестированием и сопровождением, нарушается понятие контракта.
- Исправление: разбить интерфейс на IPrinter, IScanner, IFax; клиенты зависят только от нужного интерфейса; при необходимости использовать фасад или комбинированные реализации.
(5)(5)(5) Dependency Inversion Principle (DIP) — модули высокого уровня не должны зависеть от модулей низкого уровня; оба должны зависеть от абстракций; абстракции не должны зависеть от деталей.
- Нарушение (реальный пример): сервис OrderService внутри себя делает new SqlOrderRepository() и new EmailNotifier(), жестко привязываясь к конкретным реализациям.
- Последствия: трудно заменить репозиторий (например, на NoSQL), невозможно мокать зависимости в тестах, код тесно сцеплен, развертывание/конфигурация усложняется.
- Исправление: ввести интерфейсы IOrderRepository, INotifier; внедрять их через конструктор (constructor injection) или через DI‑контейнер; в тестах подставлять mock/stub; конфигурировать конкретные реализации в точке компоновки приложения.
Краткое практическое правило: выделяйте обязанности, программируйте через интерфейсы, соблюдайте контракт поведения, разделяйте интерфейсы и инвертируйте зависимости — эти меры предотвращают указанные нарушения и облегчают сопровождение и тестирование.