Slovník pojmů
SOLID
SOLID dává užitečné otázky pro návrh změn. Neříká ale, že více tříd, interface a vrstev automaticky znamená kvalitnější software.
Stručná definice
Pět vodítek, ne pět rigidních zákonů.
Každé písmeno SOLID popisuje jiný druh návrhového problému: příliš mnoho důvodů ke změně, rozšiřování bez rizikových zásahů, porušený kontrakt potomka, příliš široké rozhraní a závislost business pravidel na detailech. Principy dávají společný jazyk pro code review a refaktoring.
Největší hodnotu mají při konkrétní změně: přidání dopravce, jiné platby nebo nového vstupu do aplikace. Pokud jednoduchý kód nemá rozumnou budoucí variantu ani skutečnou hranici, KISS a přímé řešení mohou být lepší než abstrakce předem.
Jaký problém řeší
Změna bez skrytých vedlejších dopadů
SOLID neposuzuje krásu diagramu. Pomáhá najít místa, kde změna jedné věci nečekaně rozbije jinou nebo kde třída skrývá příliš mnoho rozhodnutí.
- objekt, který současně počítá cenu, ukládá objednávku a posílá e-mail
- dlouhý řetězec if nebo switch, do něhož se při každé variantě sahá na více míst
- potomek, který nedokáže bezpečně splnit očekávání základního typu
- klient, jenž musí implementovat či znát metody, které nepotřebuje
- aplikační pravidlo pevně svázané s HTTP klientem, frameworkem nebo konkrétní databází
Praktický příklad
Zahájení platby v checkoutu
Checkout dostane implementaci PaymentInitiator jako závislost. Výběr konkrétní platební metody je smysluplnou proměnlivostí; kontrakt proto není vytvořený jen kvůli názvu principu. Každá implementace vrací PaymentStart se stejným významem, takže checkout nemusí znát detaily API poskytovatele.
Pokud by platební metoda neuměla vrátit očekávaný stav a nutila checkout dělat speciální větev pro interní detail, kontrakt by nebyl dobře navržený. Lepší je upravit význam kontraktu nebo oddělit skutečně rozdílný use case než maskovat rozdíl dědičností.
PHP
interface PaymentInitiator
{
public function start(Order $order): PaymentStart;
}
final class Checkout
{
public function __construct(private PaymentInitiator $payment) {}
public function beginPayment(Order $order): PaymentStart
{
return $this->payment->start($order);
}
}
Pět principů
Každý princip odpovídá na jinou otázku
Principy se překrývají jen částečně. Jeden problém nelze vyřešit slepým použitím zkratky SOLID.
- S — Single Responsibility Má tato část kódu jednu soudržnou odpovědnost a jeden hlavní důvod ke změně?
- O — Open/Closed Lze novou variantu přidat přes existující stabilní kontrakt, aniž se bezdůvodně rozbije osvědčené chování?
- L — Liskov Substitution Zůstane chování správné, když je implementace zaměněna jinou implementací stejného kontraktu?
- I — Interface Segregation Je rozhraní malé podle potřeb konzumenta, nebo ho nutí záviset na cizích schopnostech?
- D — Dependency Inversion Závisí vysoká business pravidla na stabilním kontraktu místo na konkrétním technickém detailu?
Hlavní principy
Význam, příklad a častý omyl u každého písmena
Příklady popisují záměr. V konkrétním projektu může být nejmenší rozumná hranice větší nebo menší.
SRP — Single Responsibility Principle
Třída má mít jednu soudržnou odpovědnost, tedy jeden hlavní typ důvodu ke změně. OrderConfirmation může koordinovat potvrzení objednávky, zatímco tvorba PDF faktury patří jinam. Neznamená to „jedna metoda na třídu“ ani rozdělování každého řádku do vlastního objektu.
OCP — Open/Closed Principle
Kód má být otevřený rozšíření, ale uzavřený vůči zbytečnému narušení stabilního chování. Nový dopravce lze přidat implementací smysluplného kontraktu místo úprav větvení všude. Neznamená to, že existující kód se nikdy nesmí změnit; oprava špatné abstrakce je často správná.
LSP — Liskov Substitution Principle
Implementace kontraktu musí zachovat očekávání klienta: nepřidat přísnější předpoklady, nezrušit slíbený výsledek a neporušit invariant. Pokud metoda DeliveryPriceQuote slibuje cenu pro danou adresu, náhrada nemá náhodně vyžadovat interní ID skladu. Dědičnost sama o sobě LSP nezaručuje.
ISP — Interface Segregation Principle
Konzument nemá být nucen záviset na metodách, které nepotřebuje. Čtečka výdejních míst může vyžadovat jen findPoints(), ne celý administrátorský kontrakt s mazáním a synchronizací. ISP nevyžaduje interface s jedinou metodou úplně všude; rozdělení musí odpovídat rozdílným klientům.
DIP — Dependency Inversion Principle
Vysoká pravidla nemají záviset na nízkoúrovňových detailech; obě strany mají záviset na abstrakci. Use case může znát kontrakt pro uložení objednávky, zatímco SQL nebo ORM implementuje tento kontrakt. DIP není dependency injection: DI je běžný způsob, jak takovou závislost předat.
Praktický návrh
Platební metody bez předčasné abstrakce
Přidání platby kartou a převodem ukazuje, kdy principy mohou pomoci. Nevyžaduje ale vytvořit framework pro tři jednoduché řádky.
- Odpovědnost
- Checkout rozhodne, zda lze objednávku zahájit. Konkrétní platební brána vytvoří svůj požadavek; nemá zároveň rozhodovat o expedici objednávky.
- Kontrakt
- PaymentInitiator vyjadřuje pouze schopnost zahájit platbu. Každá implementace vrátí výsledek, s nímž checkout umí pracovat.
- Zaměnitelnost
- Kartová brána i bankovní převod musí zachovat význam výsledku, například URL pro pokračování nebo informaci o dokončené platbě.
- Složení
- Konkrétní implementace se vybere na okraji aplikace podle zvolené metody. Checkout nemusí vytvářet HTTP klienta ani znát API každého poskytovatele.
Výhody a omezení
Dobrý návrh je čitelný i přiměřený
Přínosy
- jasnější umístění odpovědnosti a menší riziko vedlejších změn
- kontrakty, které lze cíleně testovat nebo nahradit
- lepší podpora proměnlivých částí, jako jsou dopravci a platební brány
- konkrétní otázky pro review místo neurčitého „udělej to čistěji“
Omezení a časté chyby
- interface pro každou třídu bez druhého klienta nebo reálného kontraktu
- předčasná strategie pro variantu, která zatím neexistuje
- dědičnost použitá jen pro sdílení kódu, i když význam kontraktu nesedí
- použití SOLID jako argumentu proti jednoduchému a srozumitelnému řešení
Pragmatičnost
Abstrakce má následovat proměnlivost a riziko, ne poučku.
Pokud projekt má právě jednu jednoduchou implementaci a žádný konkrétní důvod k výměně, přímá třída může být čitelnější než sada interface, factory a adapterů. Signálem pro změnu není počet řádků, ale opakované podmínky, různá očekávání klientů, obtížné testování nebo historie změn na stejném místě.
SOLID nevyžaduje konkrétní počet vrstev ani balíčků. Principy fungují jako hypotézy: pokud nová hranice zjednoduší změnu a zachová srozumitelnost, je užitečná. Pokud jen rozmnoží názvy a přesměrování, je rozumné ji odstranit.
Na co myslet
Při změně ověřit skutečný kontrakt a cenu abstrakce
Zdravé použití principů stojí na pozorovatelném chování, ne na počtu vytvořených souborů.
- pojmenovat, kdo a proč bude odpovědnost měnit
- testovat veřejné chování kontraktu i s alternativní implementací
- nezpřísnit požadavky náhrady oproti deklarovanému kontraktu
- rozdělit rozhraní podle potřeb jeho konzumentů, ne mechanicky podle metod
- držet technické detaily na hranici aplikačního pravidla
- při každé abstrakci umět vysvětlit, jakou dnešní nebo očekávanou změnu zjednodušuje
Časté otázky
SOLID bez zbytečných dogmat
Musí mít každá třída jen jednu metodu kvůli SRP?
Ne. SRP mluví o soudržné odpovědnosti a důvodu ke změně, ne o počtu metod. Třída může mít několik metod, pokud společně vyjadřují jeden srozumitelný celek.
Znamená Open/Closed, že se nesmí měnit existující kód?
Ne. Princip varuje před zbytečným narušováním stabilního chování při přidání varianty. Chybný nebo příliš úzký kód je často potřeba upravit; není účelné jej obcházet další vrstvou.
Je Dependency Inversion totéž co Dependency Injection?
Ne. DIP je princip směru závislostí k abstrakci. Dependency injection je technika předání objektu, která může DIP podpořit, ale lze ji použít i bez vhodné architektonické hranice.
Kdy SOLID nepoužívat?
Nejde o zapínanou technologii. U malého a stabilního kódu může být nejrozumnější nepřidávat žádnou novou abstrakci. Principy použijte jako otázky tehdy, když existuje konkrétní změna nebo problém se závislostmi.
Jak přistupuji k architektuře
Kód má chránit změnitelná pravidla a zůstat čitelný pro tým.
Při návrhu rozlišuji stabilní pravidla od technických detailů a hledám jen takové hranice, které se vyplatí vzhledem ke složitosti aplikace.