Organizace Free Software Foundation Europe u příležitosti včerejšího Dne Ady Lovelace vydala pod licencí CC BY-SA (Uveďte původ - Zachovejte licenci) půlhodinový animovaný film Ada & Zangemann - Příběh softwaru, skateboardů a malinové zmrzliny vycházející ze stejnojmenné knížky (online verze ve francouzštině).
Byla vydána nová verze 1.11.0 dynamického programovacího jazyka Julia (Wikipedie) určeného zejména pro vědecké výpočty. Přehled novinek v příspěvku na blogu a v poznámkách k vydání. Aktualizována byla také dokumentace. S vydáním verze 1.11 se předchozí verze 1.10 stala novou LTS verzí nahrazující verzi 1.6.
Byla vydána nová verze 6.8 živé linuxové distribuce Tails (The Amnesic Incognito Live System), jež klade důraz na ochranu soukromí uživatelů a anonymitu. Přehled změn v příslušném seznamu. Tor Browser byl povýšen na verzi 13.5.6.
Byla vydána nová stabilní verze 6.8 (YouTube) multiplatformního frameworku a GUI toolkitu Qt. Podrobný přehled novinek v poznámkách k vydání. Jedná se o LTS verzi. Pro komerční uživatele byla prodloužena podpora ze 3 na 5 let.
Desktopové prostředí KDE Plasma bylo vydáno ve verzi 6.2 (Mastodon, 𝕏). Přehled novinek i s videi a se snímky obrazovky v oficiálním oznámení. Podrobný přehled v seznamu změn.
Je druhé úterý v říjnu a tedy všem čtenářkám AbcLinuxu vše nejlepší k dnešnímu Dni Ady Lovelace (Ada Lovelace Day), tj. oslavy žen zabývajících se přírodními vědami, technologiemi, inženýrstvím a matematikou (STEM).
Byla vydána nová verze 2.47.0 distribuovaného systému správy verzí Git. Přispělo 83 vývojářů, z toho 28 nových. Přehled novinek v příspěvku na blogu GitHubu a v poznámkách k vydání.
Bylo vydáno OpenBSD 7.6. Opět bez písničky.
Programovací jazyk Python byl vydán v nové major verzi 3.13.0. Podrobný přehled novinek v changelogu.
Lze získat roota pouze se zapalovačem? Ano, lze.
Jo a používat obrázky pro prezentaci kusu programu je taky úžasný nápad.Holt se chtěl pochlubit, že má Visual Studio
Optimální hloubka hierarchie je 2Meh...
abstraktní rozhraní a implementace.Jestli "abstraktním rozhraním" myslíš koncept
interface
(Javovské jméno), tak ten je zrovna dost nahouby...Jestli "abstraktním rozhraním" myslíš koncept interface
(Javovské jméno), tak ten je zrovna dost nahouby...
To už jsi říkal mockrát a ještě nikdy jsi neřekl, proč si to myslíš. Já si zas myslím, že tenhle názor vychází z nesmyslných pověr o tom, že javovská rozhraní slouží k emulaci vícenásobné dědičnosti. Koncept interfejsů jakožto nástroje pro vytváření abstrakcí a volné vazby je jedna z mála věcí, které jsou na Javě dobře.
To už jsi říkal mockrát a ještě nikdy jsi neřekl, proč si to myslíš.Protože to zbytečně vyunucuje výřečný kód a soubory navíc.
Comparable
z Javy.interface
řeknou, že to je bezva, že díky Comparable
se nemusí vůbec starat o typ objeku, že stačí, že implementuje Comparable
a tudíž se dá porovnávat.if (A > B) ...
je na první pohled o dost zřejmější než if (A.compareTo(B)) ...
.compareTo()
. Kdyby se čísla porovnávaly taky pomocí compareTo()
, tak dejme tomu, že by to bylo alespoň konzistentní... ale takhle...
interface
nějak sjednocovat rozhraní/(chování?) objektů navenek... Jedinný, co ve skutečnosti dělá, je, že stanoví jméno metod(y) (s parametry a návratovím typem), kterou má objekt implementovat. Jestli daný objekt správně interpretuje parametry, správně provede, co má províst a vrátí správný výsledek, to interface
ani v nejmenším nezaručí. Takže seš na tom vlastně stejně, jako bez něj - stejně musíš prostě danýmu objektu věřit, že to má správně implementováno - jako u těch operátorů nebo u obyčejných metod...
interface
špatný, spíš to, že je v podstatě zbytečný...
Osobně nevidím problém v tom, aby "a > b" byl alias (na syntaktické úrovni) k "a.compareTo(b)".
Já ano. Raději bych pouze metody, bez operátorů. Klidně i 1.sum(2).compareTo(3) s vyhodnocením na (Boolean)True.
Interface ti přináší polymorfismus bez dědičnosti, ale s typovou kontrolou.Jak jako bez dědičnosti? Nedědí se implementace - pochopitelně, když žádná není, ale to rozhraní se přece dědí. Všechny třídy to rozhraní implementující "děděj" to rozhraní. Je to stejný jako čistě virtuální metody v C++.
Rozhraní se nedědí, rozhraní se vystavuje.Však jsem taky napsal "dědí" v uvozovkách... Ono je jedno jak tomu řekneš, prcincip je stejný. Všechny porovnávatelné objekty mají stejného "předka"
Comparable
.
Protože to zbytečně vyunucuje výřečný kód a soubory navíc.
Taky mám jednoho takového kámoše. Třída, která volá, ehm, co volá, přímo křičí ROZDĚLIT, tak mi řekne "nechci tam další soubor" (alisa třídu). Chjo. Toto prostě není rozumný důvod.
Zastánci interface řeknou, že to je bezva, že díky Comparable se nemusí vůbec starat o typ objeku, že stačí, že implementuje Comparable a tudíž se dá porovnávat.
Což je fajn, ne?
V praxi to znamená, že každý objekt, který chci porovnávat, musí includovat příslušnej soubor s definicí Comparable a musí mít v hlavičce, že to implementuje (aka "dědičnost").
No a?
Když mám naproti tomu k dispozici operator overloading, stačí mi, když třída implementuje příslušný operátor. Ten operátor má svuj pevně danej tvar a není na to potřeba vůbec žádná dědičnost. A navíc kód if (A > B) ... je na první pohled o dost zřejmější než if (A.compareTo(B))
No dobře, ale jak jinak zjistíš, že ty objekty můžeš porovnávat? Pokud by byl k disposici operátor overloading, tak nevím, jestli můžu to (Object).compareTo(Object)
udělat. U (Comparable).compareTo(Comparable)
mám jistotu, a vlastně mě vůbec nezajímá skutečný typ (třída) toho objektu.
Ono v Javě je to pak taková nekonzistence, že čísla (a ještě něco?) se porovnává operátory '>' apod. a objekty funkcí compareTo(). Kdyby se čísla porovnávaly taky pomocí compareTo(), tak dejme tomu, že by to bylo alespoň konzistentní... ale takhle...
Jo, s tímhle souhlasím. Škoda.
Taky mám jednoho takového kámoše. Třída, která volá, ehm, co volá, přímo křičí ROZDĚLIT, tak mi řekne "nechci tam další soubor" (alisa třídu). Chjo. Toto prostě není rozumný důvod.No tak jasný, ani jeden extrém není dobrej...
No a?Květnatost kódu. Nemám chuť číst rommány... Ale budiž, dejme tomu, že tohle není kritický...
Nemáš jistotu... To je právě ta iluze, kterou konceptNo dobře, ale jak jinak zjistíš, že ty objekty můžeš porovnávat? Pokud by byl k disposici operátor overloading, tak nevím, jestli můžu to
(Object).compareTo(Object)
udělat. U(Comparable).compareTo(Comparable)
mám jistotu, a vlastně mě vůbec nezajímá skutečný typ (třída) toho objektu.
interface
navozuje...compareTo()
vyleze správnej výsledek, to ti interface nijak zaručit nedokáže. Takže to imho není žádná výhra...
Nemáš jistotu... To je právě ta iluze, kterou koncept interface navozuje... Jedinou jistotu, kterou ti to dává, je, že kompilátor na tebe nezařve, že daný objekt metodu neobsahuje, to je všechno.
To (že danou metodu obsahuje) je ale velmi silný splněný požadavek. Pokud už někdo implementuje Comparable, tak mám (silnou) jistotu, že chtěl implementovat (no jinak by to ani nepřeložit) metody compare.
Že to správně funguje, že z tý mateody compareTo() vyleze správnej výsledek, to ti interface nijak zaručit nedokáže. Takže to imho není žádná výhra...
To mi ale nezaručuje ani přetížení operátorů, pokud je vůbec v dané třídě implementováno (na což mě ani kompilátor nemusí upozornit).
To (že danou metodu obsahuje) je ale velmi silný splněný požadavek. Pokud už někdo implementuje Comparable, tak mám (silnou) jistotu, že chtěl implementovat (no jinak by to ani nepřeložit) metody compare.Ok
To mi ale nezaručuje ani přetížení operátorůJj, nechtěl jsem říct, že přetěžování operátorů něco zaručuje...
Taky mám jednoho takového kámoše. Třída, která volá, ehm, co volá, přímo křičí ROZDĚLIT, tak mi řekne "nechci tam další soubor" (alisa třídu). Chjo. Toto prostě není rozumný důvod.Taky znám jednoho podobného – ten má zase panickou hrůzu z tabulek v relační databázi – místo aby tam měl o jednu „navíc“ a udělal to pořádně, radši totálně zprasí datový model – prý je to pak jednodušší a přehlednější.
Jedinný, co ve skutečnosti dělá, je, že stanoví jméno metod(y) (s parametry a návratovím typem), kterou má objekt implementovat.V některých prostředích bez interface nelze tyto metody genericky používat, například ten GObject. GObject umožňuje spoustu funkcí dynamicky typovaných jazyků použít třeba v C, ale jsou všechny nepovinné. Standardní způsob práce umožňuje tvořit efektivnější programy pomocí buď statického typování, nebo typování s pomocí dědičnosti a interface.
Zastánciinterface
řeknou, že to je bezva, že díkyComparable
se nemusí vůbec starat o typ objeku, že stačí, že implementujeComparable
a tudíž se dá porovnávat.
V praxi to znamená, že každý objekt, který chci porovnávat, musí includovat příslušnej soubor s definicí Comparable a musí mít v hlavičce, že to implementuje (aka "dědičnost").
Žádné includování souborů v Javě není, to si s něčím pleteš implements Comparable
dává explicitně najevo, že na objektu je definováno porovnání. Přetěžování operátorů je speciální případ, ale úplně klidně by šlo udělat tím, že třída implementuje nějaký interfejs (slovo implements
je ta nejhorší vlastnost javovských interfejsů, exposes
by bylo mnohem lepší), v případě operátorů porovnání klidně to Comparable
.
Ono v Javě je to pak taková nekonzistence, že čísla (a ještě něco?) se porovnává operátory '>' apod. a objekty funkcícompareTo()
. Kdyby se čísla porovnávaly taky pomocícompareTo()
, tak dejme tomu, že by to bylo alespoň konzistentní... ale takhle...
To je pravda, ale vůbec to nesouvisí s interfejsy. Primitivní typy v Javě jsou takový pozůstatek, který se sice hodí pro výkonnostní optimalizace, ale jinak (spolu s poli) spíš překáží.
Teoreticky, alespoň pokud vím, má interface
nějak sjednocovat rozhraní/(chování?) objektů navenek... Jedinný, co ve skutečnosti dělá, je, že stanoví jméno metod(y) (s parametry a návratovím typem), kterou má objekt implementovat.
Jistě, protože to je (z pohledu typového systému) všechno, co objekt navenek vystavuje. Ano, sjednucuje se rozhraní, nikoliv chování, protože…
Jestli daný objekt správně interpretuje parametry, správně provede, co má províst a vrátí správný výsledek, to interface
ani v nejmenším nezaručí.
něco takového totiž v plné obecnosti strojově ověřit nejde. Existují programovací jazyky, které s určitými omezujícími podmínkami umí popsat a ověřit ledacos, akorát že se jim spíš než programovací jazyky říká dokazovače vět. Proč asi.
Takže seš na tom vlastně stejně, jako bez něj - stejně musíš prostě danýmu objektu věřit, že to má správně implementováno - jako u těch operátorů nebo u obyčejných metod...
Jsi zastáncem dynamicky typovaných jazyků? Tam je rozhraní objektů skutečně jenom věcí domluvy/dokumentace, ale ve staticky typovaných jazycích to funguje jinak. Jo, souhlasím, že vyjadřovací prostředky dnešních mainstreamových staticky typovaných programovacích jazyků jsou chudé (stačí chvíli zapřemýšlet nad tím, jak udělat typ pro blbé datum!), ale interfejsy jsou IMHO krok správným směrem.
Žádné includování souborů v Javě není, to si s něčím pletešOk, tak
using
, hrajme si se slovíčky...
implements Comparable
dává explicitně najevo, že na objektu je definováno porovnání.
Ne tak úplně. implements Comparable
dává najevo, že na objektu existuje metoda se jménem compareTo()
a takovými a makovými parametry. Všechno ostatní je konvence - a na tu nám stačí dokumentace, že...
něco takového totiž v plné obecnosti strojově ověřit nejde. Existují programovací jazyky, které s určitými omezujícími podmínkami umí popsat a ověřit ledacos, akorát že se jim spíš než programovací jazyky říká dokazovače vět. Proč asi.Jj, jasný, já po nikom/ničem nechci, aby ověřoval správnost implementace...
Jsi zastáncem dynamicky typovaných jazyků?Ne.
Tam je rozhraní objektů skutečně jenom věcí domluvy/dokumentace, ale ve staticky typovaných jazycích to funguje jinak.No, to je pravda, otázka je, jestli je účelem
interface
tohle řešit... Na to je totiž imho šikovnější nějaká ta dědičnost a/nebo polymorfismus.
Ne tak úplně.Beru, žeimplements Comparable
dává najevo, že na objektu existuje metoda se jménemcompareTo()
a takovými a makovými parametry. Všechno ostatní je konvence - a na tu nám stačí dokumentace, že... No, to je pravda, otázka je, jestli je účeleminterface
tohle řešit... Na to je totiž imho šikovnější nějaká ta dědičnost a/nebo polymorfismus.
implements Comparable
dává explicitně najevo, že na objektu je definováno porovnání je nepřesné, ale když víme, co naše typové systémy umí a co neumí, považuju to za totéž jako implements Comparable
dává explicitně najevo, že metoda compareTo
se drží dané konvence.
A o to jde. Typový systém může staticky zkontrolovat, že volám compareTo
skutečně jen na těch objektech, které se hlásí k tomu, že jejich compareTo
opravdu porovnává. Můžu mít compareTo
i na objektech, které Comparable
nevystavují – chci ji na nich opravdu volat, když mi jde o porovnávání?
No vždyť toto přeci je polymorfirmus. Dědičnost nebo interface (neboJsi zastáncem dynamicky typovaných jazyků?Ne.Tam je rozhraní objektů skutečně jenom věcí domluvy/dokumentace, ale ve staticky typovaných jazycích to funguje jinak.No, to je pravda, otázka je, jestli je účeleminterface
tohle řešit... Na to je totiž imho šikovnější nějaká ta dědičnost a/nebo polymorfismus.
protocol
, to je také pěkný název) jen a pouze deklaruje statickou typovou informaci. Jistě, kompilátor by mohl sám poznat že tam ta metoda je a automaticky třídu považovat za Comparable - jsou takové jazyky.
slovo implements je ta nejhorší vlastnost javovských interfejsů, exposes by bylo mnohem lepšíJá bych tam dal to co tam má dle terminologie být, a sice
is Comparable
.
Zkuste Forth nebo Lisp nebo SmalltalkTohle jsem čekal. :))) A co takhle zkusit Lisp? No, to asi ne, ale Lisp by měl vyhovovat. Nebo raději Lisp. Případně Lisp. To je všechno hezké, ale osobně bych ze všeho nejvíc doporučoval Lisp. A pokud ne ten, tak už snad jedině Lisp. Nepřipadáte si monotematický?
A pouzivat DRY a Javu v jedne vete? To si snad autor dela legraci.. Zkuste Forth nebo Lisp nebo Smalltalk, to je skutecne DRY.Hidopišská poznámka: autor nikde DRY a Javu v jedné větě nepoužil. Java se vlastně v celém blogpostu nevyskytuje vůbec...
Java se vlastně v celém blogpostu nevyskytuje vůbec...He, pravda, plácnul jsem dole nesmysl. Budu muset kouknout, jak dneska vypadá C#, určitě vymysleli nějaké nové úžasné šílenosti, jak je Andersovým dobrým zvykem
class MyContainer { private Collection items = new Collection(); public add(Object o) { items.add(o); } public addAll(Collection c) { for (Object o : c) this.add(o); } }A ted treba, kdyz si chceme udelat druhou tridu, ktera umi to same, ale jeste si udrzuje pocet vlozenych prvku:
class MyAdvancedContainer extends MyContainer { private int count = 0; public add(Object o) { super(o); count++; } public addAll(Collection c) { super(c); count += c.size(); } }kde je teda chyba? vzdyt jsem tu dedicnost pouzil spravne, ne?
Toto by mělo být popsáno v dokumentaci třídy MyContainer jako poznámka k dědičnostitakze mi nestaci znat rozhrani objektu, ale potrebuju znat i to, co se deje uvnitr objektu. to nezni zrovna dobre.
(pokud je teda povolená)jasne a spravne reseni je zakazat dedicnost, aby uz nedochazelo k takovym chybam.
takze mi nestaci znat rozhrani objektu, ale potrebuju znat i to, co se deje uvnitr objektu. to nezni zrovna dobre.Rozhraní by Vám stačilo znát pokud byste přepoužíval jen rozhraní. Ale jelikož voláním super() přepoužíváte i implementaci, tak potřebujete znát i tu. Viz
class MyAdvancedContainer extends MyContainer { private int count = 0; private MyContainer myContainer = new MyContainer(); public add(Object o) { myContainer.add(o); count++; } public addAll(Collection c) { myContainer.addAll(o); count += c.size(); } } & voila, najednou to funguje jak má. A je mi úplně jedno co s tím dělá MyContainer.
jasne a spravne reseni je zakazat dedicnost, aby uz nedochazelo k takovym chybam.Řešení je nepoužívat dědičnost implementace ale rozhraní. V javě je to přes "implements" takže tam nahoře by mělo být správně něco jako "implements Container" a MyContainer by mělo totéž. Dědičnost implementace vede k dost nepříjemným chybám a navíc k čtení programů stylem jojo. Dědičnost rozhraní je naopak základním stavebním kamenem.
Potíž by byla, kdyby nadtřída měla třeba 20 metod, a vy byste chtěl "překrýt" třeba jen jednu nebo dvě. Zbylé by vaše podtřída musela taky implementovat (aby splnila rozhraní) a jen v nich předávat volání do nadtřídy. Sice korektní, ale poněkud nepraktické.Co je na tom za "potíž" a "nepraktického"? Říká se tomu delegace a buď tam těch 18 metod namlátíte za 5 minut sám (tomu se říká práce :) nebo Vám to nějaké IDE vygeneruje. V některých jazycích je na to pak přímo klíčové slovo. Mimochodem rozhraní s 20 metodami mi přijde že je trochu moc, určitě se dá rozdělit na menší tematické okruhy.
Co je na tom za "potíž" a "nepraktického"? Říká se tomu delegace a buď tam těch 18 metod namlátíte za 5 minut sám (tomu se říká práce :) nebo Vám to nějaké IDE vygeneruje. V některých jazycích je na to pak přímo klíčové slovo.Já jen navazuju na diskuzi o přehlednosti a korektnosti různých řešení... Proti delegaci nic nemám, ale myslím si, že masivní delegování přinese větší nepřehlednost než třeba několikaúrovňová dědění.
Mimochodem rozhraní s 20 metodami mi přijde že je trochu moc, určitě se dá rozdělit na menší tematické okruhy.Tohle je stejná jako odstavec výše... Je několik řešení, každé má výhody a nevýhody, a někdo volí to a ten zas tohle. Osobně si nemyslím, že je 20 metod nějak moc. Záleželo by na konkrétním rozhraní, zda ho rozdělovat (a starat se tak o víc typů), nebo ne.
Proti delegaci nic nemám, ale myslím si, že masivní delegování přinese větší nepřehlednost než třeba několikaúrovňová dědění.Vám asi přijde "nepřehledné", že tam bude těch 18 jednořádkových metod, ale to se dá naaranžovat pod sebe na konec souboru tak, že to tam překážet nebude. IMHO není nic nepřehlednějšího než dědění implementace, zvlášť když si trochu zadivočíte s přetěžováním. Při čtení kódu byť té koncové vrstvy se budete muset zajímat o všechno co je pod tím. U každého řádku budete koukat, jestli se používá pole Vaší třídy nebo o 3 úrovně vejš, případně jestli se volá ta či ona metoda. Za chvíli budete po těch třídách jezdit jak to jojo. Dědění implementace přináší závislost na implementaci a jak víme tak na implementaci závislost být nesmí. Proti tomu při delegaci máte naprostou čistotu - na každém řádku je jasně napsáno co a jak používáte. Používáte pouze rozhraní objektů, takže je Vám úplně jedno co se děje dál. Ve vaší třídě je jen to, co je tam napsáno a není tam nic implicitně zděděno. Přehlednější už to být nemůže. Vemte si ještě následující úpravu příkladu:
class MyAdvancedContainer extends MyContainer { private int count = 0; private MyContainer myContainer; public MyAdvancedContainer(MyContainer myContainer) { this.myContainer = myContainer; } public MyAdvancedContainer() { this(new MyContainer()); } public add(Object o) { myContainer.add(o); count++; } public addAll(Collection c) { myContainer.addAll(o); count += c.size(); } }(Přesunul jsem inicializaci myContainer do konstruktoru. Třída teď může delegovat jakémukoliv potomkovi.) A teď
myContainer c = new MyAdvancedContainer(new MyAdvancedContainer());A bude to fungovat! Zkuste to udělat s děděním. Budete tam muset cpát nějaký count2 a za chvíli z toho zjihnete.
U každého řádku budete koukat, jestli se používá pole Vaší třídy nebo o 3 úrovně vejš, případně jestli se volá ta či ona metoda.To pole (případně metoda) v předkovi bude buď privátní (a pak se mi nebude plést s polem potomka) nebo bude z potomka viditelné, ale pak je součástí veřejného „rozhraní“ (alespoň ve vztahu k potomkovi) a když ho používám, tak musím přirozeně vědět, co dělám (vyčíst si to z komentářů případně z kódu). V čem je problém? Když se ještě vrátím k původnímu příkladu: zde autor změnil chování metody, aniž by se obtěžoval zjišťovat, odkud všude se tato metoda volá a co všechno její změnou rozbije. Kdyby se obtěžoval, tak by zjistil, že
add()
se volá z addAll()
a tu chybu by neudělal.*
Celé je to o tom, že tahle dědičnost je něco jiného než rozhraní a nemůžeme úplně ignorovat implementaci. To je ale prostě vlastnost dědičnosti a ne chyba – k chybě dojde až ve chvíli, kdy programátor o této vlastnosti neví, nebo na ni kašle – pak si logicky nabije hubu, ale to je jeho problém, ne problém dědičnosti jako takové.
*) nemluvě o tom, že zde by docela stálo za to, udělat jednotkový test a ověřovat, zda přidání/odebrání prvků vede k patřičné změně počítadla. Nejsem nějaký fanatik, který by musel mít 110% kódu pokryto JUnit testy (spíš na to většinou kašlu), ale tady je to testování asi opravdu na místě.
Celé je to o tom, že tahle dědičnost je něco jiného než rozhraní a nemůžeme úplně ignorovat implementaci.S tím souhlasím. Problém pak nastává, když implementace není dostupná nebo když je ta hierarchie tříd rozsáhlá. Další problém je, že pokud tu třídu, ze které dědím, spravuje někdo jiný, tak bych měl po každé jeho změně projít svůj kód (někdy stačí spustit testy), jestli se mi nerozbil. Pěkný příklad má Oleg: Subtyping, Subclassing, and Trouble with OOP.
To pole (případně metoda) v předkovi bude buď privátní (a pak se mi nebude plést s polem potomka) nebo bude z potomka viditelné, ale pak je součástí veřejného „rozhraní“ (alespoň ve vztahu k potomkovi) a když ho používám, tak musím přirozeně vědět, co dělám (vyčíst si to z komentářů případně z kódu). V čem je problém?V tom, že to tak není. Pole není rozhraní. Pole je implementace. Pokud dědíte implementaci, tak se Vám prostě nakopíruje k Vašemu kódu, akorát že to nevidíte. Dědění implementace je tudíž copy+paste. Jestli to je špatně nebo ne, je na situaci, ale vetšinou to špatně je. BTW tím, že si něco chcete "vyčíst z kódu" porušujete oddělení implementace a rozhraní.
Kdyby se obtěžoval, tak by zjistil, že add() se volá z addAll() a tu chybu by neudělal.*Ano a to "obtěžování se" je to, co nechceme, aby lidi museli dělat.
Celé je to o tom, že tahle dědičnost je něco jiného než rozhraní a nemůžeme úplně ignorovat implementaci. To je ale prostě vlastnost dědičnosti a ne chyba – k chybě dojde až ve chvíli, kdy programátor o této vlastnosti neví, nebo na ni kašle – pak si logicky nabije hubu, ale to je jeho problém, ne problém dědičnosti jako takové.Problém je ve špatném vnímání vztahu dědičnosti v OOP. Vy máte nalito v hlavě takové ty klasické příklady z učebnice kde se něco "dědí" a k tomu se něco přidá (autíčka, zvířátka). Jenže relalita je taková, že se dědí chování (to je ten interface), protože mě u objektů nesmí nikdy zajímat, co je uvnitř (zapouzdřenost), ale jen jak se chovají. Tudíž to není tak, že Kráva extends Zvíře (a co je to Zvíře ví jen ten kdo to vymyslel), ale že Kráva implements Bučení (a je to jasné).
*) nemluvě o tom, že zde by docela stálo za to, udělat jednotkový test a ověřovat, zda přidání/odebrání prvků vede k patřičné změně počítadla. Nejsem nějaký fanatik, který by musel mít 110% kódu pokryto JUnit testy (spíš na to většinou kašlu), ale tady je to testování asi opravdu na místě.To je v tomhle příkladu dost jedno, jestli na tu chybu dojdete testem nebo až Vám někdo omlátí ten výrobek o hlavu. Pointa je že tam ta chyba je :)
Pokud dědíte implementaci, tak se Vám prostě nakopíruje k Vašemu kódu, akorát že to nevidíte. Dědění implementace je tudíž copy+paste.Blbost.
Jenže relalita je taková, že se dědí chování (to je ten interface)Interface není chování!
Interace ti jenom pojmenovává metody (případně proměnné), ale o tom, co tyto metody dělají, co vracejí, co potřebují za parametry, atd., o tom ti rozhraní řekne naprostý kulový.Myslím, že nelze obecně tvrdit, že interface neříká nic o parametrech, návratových hodnotách, apod.
int NejakaFce(int x);
. Jak z toho vyčucháš, co má být to x? A jak z toho vyčucháš, co ta funkce vrací? Nijak. No zkrátka chování určuje implementace.
Jak z toho vyčucháš, co má být to x? A jak z toho vyčucháš, co ta funkce vrací? Nijak. No zkrátka chování určuje implementace.
To x má být int. Má vrátit int. Okolí (použití dané fce) na to musí být připraveno. Další požadavky jsou v implementaci a v testech. Pokud například by ten výsledek měl reprezentovat stupně kelvina, tak test selže na záporných hodnotách (i když osobně bych tam rád viděl příslušný typ Kelvin, než int).
interface
a čuchat, která funkce by to asi tak mohla být podle jejich názvů a datových typů? Nebo si radši za B) otevřeš dokumentaci a tam si to přečteš? Pochopitelně B). A dokumentace reflektuje co? Interface taky, ale hlavně implementaci.
To jsou ale dvě různé věci. Pokud budu nabízet nějaké API, tak vím, že (Color)getColor() mi vždy vrátí validní barvu (Color je můj nebo systémový datový typ, to už je jedno, prostě mám validní hodnotu daného typu). A jestli je to "špatná" barva, mě nezajímá, moje aplikace (za tím API), to zpracuje.
Implementátor toho API na straně řekněme dodavatele ten manuál samozřejmě potřebovat bude, aby věděl, jak vytvořit správnou hodnotu typu Color.
Chování (a ještě k tomu správné) dané implementace ti žádný jazyk stejně nezaručí. Proto si myslím, že implementace interface je lepší, než se spoléhat na správné přetížení nějaké metody.
Co uděláš, budeš za A) pročítat interface a čuchat, která funkce by to asi tak mohla být podle jejich názvů a datových typů? Nebo si radši za B) otevřeš dokumentaci a tam si to přečteš? Pochopitelně B).
Nejprve B ale potom už jenom A. Interface mi zaručí, že tam bude vždy to co očekávám.
Pokud budu nabízet nějaké API, tak vím, že (Color)getColor() mi vždy vrátí validní barvu (Color je můj nebo systémový datový typ, to už je jedno, prostě mám validní hodnotu daného typu). A jestli je to "špatná" barva, mě nezajímá, moje aplikace (za tím API), to zpracuje.Barva opravdu není kritická, ale mějme jiný příklad: Třeba nějaký kontejner, u kterého potřebuješ metodu, která vrátí počet prvků. Bude to něco jako
int count(int x)
. Ano, ta funkce ti vždycky vrátí validní hodnotu daného typu a ano, tvoje aplikace to zpracuje, ale nemůžeš říct, že tě nezajímá, že to je "špatná" hodnota, protože tvoje aplikace sice půjde zkompilovat, nespadne, ale bude podávat špatný výsledek. A to je to, vo co de. Chování. Implementace.
Proto si myslím, že implementace interface je lepší, než se spoléhat na správné přetížení nějaké metody.Je to stejné. Akorátže s interface je obvykle spojen větší důraz na dodržování konvence, respektive ta konvence je univerzálnější, takže se interface jeví jako "spolehlivější". V holé podstatě to ale je stejné...
Interface mi zaručí, že tam bude vždy to co očekávám.Jasný. Jako například když ti funkce
float sinus(float x)
bude ve skutečnosti vracet kosinus, tak ti to vůbec nebude vadit, protože přece to je validní float
tak jako tak.
int count()
neměl být parametr pochopitelně...
Ano, ta funkce ti vždycky vrátí validní hodnotu daného typu a ano, tvoje aplikace to zpracuje, ale nemůžeš říct, že tě nezajímá, že to je "špatná" hodnota, protože tvoje aplikace sice půjde zkompilovat, nespadne, ale bude podávat špatný výsledek.
Mě to nezajímá u interface. Ale zajímá mě to u testů. Pokud ta implementace nebude splňovat kontrakt, neprojde testy. Tak jednoduché to je.
A to je to, vo co de. Chování. Implementace.
Já s tebou v podstatě souhlasím. Ale nesouhlasím s tím, že konstrukce interface je k ničemu. Dělá přesně to co má. Dává jistotu, že je implementovaná metoda.
vůbec nebude vadit
Bude. Ale jinde. To už se opakuju. Myslím, že myšlenka je jasná.
Ale nesouhlasím s tím, že konstrukce interface je k ničemu. Dělá přesně to co má. Dává jistotu, že je implementovaná metoda.Ok, souhlasím. Jak moc je toto k něčemu dobré, nechť si rozhodne každý sám za sebe... Ale v zásadě to tak je...
Jak z toho vyčucháš, co má být to x? A jak z toho vyčucháš, co ta funkce vrací? Nijak.My, co jsme už objevili dokumentaci, takové otázky neklademe :).
No zkrátka chování určuje implementace.Ta se buď řídí dokumentací rozhraní, nebo někdo zaslouží pověsit za blíže nespecifikovaný orgán do průvanu. Člověče, přemýšlej taky trochu prakticky :).
Ještě že jsem se já naučil to GDB. Číst nějakou dokumentaci (teda víc než README a INSTALL samozřejmě)? Z toho by mě mrdlo.
Dědění je v podstatě úplně stejný příklad, jako to, co píšeš ty, jediný dva rozdíly jsou v tom, že 1) dědičnost předchozí prvek vytvoří automaticky, ty to děláš ručně (kód navíc). Žádný kód se do potomka nekopíruje, ten využívá instanci rodiče stejně jako ty v tom příkladu (akorát se to jinak jmenuje).Při dědění se instance rodiče nevytváří. Instance je jen jedna - té odvozené třídy a je v ní mix vzniklý ze všech tříd v hierarchii pod tím. Kde se metody překrývají, tam jsou začleněny všechny verze. Takže to defacto copy a paste je. A kdo používá dědění na "sdílení společných částí kódu" v podstatě jen schovává copy/paste za honosný ksicht.
2) dědičnost umožňuje rozumné řízení oprávnění přístupu k metodám/proměnným daného objektu.Tohle nějak nechápu. Řízení přístupu není od dědičnosti nijak odvislé a potažmo ani od OOP. Můžete dát přístup k položce pro metodu v odvozené třídě stejně jako pro metodu začínající na "franta".
Interface není chování! Interace ti jenom pojmenovává metody (případně proměnné), ale o tom, co tyto metody dělají, co vracejí, co potřebují za parametry, atd., o tom ti rozhraní řekne naprostý kulový. Maximálně to můžeš čuchat z názvu metody, to ale není úplně dobrý přístup. Chování určuje implementace.Interface určuje popis chování, implementace určuje jak je toho dosaženo. Interface může být popis typů parametrů, může to být sada testů, kus dokumentace, to je jedno. Důležitý je účel. Interface je to, co zajímá uživatele balíku, implementace je to, co ho nezajímá. A název metody je dost dobrý prostor pro vyjádření účelu, nevím proč by tedy nebyl dobrý přístup z toho "čuchat" co od té věci očekávat.
Při dědění se instance rodiče nevytváří. Instance je jen jedna - té odvozené třídy a je v ní mix vzniklý ze všech tříd v hierarchii pod tím. Kde se metody překrývají, tam jsou začleněny všechny verze. Takže to defacto copy a paste je. A kdo používá dědění na "sdílení společných částí kódu" v podstatě jen schovává copy/paste za honosný ksicht.To si dovolím označit za nesmysl. Copy & paste by znamenalo, že tam ten kód bude dvakrát, což není. Dokonce ani ve výsledném programu ten kód není dvakrát. Každá funcke ve zdrojácích napsaná, pokud není inline, se objevuje v programu/knihovně jednou.
A název metody je dost dobrý prostor pro vyjádření účelu, nevím proč by tedy nebyl dobrý přístup z toho "čuchat" co od té věci očekávat.Tenhle názor tak úplně nesdílím.
Copy & paste by znamenalo, že tam ten kód bude dvakrát, což není. Dokonce ani ve výsledném programu ten kód není dvakrát. Každá funcke ve zdrojácích napsaná, pokud není inline, se objevuje v programu/knihovně jednou.To, že není do očí bijící, že to tam je několikrát z toho právě dělá ten honosný ksicht.
Jo, už si to představuju ... ccaA název metody je dost dobrý prostor pro vyjádření účelu, nevím proč by tedy nebyl dobrý přístup z toho "čuchat" co od té věci očekávat.Tenhle názor tak úplně nesdílím.
// Skontroluje jestli je tiskárna v pořádku. To znamená, jestli nedošel papír. int tiskarna() { ...
To, že není do očí bijící, že to tam je několikrát z toho právě dělá ten honosný ksicht.Nic tam několikrát není. Prosímtě, už ten nesmysl neopakuj...
To, že není do očí bijící, že to tam je několikrát z toho právě dělá ten honosný ksicht.To, že to tam několikrát není, dělá z tvého tvrzení holý nesmysl.
Při dědění se instance rodiče nevytváří. Instance je jen jedna - té odvozené třídy a je v ní mix vzniklý ze všech tříd v hierarchii pod tím.Je vidět, že vůbec netušíš, co je dědičnost.
class Rodic
{
private:
int a;
public:
Rodic(int a)
:a(a)
{
}
};
class Potomek: public Rodic
{
public:
Potomek(int a)
:Rodic(a)
{
}
};
Všimni si, že v konstruktoru potomku specifikuju, jak se má volat konstruktor rodiče.Tohle nějak nechápu. Řízení přístupu není od dědičnosti nijak odvislé a potažmo ani od OOP.Jistě, ale já mluvil o opačném směru: dědičnost potřebuje řízení přístupu.
public
, nemůže využívat položky protected
, protože v tom tvým příkladu jsou stejný jako private
.
Důležitý je účel. Interface je to, co zajímá uživatele balíku, implementace je to, co ho nezajímá.Ok, můžeš si klidně termíny "interface" a "implementace" nadefinovat jako "to, co uživatele zajímá" a "to, co ho nezajímá". Bohužel pak budeš mít tyhle pojmy jinak nadefinovaný než zbytek světa, což osobně nedoporučuju...
Ve skutečnosti před vytvořením instance potomka se prvně vytvoří instance všech jeho rodičů, postupně od toho nejvýše položeného v hierarchii.Volání nadřazeného konstruktoru nevytváří žádnou instanci. Ta někdy ani udělat nejde, protože nadřazená třída může být poloabstraktní. Nadřazený konstruktor pouze inicializuje pole respektive dělá to co je do něj naprogramováno.
Než začneš dedičnost kritizovat, je dobrý vědět, jak funguje. Ten tvůj příklad skutečně dělá prakticky to samé, co dědičnost.To samé to není. To první je dedění, to druhé je delegace a do třetí je dekorátor. A prakticky to taky není to samé, protože to první nefungovalo. Poznámky typu že nevím o čem mluvím začínám milovat. Kdybyste se o to nějakým způsobem zajímal, tak byste ten rozdíl znal. Jsou to standardní a základní věci.
To je právě problém tvého příkladu, ta "dědičná" třída může využívat pouze ty členy "rodiče", které jsou úplně public, nemůže využívat položky protected, protože v tom tvým příkladu jsou stejný jako private.To není problém, to je design. Dekorovat chci právě jen to co je vidět zvenku a nezajímá mě, jak to je udělané uvnitř. Proto taky ten můj příklad funguje, i když dekoruju tím counterem dvakrát.
Ok, můžeš si klidně termíny "interface" a "implementace" nadefinovat jako "to, co uživatele zajímá" a "to, co ho nezajímá". Bohužel pak budeš mít tyhle pojmy jinak nadefinovaný než zbytek světa, což osobně nedoporučuju...Právě ten zbytek světa (kromě Vás asi) to má nadefinované přesně takhle.
Ta někdy ani udělat nejde, protože nadřazená třída může být poloabstraktní.Pokud nelze vytvořit rodič kvůli čistě virtuálním členům, nelze vytvořit ani potomek. A to právě proto, že nelze vytvořit instanci rodiče. Kdyžtak si radši zkus někdy tohle v C++, protože C# řeší spoustu věcí za tebe narodzíl od C++.
Volání nadřazeného konstruktoru nevytváří žádnou instanci. Nadřazený konstruktor pouze inicializuje pole respektive dělá to co je do něj naprogramováno.A co sis myslel, že znamená "vytvoření instance"? Není to nic jiného, než 1) alokování paměti 2) zavolání konstruktoru. Když se vytváří instance potomka, alokuje se prvně dost místa pro obě třídy, pak se zavolá konstruktor rodiče (ten se volá vždy, ty můžeš toto volání pouze pozměnit, viz tady i v C#), která zinicializuje obsah rodičovské třídy, a pak se zavolá konstruktor potomka.
class Rodic
{
private:
int a;
public:
Rodic(int a)
:a(a)
{
}
};
class Potomek: public Rodic
{
private:
Potomek(int x, int y) //dva paramtery, abych to odlišil od konstruktoru rodice
:Rodic(4)
{}
public:
Potomek()
:Rodic(4)
{}
};
A teď nějaký vlastní kód:Potomek p
Kdyby byla pravda, co říkáš, šlo by udělat následující, totiž použít konstruktor z rodičovské třídy, jestliže je do potomka nakopírovaný:
Potomek p(4);
Jenže ouha, kompilátor vyhodí chybu: error: no matching function for call to ‘Potomek::Potomek(int)’
Čili není tam.private
nebo protected
, jenže chybový kód při volání takového konstruktoru vypadá jinak, tento kód
Potomek p(4, 4);
vyhodí tuto chybu: error: ‘Potomek::Potomek(int, int)’ is protected
Potomek p;
p.Rodic(4);
který vyhodí tento chybový kód: error: invalid use of ‘Rodic::Rodic’
A prakticky to taky není to samé, protože to první nefungovalo.Neumíš to správně napsat != nefunguje to.
To není problém, to je design. Dekorovat chci právě jen to co je vidět zvenku a nezajímá mě, jak to je udělané uvnitř.Samozřejmě, já ale mluvím o tom, že v tvojí metodě nelze udělat
protected
, čili takovou metodu, která nějak upravuje rodičovský objekt (jak to dělá nás nezajímá), ale přitom nechce dovolit tuto úpravu cizím objektům, pouze těm odvozeným.
Právě ten zbytek světa (kromě Vás asi) to má nadefinované přesně takhle.Jasně
Pokud nelze vytvořit rodič kvůli čistě virtuálním členům, nelze vytvořit ani potomek. A to právě proto, že nelze vytvořit instanci rodiče. Kdyžtak si radši zkus někdy tohle v C++…A jaký by pak měly smysl třídy s čistě virtuálními členy?
interface
kteří danou metodu nereimplementujíTo mi tam chybělo.
Pokud nelze vytvořit rodič kvůli čistě virtuálním členům, nelze vytvořit ani potomek. A to právě proto, že nelze vytvořit instanci rodiče.mám pochopitelně na mysli takové potomky, které danou
pure virtual
neboli abstract
metodu neimplementují.
Kdyžtak si radši zkus někdy tohle v C++, protože C# řeší spoustu věcí za tebe narodzíl od C++.No děkuju za radu. Knížku začínáme v C# jsem otevřel teprv včera tak nevím jestli toho pro mě nebude příliš moc najednou.
Když se vytváří instance potomka, alokuje se prvně dost místa pro obě třídySkvělé, takže víš, že ta instance je jen jedna, a obsahuje data ze dvou tříd. (Tvrdils, že instance jsou dvě.) Postav si proti tomu tu delegaci, kde jsou opravdu instance dvě. (Bonus navíc: Ta ke které deleguji může navíc být libovolný potomek, to s děděním neuděláš).
totiž že se kód z rodiče "kopíruje" do potomka, tak by to znamenalo, že se musí zkopírovat i konstruktor. Ale kam by se do potomka asi zkopíroval?To víš že se skopíruje, hezky se nacpe do té jedné instance spolu s ostatníma věcma. Jak by ho jinak ten odvozenec mohl zavolat kdyby tam nebyl?
vyhodí tento chybový kód: error: invalid use of ‘Rodic::Rodic’Aha, takže tam přece jen je, jen ten překladač nám ho překvapivě nedovolí použít.
Čili s tvojí teorii bude něco špatně.Nic s ní špatně není. V binárním souboru je to samozřejmě tak, že každý kus kompilovaného kódu je jako funkce jen jednou (protože je to totéž tak nemá smysl to mít 10x - kompilátor není blbej). Ale pro všechny účely diskuse o čistotě kódu (DRY a spol) je to totéž jako když ten zdroják z Rodiče nacpu do Potomka a pak k tomu přidám ten zbytek. Možná ještě horší, protože to tak nebije do očí. A proto že to není vidět tak si spousta lidí myslí jak je to kůl a ve skutečnosti je to průser. Neříkám že to nemá využití, ale je potřeba opatrně a s rozvahou.
v tvojí metodě nelze udělat protected, čili takovou metodu, která nějak upravuje rodičovský objekt (jak to dělá nás nezajímá), ale přitom nechce dovolit tuto úpravu cizím objektům, pouze těm odvozeným.Jenže protected je nástroj pro dědění a zbytečně vytváří vazby a komplikuje strukturu programu. Tím, že bych se chtěl hrabat v útrobách cizího objektu tak znesvobodňuju tvůrce toho objektu v tom co může změnit. A nebo ho nutím dělat jeden interface navíc - jeden protected, jeden public. Je to zbytečné. Na zneužívání "protected" právě stojí a padá celé to paradigma dědění implementací. Dřív nebo pozdějc je v tom takovej guláš že už tou nejspodnější vrstvu nejde nikam pohnout, je to jako když je na stole na sobě 20 knih. A pak ten soft jde do kytek. "Protected" jsou 90. léta, to už je za náma.
Přečti si o tom něco a pak si promluvíme zas. A nebo aspoň poslouchej co říkaj ti co už jednou byli tam co seš ty těď.Právě ten zbytek světa (kromě Vás asi) to má nadefinované přesně takhle.Jasně
... a ve skutečnosti je to průser.V čem ten průser spočívá? U copy & paste to chápu (stejnou věc mám na dvou nezávislých místech), tady ne. Pořád tam ten kód je jen jednou, ve zdrojácích i ve výsledný binárce. Z hlediska logiky je tam víckrát, ale to svým způsobem nastane i při volání libovolné funkce.
V čem ten průser spočívá? U copy & paste to chápu (stejnou věc mám na dvou nezávislých místech), tady ne. Pořád tam ten kód je jen jednou, ve zdrojácích i ve výsledný binárce. Z hlediska logiky je tam víckrát, ale to svým způsobem nastane i při volání libovolné funkce.U copy paste je ten průser ne v tom, že tam ty věci jsou, ale že je musíte měnit. Když změníte (opravíte) jedno musíte si vzpomenout kde všude to ještě je a zopakovat změnu. U tohohle je to podobně. Když změníte něco v nadřazené třídě tak je možné že na to musíte uzpůsobit všechny potomky (tj. všude kde se ten kód promítne). Pokud je ta hierarchie složitá, bude z toho ripple effect a případně nemusíte být schopen to změnit vůbec, ať už ze strachu co všechno se rozbije nebo kvůli podpoře uživatelů té třídy. Další problémy jako nečitelnost kódu se vezou s tím. Při volání funkce je situace odlišná v tom, že funkce má relativně jednoduché rozhraní (hlavičku), které odděluje její tělo od zbytku světa. S vnitřkem si můžete dělat co chcete aniž by se muselo cokoliv venku měnit. Pak je samozřejmě otázka dodržení LSP, na kterou většina lidí kteří používají dědění na "sdílení kódu" kašle, ergo většinou nelze použít na 100% odvozené třídy všude tam kde se očekává předek. Tím jde jaksi celé OO kamsi. Dědění implementace je zkrátka bazmek, který jde paradoxně proti OO, naopak dědění interfaců krásně hraje dohromady s ostatními principy.
Při volání funkce je situace odlišná v tom, že funkce má relativně jednoduché rozhraní (hlavičku), které odděluje její tělo od zbytku světa. S vnitřkem si můžete dělat co chcete aniž by se muselo cokoliv venku měnit.Aha. Takže když v tomhle případě změním
int secti(int a, int b) { return (a+b); }na
int secti(int a, int b) { return (a*b); }tak to funkci
int zjistisoucet(int a, int b) { return secti(a, b); }nijak neovlivní? A teď ještě tu o karkulce.
Když změníte něco v nadřazené třídě tak je možné že na to musíte uzpůsobit všechny potomky (tj. všude kde se ten kód promítne).Ale to je problém mizerného designu té které třídy a analýzy problému.
naopak dědění interfaců krásně hraje dohromady s ostatními principy.interface je naprosto zbytečná kravina. Pokud po někom potřebuju, aby mi udělal třídu fungující se stejným rozhraním ale jiným backendem, tak mu bude na nic, že z interface ví, že funkce pocetRadku má dva parametry a vrací int. V takovém případě je potřeba dát k dispozici buď zdrojáky celé třídy nebo dokumentaci - a pak je zbytečné se patlat s nějakým interfejsem (ale zní to cool, to jo).
Ale to je problém mizerného designu té které třídy a analýzy problému.Ano a můžu dělat design stylem domek z karet a nebo můžu dělat design pořádně. Pokud použiju primitivní nástroj tak budu mít primitivní řešení. Můžete to analyzovat jak chcete ale nikdy nenavrhnete design přes dědění implementace dostatečně pružně.
V takovém případě je potřeba dát k dispozici buď zdrojáky celé třídy nebo dokumentaci - a pak je zbytečné se patlat s nějakým interfejsem (ale zní to cool, to jo).Můžete dát existující zdrojáky implementace té vlastnosti, ale nemusíte dávat zdrojáky všech uživatelů té vlastnosti. V tom je ten rozdíl.
Jasně. Jsem zvědav, kolik softwarových projektů potřebuje změnit funkci sečti aby místo součtu dělala násobení. Ten příklad je úplně mimo, ergo je mimo i závěr.Jasně, ten příklad byl mimo. Ale krásně ukazuje, že si taky s funkcí nemůžete dělat co chcete (pokud na ní něco závisí). Stejně jako s metodou v nějaké třídě :)
Můžete dát existující zdrojáky implementace té vlastnosti, ale nemusíte dávat zdrojáky všech uživatelů té vlastnosti. V tom je ten rozdíl.To nemusím ani v jednom případě - protože ta metoda (pokud bude napsaná správně) se bude navenek chovat stejně jako ta podle které to píšu. Pokud se navenek nemá chovat stejně, pak mám smůlu, a musím upravit zdrojáky všech dalších metod, které s tou danou metodou pracují. Bez ohledu na to, jestli tam je dědičnost jen jedno nebo padesátivrstvá.
Ale to je problém mizerného designu té které třídy a analýzy problému.A proto je lepší se problematickým konstrukcím raději úplně vyhnout.
No děkuju za radu. Knížku začínáme v C# jsem otevřel teprv včera tak nevím jestli toho pro mě nebude příliš moc najednou.?? Tohle není myšleno vážně, že ne...?
Skvělé, takže víš, že ta instance je jen jedna, a obsahuje data ze dvou tříd. (Tvrdils, že instance jsou dvě.)Ty instance jsou dvě, to, že jsou vedle sebe v paměti ještě neznamená, že to je jedna instance. Kdyby to byla jedna instance, stačilo by jedno volání konstruktoru.
To víš že se skopíruje, hezky se nacpe do té jedné instance spolu s ostatníma věcma.Znovu: Co tě vede k myšlence, že ta instance je jedna? To že data jsou "vedle sebe" v paměti? Znamená to snad, že když napíšu
SomeClass classes[2]
, tak je to taky jedna instance? V tomto případě se taky alokuje celé místo pro obě dvě třídy najednou, a je to jedna instance? Pochopitelně že ne. Proč? Protože se dvakrát volal konstruktor.
Jak by ho jinak ten odvozenec mohl zavolat kdyby tam nebyl?Úplně stejně jako když bys instanci rodiče vytvořil ručně.
Aha, takže tam přece jen je, jen ten překladač nám ho překvapivě nedovolí použít.Pochopitelně, že "tam je", kde "tam" znamená v programu. Překladač jasně řekl, kde konkrétně je - to je ta část před operátorem
::
.V binárním souboru je to samozřejmě tak, že každý kus kompilovaného kódu je jako funkce jen jednou (protože je to totéž tak nemá smysl to mít 10x - kompilátor není blbej). Ale pro všechny účely diskuse o čistotě kódu (DRY a spol) je to totéž jako když ten zdroják z Rodiče nacpu do Potomka a pak k tomu přidám ten zbytek.Ne to teda rozhodně není totéž, kdybys skutečně nakopíroval zdroják Rodiče do Potomka a přidal další věci, pak by se stalo přesně to, že by skutečně v programu ten kód byl dvakrát (případně vícekrát), protože po nakopírování by ten kód patřil do prostoru každého potomka a měl by tudíž v binárce jiná jména symbolů. Ježiš, ty fakt vůbec nevíš...
Jenže protected je nástroj pro dědění a zbytečně vytváří vazby a komplikuje strukturu programu. Tím, že bych se chtěl hrabat v útrobách cizího objektu tak znesvobodňuju tvůrce toho objektu v tom co může změnit.1)
protected
rozhodně neznamená "hrabat se v útrobách cizího objektu"Přečti si o tom něco a pak si promluvíme zas. A nebo aspoň poslouchej co říkaj ti co už jednou byli tam co seš ty těď.
Přečti si o tom něco a pak si promluvíme zas.Já si klidně rád "o tom" něco přečtu. Zajímalo by mě ale, jestli vůbec jsi schopen říct, co přesně má být to "o tom".
Znovu: Co tě vede k myšlence, že ta instance je jedna? To že data jsou "vedle sebe" v paměti? Znamená to snad, že když napíšu SomeClass classes[2], tak je to taky jedna instance? V tomto případě se taky alokuje celé místo pro obě dvě třídy najednou, a je to jedna instance? Pochopitelně že ne. Proč? Protože se dvakrát volal konstruktor.Vede mě k tomu to, že se zavolalo jen jednou "new", respektive že se zavolá jen jednou "delete". Nelze z toho rodiče+předka oddělit jen toho předka. Prostě je to jedna věc, která najednou vznikne a zanikne a vede na ní jeden odkaz. Když budu mít pole tak tam jsou ty věci dvě to je vidět ne? BTW i když bych přijal tvou hypotézu že jsou dvě instance (rodič a předek zvlášť) tak v případě delegace by teda byly tři a víc (rodič a předek a pak ještě jednou ten komu deleguju, tj. minimálně jedna navíc). Takže oklikou k původnímu problému: není to to samé jako dědění.
Podívej, to, že se naučíš frikulínské pojmy a obecné poučky z OOP/obecného programování (DRY, paradigma, interface (ten ti mimochodem stále uniká), implementace (jakybsmet),... atd..) z tebe nedělá odborníkaJo, ta fakt sedla. Poučkový argument, už jsem se ho nemohl dočkat. Tys toho určitě hodně přečetl i zažil, nech mě hádat, dva školní úkoly a když si něco nevěděl tak ses zeptal kamaráda co tě všechno naučil. Tudíž víš všechno desetkrát líp než zbytek světa, aniž bys s ním zatím přišel do kontaktu.
(neříkám, že já jsem odborník, no ale...). Bez znalosti toho, jak ty věci skutečně fungují, jsou to jen prázdné pojmy...No právě tak se to jdi naučit a pak se vrať.
Já si klidně rád "o tom" něco přečtu.Fajn, můžeš začít s knihou Design Patterns. Tam se o tom co je interface a o nevýhodách dědění pojednává hned v prvních dvaceti stranách. Připomínám že ta kniha je z roku 1995, takže dneska tvoří dost solidní a uznávanou klasiku. Nebo by ti možná stačilo pouze vytáhnout tu hlavu z prdele na vzduch a dát si na google "interface class inheritance" nebo "interface implementation inheritance" apod.
Vede mě k tomu to, že se zavolalo jen jednou "new", respektive že se zavolá jen jednou "delete".Zaprvé:
new
a delete
se nevolá, nejsou to funkce, jsou to operátory. (Proč ti vlastně poskytuju tohle celé školení zdarma?)new
(resp. delete
) určije počet instancí" zase jako tvoje předchozí tvrzení dostane vede ke sporu:
V C#, který ti povolí vytvořit objekt jen na heap (afaik), to není moc dobře vidět (C# jde cestou "ignorance is bliss"), ale například v C++ můžu vytvořit dva objekty (jeden na stacku a druhý na heap) takto:
Objekt jeden;
Objekt* druhy = new Objekt;
(poznámka: oba jsou to v této chvíli plnohodnotné a plně vytvořené instance objektů)new
bylo použito jednou (a delete
bude taky použito jednou), čili podle tvé hypotézy tam je jen jedna instance. Každý ale vidí, že jsou dvě, druhá je na stacku.new
, takto:
Objekt* dva;
dva = new Objekt[2];
Nelze z toho rodiče+předka oddělit jen toho předka. Prostě je to jedna věc, která najednou vznikne a zanikne a vede na ní jeden odkaz.Samozřejmě, nic z této věty já nerozporuju. Nemám důvod snažit se je oddělit. Pochopitelně, že to je jedna "věc", která vzniká a zaniká najednou. Ta věc ale přesto obsahuje dvě instance (nebo více podle počtu předků).
BTW i když bych přijal tvou hypotézu že jsou dvě instance (rodič a předek zvlášť) tak v případě delegace by teda byly tři a víc (rodič a předek a pak ještě jednou ten komu deleguju, tj. minimálně jedna navíc).Ano, přesně tak to je, právě proto je zbytečné vytvářet ručně další instanci, když už ti dědičnost jednu vytvořila. A naopak, pokud si teda chceš nutně vytvářet instanci ručně, nemá smysl dědit.
Fajn, můžeš začít s knihou Design Patterns. Tam se o tom co je interface a o nevýhodách dědění pojednává hned v prvních dvaceti stranách. Připomínám že ta kniha je z roku 1995, takže dneska tvoří dost solidní a uznávanou klasiku.Ok, tak fajn, až se naučím pořádně jak fungují objekty, dědičnost a další principy - tím myslím jak skutečně fungují, pak se zaměřím na obecnější postupy, protože budu vědět, o čem se tam mluví. Budu mít stavební bloky a z těch se bude dát něco stavět.
Ano, přesně tak to je, právě proto je zbytečné vytvářet ručně další instanci, když už ti dědičnost jednu vytvořila.Zbytečné to není, protože ta druhá instance je nezávislá na mně a je zapouzdřená. Proto taky ten druhý příklad funguje a ten první ne. Jo vlastně, tohle jsem už říkal... Hele, ten princip který jsem naznačil na těch příkladech prostě funguje a je obecně známý a používaný. Můžeš s tím nesouhlasit ale to je vše co s tím můžeš. Tím že se opřeš do mě a napadneš moje znalosti s tím nepohneš.
Ok, tak fajn, až se naučím pořádně jak fungují objekty, dědičnost a další principy - tím myslím jak skutečně fungují, pak se zaměřím na obecnější postupy, protože budu vědět, o čem se tam mluví.Já si myslím že se jedná o prohlubování znalostí v nekonfliktních směrech, které lze provést paralelně.
Urážky a argumenty ad hominem jsou pod úroveň mou a doufám že i tvou, nebudu na ně reagovat ani teď ani kdykoli nadále v diskusi.Je příjemné to slyšet od tebe.
Zbytečné to není, protože ta druhá instance je nezávislá na mně a je zapouzdřená.Jasný, ale zbytečné to je v případě, že používáš dědičnost. Jestli cheš vytvářet vlastní instanci ručně, tak prosím, ale pak se vykašli na dědičnost, protože ti je v tý chvíli nahouby... akorát zabírá pamět.
Proto taky ten druhý příklad funguje a ten první ne.Nevidím důvod, proč by to nemělo s dědičností fungovat. Kdyžtak uveď konkrétní problémy, pokud se ti chce...
Můžeš s tím nesouhlasit ale to je vše co s tím můžeš.Je se nesnažím nesouhlasit tím, že to funguje, jasně že to fungje a dá se to používat.
Tím že se opřeš do mě a napadneš moje znalosti s tím nepohneš.Sorry, ale já prostě nemám rád, když někdo šíří bludy. Přístup "nevím jak dědičnost funguje, ale v chytré knížce psali, že je špatná, a tak ji tedy zkritizuju" mě zkrátka dokáže nadzvednout ze židle. Jinak bych rozhodně neměl potřebu napadat nečí (ne)znalost. Jinak moje znalosti jsou pochopitelně taky omezené.
Jestli cheš vytvářet vlastní instanci ručně, tak prosím, ale pak se vykašli na dědičnost, protože ti je v tý chvíli nahouby... akorát zabírá pamět.Vždyť jsem psal, že optimální přístup by byl udělat společný interface, ale že jsem to tak nechal proto a proto (viz výše). A na houby ta dědičnost není, protože dědím právě ten interface. A tam je to důležité. Že to zabírá paměť? Kolik? Doufám že se teď nezačnem dohadovat o syndromu předčasné optimalizace
Nevidím důvod, proč by to nemělo s dědičností fungovat. Kdyžtak uveď konkrétní problémy, pokud se ti chce...Vždyť ten problém máš napsaný celou dobu v tom grand-parent příspěvku.
snažil jsem se poukázat, že ta delegace není od té dědičnosti tak zásadně rozdílná.To jsi se snažil špatně/zbytečně, protože v tom je zásadní rozdíl.
Pro zajímavost existuje příklad z praxe a to je knihovna Qt.Uh, frameworky a obzvlášť GUI frameworky jsou dost specifická kapitola. Mísí se v tom samozřejmě ta dědičnost (tak se to dělalo dřív a teď z toho nelze couvnout), která má ale hodně vybroušený protected-interface tak, aby se s tím dalo aspoň nějak hýbat - z toho je vidět, že velká část dědění se za posledních 10 let přesunula směrem k dědění interface. Samozřejmě je v tom hodně umění stran organizace těch tří prdelí tříd a v neposlední řadě spousta quirků pro konkrétní jazyk a platformu. Obdivuhodné to ano, ale ne nutně hodno následování do posledního písmene. Porovnej si to třeba s tím jak funguje Tk.
Konkrétně, pokud si dobře vzpomínám, především na místech, kdy platformě abstraktní třída má platformě specifické delegáty.Aniž bych do toho koukal tohle mi zní spíš jako implementace vzoru Bridge, který se v těchto situacích používá. A tam nejde o delegáty ale o jakýsi překlad mezi modelem toho GUI a modelem platformy (X a spol.)
Takže každý přístup má podle mě svoje místo.Jasně, ale věř mi, že dědění za účelem přepoužití implementace moc míst nemá
Přístup "nevím jak dědičnost funguje, ale v chytré knížce psali, že je špatná, a tak ji tedy zkritizuju" mě zkrátka dokáže nadzvednout ze židle.Obdivuju rychlost s jakou dokážeš někoho posadit do role blázna hodného veřejného lynčování. Možná by ti prospělo si na tu židli sednout nějak pevněji.
Že to zabírá paměť? Kolik? Doufám že se teď nezačnem dohadovat o syndromu předčasné optimalizacePokud dědíš třídu se samými pure virtual prvky (i.e. interface), tak to pochopitelně žádnou pamět nezabere. Pokud bys dědil nějakou datovou strukturu a zároveň ji delegoval, pamět by to zbytečně zabíralo. Z toho kódu, co jsi napsal, to vypadlo, jakože dědíš i datovou strukturu. Jestli to bylo myšleno jako dědění interface, pak je vše ok...
Vždyť ten problém máš napsaný celou dobu v tom grand-parent příspěvku.Jestli myslíš ten kód v kořenovém příspěvku, tak ten je prostě špatně napsaný, není to problém dědičnosti.
To jsi se snažil špatně/zbytečně, protože v tom je zásadní rozdíl.No, tak to se pak můžeme leda tak hádat o to, který rozdíl je zásadní a který ne. Ty rozdíly jsou docela hezky popsané například tady.
Mísí se v tom samozřejmě ta dědičnost (tak se to dělalo dřív a teď z toho nelze couvnout), která má ale hodně vybroušený protected-interface tak, aby se s tím dalo aspoň nějak hýbat - z toho je vidět, že velká část dědění se za posledních 10 let přesunula směrem k dědění interface.[Citation needed]
Porovnej si to třeba s tím jak funguje Tk.To se dost dobře nedá porovnat - Tcl/Tk obsahuje minimum featur, je mnohem méně komplexní.
Jasně, ale věř mi, že dědění za účelem přepoužití implementace moc míst nemáHm, po celé té diskusi nejsem zrovna nakloněn ti něco jen tak věřit.
Obdivuju rychlost s jakou dokážeš někoho posadit do role blázna hodného veřejného lynčování.Jj, jsem prostě zloduch k pohledání A to jsem ti ještě ani neřekl, ať "vytáhneš hlavu z prdele" nebo jak to bylo
Jestli myslíš ten kód v kořenovém příspěvku, tak ten je prostě špatně napsaný, není to problém dědičnosti.Proč myslíš že je špatně napsaný?
Ty rozdíly jsou docela hezky popsané například tady.Tak tam je těch rozdílů popsaných docela dost, což poukazuje na to, že v tom skutečně velký rozdíl je. Ten rozdíl nemusí být implementační ale spíš ve způsobu práce. Programovací techniky jsou o tom jak s nima pracují lidé nikoliv jak se to pak přechroustá pro počítač. Plus je tam doporučení
Use composition when you can, private inheritance when you have to. Normally you don't want to have access to the internals of too many other classes, and private inheritance gives you some of this extra power (and responsibility). But private inheritance isn't evil; it's just more expensive to maintain, since it increases the probability that someone will change something that will break your code.A to hovoří doufám dost jasně.
Proč myslíš že je špatně napsaný?Není to zřejmé na první pohled?
public
a virtual
, čili může být v potomcích reimplementována, nemůžu ji logicky použít jinde v implementaci.private
funkci pojmenovanou třeba m_add(const Object& o)
(podle toho, jakou používáš konvenci), která by obsahovala vlastní implementaci přidání objektu, a dále dvě public virtual
funkce, které by ji používaly.
Programovací techniky jsou o tom jak s nima pracují lidé nikoliv jak se to pak přechroustá pro počítač.Důležité je oboje.
A to hovoří doufám dost jasně....o private inheritance, jistě.
Správné řešení by bylo mít private funkci pojmenovanou třeba m_add(const Object& o) (podle toho, jakou používáš konvenci), která by obsahovala vlastní implementaci přidání objektu, a dále dvě public virtual funkce, které by ji používaly.Pokud pak udělám potomka s veřejnou virtuální metodou
addTwo
, která do kolekce přidává dva objekty, tak z ní nesmím volat add
ani addMany
.
Pokud pak udělám potomka s veřejnou virtuální metodouProč bys nemohl?addTwo
, která do kolekce přidává dva objekty, tak z ní nesmím volatadd
aniaddMany
.
Tím se metoda addTwo
sváže s metodou, pomocí níž je implementována BÚNO add
. Což znamená, že pokud v dalších potomcích změníte chování add
tak se mění i chování addTwo
(mj. chování addMany
zůstává stejné).
A přesně to je problém příkladu, který uvedl deda.jabko. Navíc pokud ta hierarchie bude hlubší, bude tam více metod a potomci budou volat různé metody předků, tak se to bude komplikovat ještě více.
Tím se metoda addTwo sváže s metodou, pomocí níž je implementována BÚNO add. Což znamená, že pokud v dalších potomcích změníte chování add tak se mění i chování addTwo (mj. chování addMany zůstává stejné).Přijde mi, že hledáš problém, kde není.
class Rodic
{
private:
void _add(int x);
public:
virtual void add(int x);
};
class Potomek: public Rodic
{
public:
virtual void addTwo(int x);
};
class Potomek2: public Potomek
{
public:
virtual void add(int x);
};
void Rodic::_add(int x)
{
cout << x << endl;
}
void Rodic::add(int x)
{
_add(x);
}
void Potomek::addTwo(int x)
{
Rodic::add(x);
add(x);
}
void Potomek2::add(int x)
{
cout << "chyba" << endl;
}
int main()
{
Potomek2 p;
p.addTwo(3);
return 0;
}
3
reimplementovano
Potomek2::add(int x)
má pochopitelně vypisovat "reimplmentace", ne "chyba", ale ono je to v zásadě jedno...
Přijde mi, že hledáš problém, kde není.Jen jsem chtěl říct, že použitím soukromé metody v rodiči, která odvede práci za
add
i addMany
, se problém nevyřeší a stejně musíte být v potomcích velmi opatrný.
Potomek2
) musím koukat na implementaci předků.
Pak nechápu, proč v metodě void Potomek::addTwo(int x)
voláte add
rodiče a pak ještě add
objektu. Je to dost nezvyklé a lidem, co z vaší třídy dědí, to může způsobit nepříjemnosti.
Pak nechápu, proč v metodě void Potomek::addTwo(int x) voláte add rodiče a pak ještě add objektu.To byl jen příklad, aby bylo vidět, jaký bude rozdíl když použiju jednu nebo druhou metodu. Ve skutečnosti bych buď zavolal dvakrát
add
rodiče, nebo dvakrát add
na vlastní instanci, podle toho, jestli chci umožnit potomkům zasahovat do implementace addTwo()
.
Potomek2
nemohl měnit (a tedy nemohl rozbít) implementaci addTwo()
, pak použiju v implementaci addTwo()
dvakrát add()
a vše funguje jak má...
pak použiju v implementaci...dvakrát add() rodiče a vše funguje jak má...addTwo()
dvakrátadd()
a vše funguje jak má...
Pokud je tvým požadavkemAno je to můj požadavek a tohle je správné řešení (tím řešením je, že virtuální metody se smí volat, ale jen rodičovské a soukromé).
class A def add(x) puts "A.add" _add(x) end def addMany(xs) puts "A.addMany" xs.each { |x| _add(x) } end private # Podle vašeho návodu řeším přidávání pomocí privátní funkce. def _add(x) puts "A._add (pridavam #{x})" end end class B<A def addTwo(x, y) puts "B.addTwo" add(x) add(y) end end class C<B attr_reader :size def initialize @size = 0 end def add(x) puts "C.add" super(x) @size += 1 end def addTwo(x, y) puts "C.addTwo" super(x, y) @size += 2 end def addMany(xs) puts "C.addMany" super(xs) @size += xs.size end end c = C.new c.addTwo(1, 2) puts "Velikost kolekce #{c.size}"Výstup je
C.addTwo B.addTwo C.add A.add A._add (pridavam 1) C.add A.add A._add (pridavam 2) Velikost kolekce 4Problém je, že abych to napsal správně, musím vědět, kde se co volá, a tudíž nahlédnout do implementací předků.
Takže ses v podstatě snažil dokázat, že pomocí dědičnosti lze napsat špatný kód?Ne. Chtěl jsem ukázat, že to vaše řešení, kde se používá soukromá metoda, ničemu nepomůže. A nejde o to, jestli ten kód používající dědičnost zrovna teď funguje, ale o to, že je ho možné velmi snadno rozbít.
Dále pak pokud nadefinuju metodu add() jako public a virtual, čili může být v potomcích reimplementována, nemůžu ji logicky použít jinde v implementaci....což mě omezuje v tom, jak si implementuju svoje public funkce.
Správné řešení by bylo mít private funkci pojmenovanou třeba m_add(const Object& o) (podle toho, jakou používáš konvenci), která by obsahovala vlastní implementaci přidání objektu, a dále dvě public virtual funkce, které by ji používaly.... tím neděláš nic jiného než se se snažíš schovat co nejvíc implementace za něco jednoduššího kde se toho nemůže tolik rozbít. Což je krok směrem k interfacům, akorát že tady to je tak na půl cesty a IMHO to má to horší z obou světů. Pokud ta privátní funkcionalita bude totálně odstíněna od zbytku objektu a veřejné metody budou jen delegovat privátním tak jsi v podstatě jen udělal zabudovaný objekt ke kterému máš přístup jen ty. Není ale důvod to takhle držet, ta privátní věc se klidně může vyloupnout ven jako samostatná třída se svým vlastním interface. Pak přestane mít smysl udržovat objekt který pouze deleguje všechny operace tomu druhému a de facto dospěješ k tomu řešení co jsem napsal já.
Každý extrémní názor přináší akorát nepříjemnosti.Ano.
Programátor, který píše kód s důrazem na maximální efektivitu na HW, ale píše návrhovej bordel, si podřezává větev pod zadkem. Programátor, který se soustředí pouze na programovací paradigmata a návrhové vzory a nezajímá ho, jak se kód fyzicky provádí, zase napíše kód, který bude krásně čitelní lidmi, ale na stroji bude neefektivní.Důležitý rozdíl je ten, že programátor který dělá dobrý design otevírá cestu k pozdější optimalizaci ale naopak to neplatí. Proto se to řeší v tomhle pořadí.
...což mě omezuje v tom, jak si implementuju svoje public funkce.Nikdo ti nezakazuje napsat to špatně. Jestli chceš psát dobrý kód, pak "nedělej to špatně" mi nepřijde jako omezení.
... tím neděláš nic jiného než se se snažíš schovat co nejvíc implementace za něco jednoduššího kde se toho nemůže tolik rozbít. Což je krok směrem k interfacůmJasně, ale já třeba můžu chtít mít nějakou piblic imeplementaci u které můžu chtít nabídnout potomkům, aby ji změnili. Prostě to je to, co už jsem říkal kolegovi výše: chce to si prvně rozmyslet, co vlastně chceš v konkrétním případě naprogramovat, a pak podle toho použít správný návrhový vzor.
Pak přestane mít smysl udržovat objekt který pouze deleguje všechny operace tomu druhému a de facto dospěješ k tomu řešení co jsem napsal já.Ten objekt může mít nějaké další vlastnosti, metody, atd. u kterých můžu chtít zase něco jinýho, kde dědění implementace bude vhodnější...
Důležitý rozdíl je ten, že programátor který dělá dobrý design otevírá cestu k pozdější optimalizaci ale naopak to neplatí.To se takhle obecně imho nedá říct. Existují low-level optimalizace, které se dají nasadit nezávisle na designu a stejně tak jistě existují příklady designu, který nějaké ompimalizace znemožňuje/zhoršuje. Implementovat nějaký design a pak teprv se dívat, co na to řekne stroj, to mi nepřijde moc chytrý. Lepší brát v úvahu jak nároky stroje tak nároky designu a vytvořit co nejideálnější kompromis.
Prostě to je to, co už jsem říkal kolegovi výše: chce to si prvně rozmyslet, co vlastně chceš v konkrétním případě naprogramovat, a pak podle toho použít správný návrhový vzor.Některé vzory neškálují a poloabstraktní třídy mezi ně patří. Vzory se dělají na to, abych si mohl "prvně rozmyslet" jen 5 věcí, které udržím, ať už má program deset nebo milión řádků. Dědění implementací tuhle vlastnost nemá.
Mně přijde, že se furt snažíš najít nějaký černobílý princip. Tohle ale není náboženství, tady není dobrý mít nějaký dogmata jako "Dědičnost jest dílo ďáblovo, vždy delegaci používati budeš".Dogma (podporované literaturou a zkušenostmi) je "dědičnost použít jen ve speciálním případě kdy už nic lepšího nemáš", a já se ti snažím jen říct, že všechny příklady které tu zatím prošly nejsou speciální a mají mnohem čistší řešení mimo dědičnost. Speciální je maximálně ten GUI toolkit ale ten to má spíš jako dědictví z 90. let.
Existují low-level optimalizace, které se dají nasadit nezávisle na designu a stejně tak jistě existují příklady designu, který nějaké ompimalizace znemožňuje/zhoršuje.Opět zvláštní případy čítající jednotky kusů v běžném provozu.
Vzory se dělají na to, abych si mohl "prvně rozmyslet" jen 5 věcí, které udržím, ať už má program deset nebo milión řádků.Souhlasím, to ale není argument proti dědičnosti...
a já se ti snažím jen říct, že všechny příklady které tu zatím prošly nejsou speciální a mají mnohem čistší řešení mimo dědičnost.Jo tak čistší jo?
Speciální je maximálně ten GUI toolkit ale ten to má spíš jako dědictví z 90. let.Jestli narážíš na Qt, tak jeho nejnovější řada (4) je z roku 2005 (měnila se dost architektura, nebyl by býval problém změnit design patterns) a není to zdaleka jen GUI toolikt, pomocí dědičnosti (ale i delegace) se tam řeší spousta dalších věcí...
Opět zvláštní případy čítající jednotky kusů v běžném provozu.[Citation needed]
Dejme tomu, že zmiňovaný kontejner má třeba 100 nějakých metod ...Asi máme jiný názor na definic pojmu "čistý kód".To asi jo. :)
Dejme tomu, že zmiňovaný kontejner má třeba 100 nějakých metod a ty potřebuješ přidat to počítadlo. V případě delegace budeš muset všech 100 metod opsat, ale jejich implementace bude dummy.Pokud se budete řídit tím, co jsem vám před chvílí odsouhlasil nahoře, tak stejně musíte upravit všechny metody, co mění počet prvků v kontejneru. A pokud se tím řídit nebudete, tak se musíte podívat na implementaci předků, abyste věděl, jak to naprogramovat. Další problém dědičnosti je, že pokud mám potomka, co má počítadlo a v předkovi implementuji virtuální metodu, co mění počet prvků v kolekci, kterou v tom potomkovi neošetřím, tak se mi může stát, že potomek už počítá špatně resp. nepočítá (a platí, že když se řídíte tím, co jsem vám nahoře odsouhlasil, tak se to stane). Kdybych tu metodu přidal do rozhraní a dědil jen z rozhraní, tak mě kompilátor seřve, že to rozhraní neimplementuju. Kdybych použil kompozici, tak tu novou metodu tam nemám a nic se neděje.
Navíc to zanáší stack, hlavně pokud budou mít ty metody hodně parametrůPlatí pro hloupé kompilátory, rozumný kompilátor tohle inlinuje.
>Dejme tomu, že zmiňovaný kontejner má třeba 100 nějakých metodTenhle problém lze vyřešit třeba tak, že rozhraní pro kontejner bude mít velmi málo metod a zbytek funkčnosti bude implementován ve funkcích mimo kontejner pomocí těch pár metod z rozhraní pro všechny kontejnery stejně. Tohle řešení je typické pro Haskell, kde typová třída má málo funkcí a zbylé funkce jsou poskládány z toho mála. V OCamlu jde stejná věc udělat pomocí funktorů: udělá se signatura s mnoha funkcemi a funktor, co jako parametr dostane modul a pár funkcemi, a vrací modul se signaturou s mnoha funkcemi. Navíc si stále mohu celý modul udělat bez tohoto funktoru.
stejně musíte upravit všechny metody, co mění počet prvků v kontejneru.Jj.
Další problém dědičnosti je, že pokud mám potomka, co má počítadlo a v předkovi implementuji virtuální metodu, co mění počet prvků v kolekci, kterou v tom potomkovi neošetřím, tak se mi může stát, že potomek už počítá špatně resp. nepočítáKdyž ji neošetřím, jistě...
Kdybych použil kompozici, tak tu novou metodu tam nemám a nic se neděje.Jo takhle to myslíš, jako že když na ni zapomenu, nic se neděje... Ano, to je jistě výhoda kompozice.
Proč myslíš že je špatně napsaný?V první řadě je špatně napsaný proto, že nefunguje, nedělá to, co se od něj očekává. A pokud by existovaly nějaké testy (což by v takovém případě asi měly), neprošel by jimi. V druhé řadě je špatně už ten návrh. Je dobré se zeptat, čeho je vlastnost ten počet (
int count
)? Je to vlastnost té kolekce? Nebo vlastnost nějakého jiného objektu, který na tuto kolekci obsahuje odkaz?
V první řadě je špatně napsaný proto, že nefunguje, nedělá to, co se od něj očekává.To bych řekl že je jeho existenční pointa
Je dobré se zeptat, čeho je vlastnost ten počet (int count)? Je to vlastnost té kolekce? Nebo vlastnost nějakého jiného objektu, který na tuto kolekci obsahuje odkaz?Z toho kódu je vidět, že je to vlastnost toho Advanced třídy.
Z toho kódu je vidět, že je to vlastnost toho Advanced třídy.Tak znovu a polopatičtěji: je dobré se zeptat, čeho je reálně vlastnost ten počet, aneb čeho by měl být v tom kódu. Ta vlastnost (počet) přísluší té kolekci – ne nějakému objektu, který na ni odkazuje. Vést si takové počítadlo někde bokem (kam nepřísluší) je dost ošemetné. Je to jako kdybych stál před barákem, před jedním vchodem, a dělal si čárky, kolik přišlo lidí. To je špatně, pokud je tam ještě jeden jiný vchod, kterým taky chodí lidé. Ten člověk, který dělá čárky, musí prostě stát někde uvnitř, třeba před výtahem, ke kterému směřují lidi z obou (všech) vchodů. Jádro tohoto problému je v návrhu a logickém uvažování – ne v nějakém akademickém sporu, jestli je lepší dědičnost nebo skládání.
Ta vlastnost (počet) přísluší té kolekci – ne nějakému objektu, který na ni odkazuje. Vést si takové počítadlo někde bokem (kam nepřísluší) je dost ošemetné.Co když to ta kolekce neumí a zdrojový kód nelze měnit?
Je to jako kdybych stál před barákem, před jedním vchodem, a dělal si čárky, kolik přišlo lidí. To je špatně, pokud je tam ještě jeden jiný vchod, kterým taky chodí lidé.Nemám problém si ošetřit všechny situace které potřebuju já využít (na to jsou ty testy), jak to používá někdo jiný mě nemusí zajímat.
A kdo používá dědění na "sdílení společných částí kódu" v podstatě jen schovává copy/paste za honosný ksicht.Mně spíš jako „copy/paste“ přijde, když musím opsat všechny metody, místo abych je podědil po předkovi. Když v předkovi (ve skutečnosti to předek není, protože nedědíme, ale skládáme) přibude metoda, musím ji dopsat do všech potomků, když metoda ubude nebo se změní její hlavička, musíš opět provést změny všude. Typická vlastnost „copy+paste“ kódu.
Když v předkovi (ve skutečnosti to předek není, protože nedědíme, ale skládáme) přibude metoda, musím ji dopsat do všech potomků, když metoda ubude nebo se změní její hlavička, musíš opět provést změny všude.To je pravda, ale chybí ti k tomu ta perspektiva, že se to děje jen při změně interface. Pokud změníš způsob zacházení s objektem, tak očekávej, že musíš hrábnout taky do klientského kódu. Dokonce je fajn, že to bez toho ani nedovolí přeložit, to abys ty změny vychytal všechny. Oproti tomu...
Typická vlastnost „copy+paste“ kódu.... typická vlastnost copy+paste, dědění implementace, těsných vazeb a obecně špatného návrhu je, 1) že změny mimo objekt musíš dělat i při změně implementace objektu a 2) často nejseš na vše upozorněn překladačem a hrozí riziko zanesení chyb. ("Nehrabej na to, kdoví co se tím rozesere" je typický popis tohoto symptomu.)
To je pravda, ale chybí ti k tomu ta perspektiva, že se to děje jen při změně interface.Hmm, a co třeba takováhle situace: V jedné metodě, kterou používám v třeba 50 třídách, se najde (třeba i bezpečnostní) chyba. Pokud jí mám poděděnou (tedy v kódu se vyskytuje fyzicky jen jednou) není co řešit, opravím a jede se dál. Pokud ta metoda je copy+paste ve všech třídách (a někde možná i trochu pozměněná), musím to opravit všude. Pokud na nějakou zapomenu, je průšvih. Ideální, co?
Tenhle příklad není asi úplně dobrý. Já nevím jak si to konkrétně představujete.Mně přijde výtečný. Co třeba zpracování dat z HTTP serverů? Jen příklad. Potřebuju v programu na požádání vytvářet specializované objekty, které udržují data konkrétních datových zdrojů (v podstatě slouží jako cache). Každý objekt je implementován trochu jinak, tedy ke každému mám třídu, která slouží jako definice jeho datových položek a operací nad nimi. Každá třída musí implementovat mimo jiné natažení dat z HTTP serverů do vlasntních datových struktur. Tedy mám dvě možnosti. 1) Použít pouze interface, tedy použít copy & paste, abych měl pro všechny třídy implementované stažení dat. 2) Použít společného předka, který definuje společné datové struktury a operaci natažení dat. Ta operace bude pravděpodobně u všech zdrojů téměř stejná (max ojedinělé výjimky). Tedy není potřeba ji psát (kopírovat) tolikrát, stačí ji parametrizovat. A teď, najde se bezpečnostní chyba, třeba blbě udělaný buffer (ona to nemusí být vždy jen bezpečnostní chyba). Budu hledat na jednom místě a opravovat na jednom místě (varianta 2) nebo budu muset projít všechny třídy a opravovat to po jedné (varianta 1)?
Nebo ta metoda má nějaké vazby na pole a další metody těch tříd. Pak v závislosti na míře provázanosti a velikosti změny budete muset upravovat i těch 50 odvozených tříd, aby byly s novou verzí kompatibilní.Pokud zblbnu práci s bufferem, není jediný důvod měnit odvozené třídy.
Každá třída musí implementovat mimo jiné natažení dat z HTTP serverů do vlasntních datových struktur. Tedy mám dvě možnosti.Tady máš asi tak milión možností; bohužel, ty dvě, které jsi jmenoval, jsou bezkonkurenčně nejhorší.
Tak ona je tu možnost vyšoupnout tu společnou funkcionalitu do nějaké jiné třídy místo do společného předka a pak použít kompozici.To podle mě nebude fungovat, v případě, že třídu vybírám například na základě URL (což je celkem logický požadavek, protože některé url ukazuje na článek, jiné třeba na seznam článků). Tedy mám-li funkci, která vrací nově vytvořený objekt článku či seznamu článků, při kompozici nemám obecně jednoznačně definovánu pozici vloženého objektu. Pravda, při pečlivém rozvržení toho jde ve statickém jazyce docílit, v některém z dynamických jazyků je to dotaz na slovník name-value hodnot, ale nic z toho mi nepřijde ani čistší ani elegantnější než společnou pozici společných dat zdědit.
Toho kopírovaného kódu bude méně, ale přece nějaký bude – pořád je to vlastně to „copy&paste“ protože instanciace a volání nějaké té knihovní třídy a metody je nějaký kód, byť třeba jen jeden dva řádky – ale je to duplicita, která se objeví ve všech „potomcích“ (takže v případě nějaké změny je potřeba přepisovat všude).Možná mi něco nedochází, ale já v tom ani copy&paste, ani zbytečnou duplicitu nevidím. Snad jediné, co má smysl kopírovat je hlavička funkce, ale tu bych tam musel mít v každém případě, přecejenom je to několik funkcé stejného rozhraní, ale různé implementace.
Co třeba zpracování dat z HTTP serverů? Jen příklad. Potřebuju v programu na požádání vytvářet specializované objekty, které udržují data konkrétních datových zdrojů (v podstatě slouží jako cache).Dej ten příklad do nějakého kódu, který vystihuje tu pointu.
průměrný i mírně podprůměrný programátor má dostatečnou představivost na to, aby mé předchozí vysvětlení pochytil či převedl na ukázku kóduSuper, tak to udělej :)
přibude metoda, musím ji dopsat do všech potomkůA proč? Vždyť tu novou metodu tam mít nemusím.
když metoda ubude nebo se změní její hlavička, musíš opět provést změny všude.V tomto případě musím upravit všechna místa, kde jsem tu metodu používal. Tedy i místa mimo toho „nepotomka“. Při dědění: Při změnách v implementaci předka, které navenek nemění chování objektů vytvořených z třídy předka, se mi chování potomků může pokazit. Přídáním metod do předka se snadno může stát, že potomek už není podtyp, a pokud ty nové metody začnu používat, tak se potomek může začít chovat nekorektně. Všimněte si, že když nepoužiji dědění, tak se tomu mohu vyhnout.
Všimněte si, že když nepoužiji dědění, tak se tomu mohu vyhnout.To skoro vypadá, jako byste se tomu při použití dědění vyhnout snad nemohl.
MyContainer
a podruhé její potomek.)
To, že se při použití dědičnosti dá chybovat, ještě neznamená, že bychom měli dědičnost (implementace) zavrhnout úplně. Chybovat se dá ve všem (v tom případě by bylo nejlepší neprogramovat vůbec, protože pak budeme mít 0 chyb).
Taky dost záleží, jakým směrem se vývoj ubírá:
To, že se při použití dědičnosti dá chybovat…Problém je, že se při jejím použití chybuje dost často. Například potomci, co vzniknou, se často automaticky používají jako podtypy, aniž by někdo ověřoval, že to podtypy skutečně jsou.
Problém je, že se při jejím použití chybuje dost často.Totéž se dá říct o slabě/dynamicky typovaných jazycích, assembleru, céčku atd. Přesto neprogramujeme všichni v tom jediném správném jazyce a jediným správným způsobem.
Ten příklad je dost nešťastný, je to jakýsi hybrid mezi kompozicí a dědičností, který nedává moc smysl (zbytečné dvě instance stejné třídy – jednou MyContainer a podruhé její potomek.)Vždyť říkal, že by bylo lepší použít interface...
je to jakýsi hybrid mezi kompozicí a dědičností, který nedává moc smysl (zbytečné dvě instance stejné třídy – jednou MyContainer a podruhé její potomek.)Co je na dvou instancích špatného? Jedině by to šlo přešoupnout tak aby oba byly potomci nějakého abstraktního Containeru (jak jsem říkal), ale nechal jsem to takhle, aby bylo vidět, že dědit interface lze i bez slova interface.
Jasně, všechno jde řešit přes rozhraní a společnou funkcionalitu vyšoupnout do nějakých pomocných tříd a pak to dát pomocí kompozice dohromady… ale nemyslím si, že by to pak bylo přehlednější (aspoň ne většinou).Věřte, že to bude jak přehlednější, tak přepoužitelnější.
To, že se při použití dědičnosti dá chybovat, ještě neznamená, že bychom měli dědičnost (implementace) zavrhnout úplně.To ne. Dědičnost zavrhujeme proto, že vytváří extrémně těsnou vazbu.
Bla bla blaTímhle by se dala charakterizovat celá tahle diskuse. On totiž ten postoj programátora k dědičnosti (a další „zásadní otázky“) má jen pramalý vliv na kvalitu a úspěšnost výsledného softwaru. Jeden expert tu hlásá, že
Interface
je zbytečný, druhý že dědičnost by se neměla používat a třetí velebí princip DRY… tahle soustava rovnic nemá řešení. Naštěstí jsou to ale jen komentáře v diskusi, ne nějaké obecně závazné zákony
to jo. ale objektovy pristup a dedicnost jsem pouzil spravne, ne? ;-]No to teda rozhodně ne.
Zajímavé je, že c#, ve kterém je ukázka v tomto blogu tento problém řeší. Překrývání metod tam funguje jinak a při deklaraci metody se dá říci, jak se chovat při jejím překrytí na potomku.
Více například tady.
Jo, jenže je třeba si uvědomit, že všechny tyhle kritiky, na které narážíte, kritizují reálné problémy.O tom nepochybuji. Ale IMHO nenabízejí reálná lepší řešení, jen teoretická řešení nebo akademickou debatu.
jo a metoda nemůže mít víc než 65k instrukcí, heheOK, ale já se bavil o omezeních daných pouze lidmi, nikoli technologiemi (které sice dělali taky lidi, ale vy jistě víte, co myslím).
Osobně se řídím starým pravidlem: metoda/funkce by se měla vejít na obrazovku. Na nic jiného pravidla nemám, ale když má třída tisíc řádek, tak už je to pravděpodobně svinstvo (kdybych měl odhadnout, řekl bych, že moje třídy mívají tak kolem sta až dvou set řádek, víc spíš výjimečně).Jasně, souhlas. Jenže já se tím řídím tak nějak citem (a vy asi taky). Nemám prostě dáno: 20 příkazů - vždy dobré, 21 příkazů - vždy špatné (věřte tomu, že jsou lidé, co to tak mají).
A u té dědičnosti a primitivních typů jste zapomněl na pole…O dědičnosti primitivních typů jsem ani nemluvil...
O dědičnosti primitivních typů jsem ani nemluvil...To jistě ne. Ale v tom povzdechu Dědičnost je kontrarevoluční, primitivní typy buržoazní. opravdu chybějí pole. Tedy konkrétně v Javě, v jiných jazycích možná nejsou tak zprasená
Napíšete taky o tom, proč jsou v OOP velmi důležité konstruktory?Inicializace nově vzniklých objektů v paměti je důležitá i mimo OOP.
Kdesi jsem četl názor, že dědičnost je dobrý nástroj při modelování, ale mizerný při implementaci.Mně se dědičnost velmi osvědčila a spousta věcí bez polymorfie nelze rozumně řešit, viz polymorfie síťových adres v céčku, polymorfie prvků uživatelského rozhraní, atd.
Dědičnost a polymorfismus jsou dvě velmi odlišné věci, které spolu nemusí vůbec souviset (jako např. ty adresy v C).Proto úmyslně používám slovo polymorfismus, aby bylo jasno. Dědičnost je jeden ze způsobů jak na polymorfismus nahlížet, a zároveň to slovo označuje i způsob, jak ho implementovat. Interfaces jsou další způsob modelování i implementace polymorfismu.
S pomocí dědičnosti se kód rychle píše, ale velmi pomalu čte. Polymorfismus umožňuje dělat kouzla, která zůstávají čitelná. Je škoda, že OOP tyhle dvě věci míchá tak moc dohromady.Oni spolu docela úzce souvisí, asi proto se to často probírá dohromady. Škodu v tom žádnou nevidím, spíše naopak.
Oni spolu docela úzce souvisí, asi proto se to často probírá dohromady.Problém je právě v tom, že dědičností automaticky nevzniká podtyp a naopak podtyp nemusí vzniknout jen dědičností.
Problém je právě v tom, že dědičností automaticky nevzniká podtypPokud vím, tak vznik podtypu je právě smyslem dědičnosti.
naopak podtyp nemusí vzniknout jen dědičností.Jde o to, co všechno nazýváme podtypem, odkaz na nějakou oficiální terminologii, od které bysme se neměli odchýlit? Já znám podtyp právě jako pojem dědičnosti.
Interfaces jsou další způsob modelování i implementace polymorfismu.Interfaces je imho specielní případ dědičnosti (bereme-li virtualní členy tříd jako součást dedičnosti, což předpokládám, že jo).
Jenže dědičnost není pohled na polymorfismus. Je to bastlítko na šetření klávesnice a občas to zpřehlednění program (ovšem daleko častěji to nadělá víc škody než užitku).Dědičnost jako bastlítko na šetření klávesnice... ehm... velmi zajímavý názor, asi si no napíšu na seznam kuriozit.
Že se poblíž dědičnosti obvykle vyskytuje i polymorfismus je spíš jen šťastná náhoda.Tady už to zavání tím, že přecházíme na plané tlachání, absolutně nevím, co si z těchto vašich tvrzení odnést za informaci.
Vlastně je polymorfismus vlastnost vyskytující se i u spousty jiných věcí než je na dědičnosti postavené OOP, třeba funkcionální jazyky z polymorfismu těží až neskutečným způsobem a dědičnost tam kolikrát ani vůbec není.Moc nevěřím tomu, že by se nad tím nedal postavit nějaký hierarchický model.
nebo extrémě abstraktní.V tom žádný zádrhel nevidím. Objektové programovací jazyky a modely se s tím srovnávají celkem bez potíží.
Vlastně tím ještě nevyhnutelně přináší problémy s vícenásobnou dědičností.Nevyhnutelně? Doteď jsem s vámi chtěl polemizovat, ale toto mi přijde jako nesmysl.
Inicializace nově vzniklých objektů v paměti je důležitá i mimo OOP.To jistě je, ale kdo rozhoduje, která konkrétní třída se bude instancovat?
To jistě je, ale kdo rozhoduje, která konkrétní třída se bude instancovat?Obvykle je to ten, kdo si instancování vyžádá. Pak jsou ještě případy, kdy je lepší to nechat na nějaké factory, i tenhle model jsem nedávno s úspechem použil, kvůli lepší modularitě kódu.
Obvykle je to ten, kdo si instancování vyžádá.To je ten průšvih.
(Smalltalk allClasses collect: [:class | class allSuperclasses size]) asBag sortedCounts
výsledek:
{(990->3). (700->4). (581->2). (546->5). (175->6). (55->7). (21->8). (11->9). (5->1). (4->10). (2->0). (2->11). (1->12)}
A vítězem je:
(Smalltalk allClasses detectMax: [:class | class allSuperclasses size]) withAllSuperclasses
výsledek:
CustomQuestionDialogWindow QuestionWithoutCancelDialogWindow QuestionDialogWindow ProceedDialogWindow MessageDialogWindow DialogWindow StandardWindow SystemWindow MorphicModel BorderedMorph Morph Object ProtoObject
Mazec Údaje jsou pro Pharo 1.1.1.
Poďme spolupracovať a nie sa hádať a ťahať za malichernosti ako školkári, sorry začína mi byť s tejto komunity zle.Neboj, je to oboustranné :-P.
Poďme spolupracovaťNa čem? Něčeho bych se zúčastnil rád, jen
Ideální by pro mě byla nějaká jedno-dvoudenní akce, první den ráno se někde sejít s třema dalšíma lidma, začít kódit spolu s velkou zásobou kafe, a druhý den večer to mít hotové. Jenže kde se k něčemu takovému dostat...Abclinuxu hacking event?
Osobne si myslím že autor zápisku chcel poukázať na dedičnosť objektovProč jako jediný mám pocit, že ve skutečnosti o nic takového autorovi nešlo, ale jediné o co autorovi šlo (toto pozoruju už ve více jeho zápiscích a různých komentářích na jiných fórech) je rozpoutat flame kde by mohl nasbírat zadarmo rozumy a ty pak rozdávat jinde (můj skromný tip? – v zaměstnání svým _méně_ rozumově zdatným podřízeným a ostatní čeládce)?
No, to bych si dovolil dost nesouhlasit, treba ta prezentace o hrach na PS3 byla dost zajimava (aneb jak vam muze OOP rozbourat cache).Já se díval na to, na co jsem byl výše odkázán, a to pokud vím není ani prezentace.
Pointa tech clankuOd komentujících obvykle očekávám, že odkáží na zajímavý článek. Tedy komentuju to, na co bylo odkázáno v komentáři zde, ne to, na co bylo odkázano pod textem odkázaným v komentáři zde.
Uz je to ostatne dobre videt na tom, ze bez relacnich databazi (kde jsou data ulozena v centralnim schematu) by vetsina OOP aplikaci nemohla fungovat.Nevím jak toto tvrzení interpretovat, ale ať se snažím jakkoli, připadá mi to jako nesmysl, nebo alespoň nepravda.
Tiskni Sdílej: