it-swarm-eu.dev

Kód Smell: Zneužívání dědičnosti

Obecně je v komunitě OO) všeobecně přijímáno, že člověk by měl „upřednostňovat kompozici před dědičností“. Na druhé straně dědičnost poskytuje jak polymorfismus, tak přímý a těsný způsob, jak vše přenést na základní třídu pokud není výslovně přepsáno, a proto je velmi pohodlné a užitečné. Delegace může být často (i když ne vždy) podrobná a křehká.

Nejzřetelnějším a nejjistějším znakem zneužití dědictví IMHO je porušení princip substituce Liskova . Jaké jsou další známky toho, že dědičnost je nesprávným nástrojem pro práci, i když se to zdá vhodné?

52
dsimcha

Když zdědíte jen proto, abyste získali funkčnost, pravděpodobně zneužíváte dědičnost. To vede k vytvoření Boží objekt .

Dědičnost sama o sobě není problém, pokud vidíte skutečný vztah mezi třídami (jako jsou klasické příklady, jako je Pes rozšiřuje zvíře) a do rodičovské třídy nevkládáte metody, které na některých z nich nedávají smysl děti (například přidání metody Bark () do třídy Animal by nedávalo smysl ve třídě Fish, která rozšiřuje Animal).

Pokud třída potřebuje funkčnost, použijte kompozici (snad injektování poskytovatele funkčnosti do konstruktoru?). Pokud třída potřebuje BÝT jako jiná, použijte dědičnost.

48
Fernando

Řekl bych, že s ohledem na riziko, že bude sestřelen, je dědičnost sama o sobě vůní kódu :)

Problém s dědičností je v tom, že může být použit pro dva ortogonální účely:

  • rozhraní (pro polymorfismus)
  • implementace (pro opakované použití kódu)

Mít jediný mechanismus, jak získat oba, je v první řadě tím, co vede k „zneužívání dědičnosti“, protože většina lidí očekává, že dědičnost bude o rozhraní, ale lze ji použít pro výchozí implementaci i jinak pečlivě programátory (je to tak snadné přehlédnout to ...)

Ve skutečnosti moderní jazyky jako Haskell nebo Go opustily dědictví, aby oddělily obě obavy.

Zpátky na trať:

Porušení liskovského principu je proto nejjistějším znakem, protože to znamená, že část smlouvy „rozhraní“ není respektována.

I když je rozhraní respektováno, můžete mít objekty zděděné z „tlustých“ základních tříd jen proto, že jedna z metod byla považována za užitečnou.

Liskovův princip sám o sobě tedy nestačí, potřebujete vědět, zda je polymorfismus použitý. Pokud tomu tak není, pak na prvním místě nebylo mnoho věcí zdědit, to je zneužití.

Souhrn:

  • prosadit Liskov Principle
  • zkontrolujte, zda se skutečně používá

Cesta kolem:

Jasné oddělení obav:

  • dědí pouze z rozhraní
  • použít delegování k delegování implementace

znamená, že pro každou metodu je třeba alespoň pár úhozů a najednou lidé začnou přemýšlet o tom, zda je tak skvělý nápad znovu použít tuto „tukovou“ třídu jen pro její malou část.

39
Matthieu M.

Je to skutečně „obecně přijímané“? Je to nápad, který jsem několikrát slyšel, ale je to stěží všeobecně uznávaný princip. Jak jste právě zdůraznili, dědičnost a polymorfismus jsou znaky OOP. Bez nich máte právě procedurální programování s praštěnou syntaxí.

Složení je nástroj pro vytváření objektů určitým způsobem. Dědičnost je nástroj pro vytváření objektů jiným způsobem. S žádným z nich není nic špatného; stačí vědět, jak oba fungují a kdy je vhodné použít každý styl.

14
Mason Wheeler

Síla dědičnosti je opakovaná použitelnost. Rozhraní, data, chování, spolupráce. Je to užitečný vzor, ​​ze kterého lze předávat atributy novým třídám.

Nevýhodou použití dědičnosti je opakovaná použitelnost. Rozhraní, data a chování jsou zapouzdřeny a hromadně přenášeny, takže se jedná o nabídku typu „vše nebo nic“. Problémy se zjevně projevují, jak se hierarchie prohlubuje, protože vlastnosti, které by mohly být užitečné v abstraktní, se zmenšují a začnou zakrývat vlastnosti na místní úrovni.

Každá třída, která používá objekt kompozice, bude muset více či méně znovu implementovat stejný kód, aby s ním mohla pracovat. I když tato duplikace vyžaduje určité porušení DRY, poskytuje návrhářům větší svobodu při výběru těch vlastností třídy, které jsou pro jejich doménu nejvýznamnější. To je zvláště výhodné, když se třídy specializují.

Obecně by mělo být upřednostňováno vytvářet třídy prostřednictvím kompozice a poté převádět na dědičnost těchto tříd s většinou jejich vlastností společných, přičemž rozdíly zůstávají jako kompozice.

IOW, YAGNI (nepotřebuješ dědictví).

5
Huperniketes

Pokud třídíte A na třídu B, ale nikoho nezajímá, pokud B zdědí A nebo ne, takže to je známka toho, že byste to mohli dělat špatně .

3
tia

„Upřednostňovaná kompozice před dědičností“ je nejhlubší tvrzení. Oba jsou nezbytnými prvky OOP. Je to jako říkat „Oblíbená kladiva nad pily“.

Dědičnost může být samozřejmě zneužita, může být zneužit jakýkoli jazykový znak, mohou být zneužity podmíněné příkazy.

2
Chuck Stephanski

Snad nejzřetelnější je, když třída zdědění končí s hromadou metod/vlastností, které se v exponovaném rozhraní nikdy nepoužívají. V nejhorších případech, kdy základní třída obsahuje abstraktní členy, najdete prázdné (nebo bezvýznamné) implementace abstraktních členů, jen aby „vyplnily mezery“ tak, jak byly.

Přesněji řečeno, někdy vidíte, kde ve snaze zvýšit opakované použití kódu byly dvě věci, které mají podobné implementace, stlačeny do jedné implementace - navzdory tomu, že tyto dvě věci nesouvisí. To má tendenci zvyšovat spojování kódu a oddělit je, když se ukáže, že podobnost byla jen náhoda, může být docela bolestivá, pokud není spatřena brzy.

Aktualizováno pomocí několika příkladů: Přestože to není přímo spojené s programováním jako takovým, domnívám se, že nejlepším příkladem pro tento druh věcí je lokalizace/internacionalizace. V angličtině je jedno slovo pro „ano“. V jiných jazycích může být mnoho, mnoho dalších, aby bylo možné gramaticky souhlasit s otázkou. Někdo by mohl říct, ah, pojďme mít řetězec pro "ano" a to je v pořádku pro mnoho jazyků. Pak si uvědomí, že řetězec „ano“ ve skutečnosti neodpovídá „ano“, ale ve skutečnosti pojem „kladná odpověď na tuto otázku“ - skutečnost, že je to „ano“, je náhoda a čas se ušetří pouze mít jedno „ano“ je zcela ztraceno při rozebírání výsledného nepořádku.

V naší kódové základně jsem viděl jeden obzvláště ošklivý příklad, kde to, co vlastně byl rodič -> vztah dítěte, kde rodič a dítě měly vlastnosti s podobnými jmény, bylo modelováno s dědičností. Nikdo si toho nevšiml, protože se zdálo, že všechno funguje, potom se chování dítěte (ve skutečnosti základní) a rodiče (podtřída) muselo jít různými směry. Jak by to mělo štěstí, stalo se to přesně ve špatný čas, kdy se blížil konečný termín - při pokusu o vyladění modelu sem a tam složitost (jak z hlediska logiky, tak duplikace kódu) prošla střechou okamžitě. Bylo také opravdu obtížné uvažovat o tom, jak s ním pracovat, protože kód jednoduše nesouvisel s tím, jak si podnikatelé mysleli nebo mluvili o pojmech, které tyto třídy představovaly. Nakonec jsme museli kousnout kulku a udělat nějaké hlavní refaktoring, kterému by se dalo úplně vyhnout, kdyby se původní člověk nerozhodl, že několik podobných znějících vlastností s hodnotami, které se shodovaly, představovalo stejný koncept.

0
FinnNk

Myslím, že bitvy jako dědičnost vs. složení jsou opravdu jen aspekty hlubší základní bitvy.

Tento boj je: co povede k co nejmenšímu množství kódu pro největší rozšiřitelnost budoucích vylepšení?

Proto je na tuto otázku tak těžké odpovědět. V jednom jazyce by mohlo být to, že dědičnost se zavádí rychleji než složení, než v jiném jazyce, nebo naopak.

Nebo by to mohl být konkrétní problém, který řešíte v kódu, výhod plynoucích z výhod, než z dlouhodobé vize růstu. To je stejně obchodní problém jako programovací.

Abychom se vrátili k otázce, dědictví by mohlo být špatné, pokud vás to v budoucnu dostane do rovné bundy - nebo pokud vytváří kód, který tam nemusí být (třídy bezděky třídí od ostatních, nebo horší, ale základní třídy, které začínají dělat spoustu podmíněných testů pro děti).

Existují situace, kdy by dědičnost měla být upřednostňována před kompozicí, a rozdíl je mnohem jasnější než stylová záležitost. Parafrázuji zde Sutter a Alexandrescu's C++ Coding Standards, protože moje kopie je v současné době v mé knihovně. Důrazně doporučujeme čtení, mimochodem.

Především se dědičnost nedoporučuje, pokud je to zbytečné, protože vytváří velmi intimní, těsně spjatý vztah mezi základnou a odvozenými třídami, který může způsobit bolesti hlavy po silnici kvůli údržbě. Není to jen názor nějakého chlapa, že syntaxe dědičnosti ztěžuje čtení nebo tak něco.

Jak již bylo řečeno, dědičnost je podporována, pokud chcete, aby se samotná třída odvozená znovu použila v kontextech, kde se v současné době používá základní třída, aniž by v tomto kontextu musel být kód upravován. Například, pokud máte kód, který začíná vypadat jako if (type1) type1.foo(); else if (type2) type2.foo();, pravděpodobně máte velmi dobrý důvod pro prohlížení dědičnosti.

Stručně řečeno, pokud chcete pouze znovu použít metody základní třídy, použijte složení. Pokud chcete použít odvozenou třídu v kódu, který v současné době používá základní třídu, použijte dědičnost.

0
Karl Bielefeldt