<? phpukraine СПІВБЕСІДИ
Пошук по платформі
АРХІТЕКТУРА · MIDDLE ЧАСТО ПИТАЮТЬ

Що означає SOLID на практиці, а не в підручнику?

На практиці найчастіше працюють два принципи: єдина відповідальність тримає класи малими й зрозумілими, інверсія залежностей робить код тестованим; решта є наслідками, і сліпе дотримання всіх пʼяти породжує зайві абстракції.

Наведіть приклад порушення LSP у реальному коді.
Коли DIP заважає, а не допомагає?
Який із принципів SOLID ви порушуєте свідомо?
SOLID дизайн принципи

SOLID — це не чекліст, а набір спостережень про те, де код починає опиратись змінам. На практиці найбільше працюють два з пʼяти. Єдина відповідальність: клас має одну причину для зміни. Не один метод, а один привід, з яким до нього приходять. Симптоми порушення видно без теорії: конструктор на десять залежностей, тести з величезним setup, клас, який правлять і бухгалтерія, і маркетинг. Інверсія залежностей: код залежить від абстракції там, де реалізація може змінитись або її треба підміняти в тестах, тобто на межах із базою, зовнішніми API, часом, файлами.

Решта принципів здебільшого наслідки. Відкритість до розширення означає точки розширення там, де варіанти справді додаються: способи оплати, формати експорту. Підстановка Лісков — це про контракти: підклас, який кидає виняток у методі батька або мовчки змінює його семантику, ламає весь код, написаний під батька. Розділення інтерфейсів — про те, щоб споживач не залежав від методів, які не використовує, і саме воно зазвичай виправляє порушення LSP.

Формальне дотримання всіх пʼяти без потреби дає інтерфейс на кожен клас з однією реалізацією, десятки файлів на один сценарій і код, у якому важко знайти, де щось відбувається. Для CRUD-адмінки це шкідливо. Принципи застосовують там, де є біль: клас, який страшно міняти, тест, який неможливо написати, зміна, яка тягне правки в десяти місцях.

// До: одна причина для зміни? Ні: платежі, лист, PDF, склад в одному класі
final class OrderService
{
    public function checkout(Order $order): void
    {
        $charge = $this->stripe->charge($order->total(), $order->card());   // зовнішній API
        $this->mailer->send(new ReceiptMail($order, $charge));            // формат листа
        file_put_contents("/invoices/{$order->id}.pdf", $this->pdf($order)); // інфраструктура
        $this->db->table('stock')->decrement('qty', $order->qty());          // складський облік
    }
}

// Після: SRP + DIP там, де є межа з зовнішнім світом. Усередині домену конкретні класи.
interface PaymentGateway { public function charge(Money $amount, CardToken $card): Charge; }

final class Checkout
{
    public function __construct(
        private PaymentGateway $payments,        // абстракція: буде FakeGateway у тестах
        private EventDispatcher $events,
    ) {}

    public function handle(Order $order): void
    {
        $charge = $this->payments->charge($order->total(), $order->card());
        $order->markPaid($charge->id());
        $this->events->dispatch(new OrderPaid($order->id)); // лист, PDF, склад — окремі слухачі
    }
}

// LSP порушено: підклас звужує контракт батька
class ReadOnlyOrders extends Orders
{
    public function save(Order $o): void { throw new LogicException('read only'); }
}
// Правильно: окремі інтерфейси OrdersReader і OrdersWriter (ISP), без наслідування
Не розшифровку абревіатури, а приклад: клас, який ви розділили, і що це дало в тестах чи при зміні вимог.
Що SRP це про одну причину для зміни, а не про один метод на клас, і що симптом порушення це клас, який редагують з різних приводів різні люди.
Що OCP на практиці означає точки розширення там, де зміни очікувані, наприклад стратегії оплати, а не інтерфейс на кожен клас.
Реальний приклад LSP: підклас кидає виняток у методі батька або звужує контракт, і код, який працює з батьком, ламається.
Що DIP це залежність від абстракції там, де реалізація може змінитись або її треба підміняти в тестах, а не інтерфейс для єдиної реалізації назавжди.
Переказувати визначення пʼяти принципів без жодного прикладу з власного коду.
Створювати інтерфейс для кожного класу з однією реалізацією й називати це DIP.
Трактувати SRP як один метод на клас і розсипати логіку по десятках класів без цілісності.
Не помічати порушення LSP у власному коді: ReadOnlyRepository extends Repository з save(), який кидає виняток.
Застосовувати принципи до CRUD-коду, де жодної складності немає, і роздувати проєкт шарами.
ПОРАДА

Не переказуйте абревіатуру. Наведіть приклад класу, який ви розділили, і що це дало в тестах. Скажіть, який принцип порушуєте свідомо і чому.

оновлено 3 вересня 2026 · ліцензія CC-BY-SA-4.0 Знайшли неточність? Напишіть →
ПЕРЕВІРТЕ СЕБЕ

Принципи цінні тим, що показують, де код опирається змінам; сліпе застосування породжує зайві абстракції.