Slovník pojmů
Dědičnost v programování
Dědičnost umožňuje jednomu objektovému typu převzít chování a kontrakt jiného. V dobrém návrhu může potomek nahradit rodiče bez porušení očekávání volajícího kódu.
Stručná definice
Rodič, potomek a zachovaný kontrakt.
Potomkovská třída dědí dostupné chování rodiče a může metodu přepsat. protected zpřístupní člen rodiči a potomkům, ne libovolnému okolnímu kódu. Abstraktní třída může určit společný základ, aniž by šla sama přímo vytvořit.
Dědičnost je součást objektově orientovaného programování, ale není výchozí odpovědí na znovupoužití kódu. Rozhraní a kompozice často vyjádří variabilitu s menší vazbou.
Jaký problém řeší
Vyjadřuje vztah „je typem“ se společným významem.
Dědičnost dává smysl, když potomci skutečně sdílejí kontrakt rodiče a volající je může bezpečně zaměnit.
- společný základ pro několik kompatibilních typů
- přepsání konkrétního detailu při zachování výsledku
- abstraktní třída pro sdílený invariant
- návrh, v němž je substituce potomka za rodiče ověřitelná
Praktický příklad
Exporter se společným základem
Ukázka používá abstraktní základ a protected pomocnou metodu. CSV exportér však není důvod k hluboké hierarchii: pokud třídy sdílejí jen jednu operaci export(), může být menší rozhraní a kompozice přehlednější.
Přepsání metody není totéž co běžné method overloading podle signatur. PHP takové přetěžování metod jako některé jazyky nepodporuje. Důležitější než syntaktické extends je zachování kontraktu a princip substituce známý také ze SOLID.
PHP
abstract class Exporter
{
abstract public function export(array $rows): string;
protected function escape(string $value): string
{
return str_replace('"', '""', $value);
}
}
final class CsvExporter extends Exporter
{
public function export(array $rows): string
{
return implode("\n", array_map(
fn (array $row) => '"' . $this->escape($row['number']) . '"',
$rows,
));
}
}
Jak funguje
Společný kontrakt a konkrétní export
Diagram ukazuje jednu možnost návrhu, nikoli doporučený výchozí vzor pro každé sdílení kódu.
- Rodičovský typ Exporter určuje operaci export a společnou pomocnou logiku.
- Potomkovský typ CsvExporter poskytne konkrétní implementaci exportu.
- Volající Pracuje s očekávaným kontraktem, ne s interním detailem CSV.
- Substituce Jiný kompatibilní exportér musí zachovat význam metody.
Hlavní části a varianty
Znovupoužití kódu není samo o sobě vztah typu.
Dědičnost přináší silnější vazbu než pouhé sdílení pomocné funkce.
Rodič a potomek
Potomek přebírá dostupné členy rodiče a rozšiřuje nebo přepisuje chování.
Přepsání
Metoda může mít jinou implementaci, ale nesmí porušit očekávaný vstup, výstup a invariant.
Rozhraní
Implementace interface vyjadřuje kontrakt bez sdílení implementace; není to dědičnost třídy.
Kompozice
Objekt spolupracuje s jiným objektem místo toho, aby se stal jeho potomkem; často je flexibilnější.
Omezení a časté chyby
Hluboká hierarchie se mění obtížně.
Kde může pomoci
- skutečný společný kontrakt rodiče a potomka
- malá a stabilní společná implementace
- testovatelná substituce jednoho typu jiným
Rizika
- dědičnost použitá jen kvůli znovupoužití
- hluboké hierarchie s nejasným chováním
- potomek zpřísní vstup nebo zruší slíbený výsledek
- zaměnění implements za extends
Praktické použití
Začněte otázkou, zda je potomek skutečně rodičem.
Pokud typy sdílejí jen technickou implementaci, kompozice bývá často bezpečnější. Dovolí přidat spolupracovníka nebo strategii bez nutnosti měnit hierarchii a bez zděděných detailů, které potomek nechce.
Dědičnost má zůstat mělká a její kontrakt se musí testovat. Potomek nesmí vyžadovat více, nabízet méně ani měnit invariant, na kterém stojí volající rodičovský typ.
Dědičnost může podpořit polymorfismus, ale není jeho podmínkou: nezávislé třídy mohou implementovat stejné rozhraní bez společného rodiče. extends vytváří vztah s rodičovskou třídou, zatímco implements slibuje kontrakt. Tento rozdíl je důležitější než znovupoužití několika řádků kódu.Při návrhu exportérů je užitečné se nejdřív ptát, jaký výsledek a jaké chyby může volající očekávat. Teprve stabilní společný kontrakt ospravedlňuje abstraktní rodičovskou třídu. Jestliže se varianty liší hlavně spolupracovníkem nebo formátem, předání strategie přes kompozici obvykle umožní změnu s menším rizikem než další úroveň dědičnosti.
Kontrakt rodiče zahrnuje více než signaturu metody. Zpřísnění povoleného vstupu, odstranění dříve slíbeného výsledku nebo změna podstatného vedlejšího účinku může rozbít volající kód, i když PHP stále přijme extends. Proto se substituce ověřuje konkrétními scénáři, ne jen tím, že se potomek podařilo vytvořit.
Praktickým testem dědičnosti je změna očekávání rodiče. Pokud by nová vlastnost rodičovské třídy vyžadovala úpravu mnoha potomků nebo by některý potomek musel její použití zakázat, hierarchie pravděpodobně sdružuje nesouvisející varianty. Mělčí model se samostatnými spolupracovníky může být snazší rozšířit a lépe ukazuje, která část chování je opravdu společná.
Kontrola změny
Ověřte substituci, ne jen úspěšné extends.
Hlavní otázka zní: zůstane program správný, když rodičovský typ nahradím potomkem?
- popsat očekávaný kontrakt rodiče
- testovat alternativní potomky stejným scénářem
- držet hierarchii mělkou
- zvážit kompozici před novým extends
Časté otázky
Dědičnost bez dogmatu
Kdy dědičnost použít?
Když potomek opravdu je typem rodiče a dokáže dodržet jeho kontrakt.
Jak se liší od interface?
Dědičnost přebírá vztah a případně implementaci třídy; interface slibuje kontrakt bez společné implementace.
Proč dát přednost kompozici?
Často vytváří menší vazbu a dovolí chování skládat či měnit bez hierarchie.
Co znamená protected?
Člen je dostupný uvnitř třídy a jejím potomkům, ne veřejnému okolí.
Osobní zkušenost
Vztahy typů musí chránit kontrakt, ne jen šetřit řádky.
V architektonickém návrhu volím dědičnost střídmě a dávám přednost kompozici tam, kde lépe odpovídá změně.