Fórum Root.cz
Hlavní témata => Vývoj => Téma založeno: Martin 15. 07. 2013, 21:05:50
-
Ahoj, učím se programovat javě, a narazil jsem na pojmy rozhraní, dědičnost a abstratkní třídy. Když jsem si o nich vyhledal o každém něco zvlášť, myslím, že vím, co to asi tak je, ale nevím, co je kde vhodnější použít, na některých místech bych použil ono, nicméně všichni tam používají to druhé, jak tyto případy odlišit? Čeho se držet?
Předem díky za odpovědi a hezký večer.
-
to je jako se zeptat na rozdil mezi bramborem, nozem a vrtackou
rozhrani je ciste abstraktni trida, ktera definuje zakladni vlastnosti potomka
abstraktni trida je trida, ktera se musi dedit, protoze sama o sobe nemuze existovat
dedicnost je vlastnost vlastnictvi vlastnosti nadrazeneho objektu
nicmene zakladem je se naucit OOP, jakmile to neumite, neucte se javu
-
Já jsem ani tak nemyslel co přesně to je, spíš jaký je v tomr rozdíl, a co kde použít, neboť se mi často stává, že bych někde použil klidně to i to a nevím co si pak vybrat.
-
Rozhraní je přesně to, co říká název. Je to něco jako předpis, jak s objektem komunikovat. Sdružuje prototypy metod a pokud jej třída implementuje, musí dodat také implementaci všem těmto metodám. Používá se pro třídy, které mají různý charakter, ale dá se s nimi principielně provádět několik stejných akcí. Tyto akce je pak lepší sdružit do rozhraní a naimplementovat.
Abstraktní třída slouží jako abstraktní základ hierarchie dědičnosti. Tady se snadno najde příklad z reálného života. Třeba třída Zvíře, od které budou dědit třídy jako Pes, Kočka atd... Zvíře bude sdružovat všechny společné vlastnosti všech zvířat (věk, hmotnost atd...) a metody všech zvířat, ale nedává smysl, aby vznikla instance Zvířete, ta prostě existovat nemůže. Všechno, co tedy bude zvíře obsahovat, se v konkrétních instancích bude vyskytovat pouze jako součást potomků v hierarchii.
-
Aha, ale kde se vezme ta hranice toho, co může být instance a co ne? Vždyť přece Pes může být také abstraktní - dědí Zlatý retrívr a Kokršpaněl. Proč nepoužít tedy jen jedno? Nebo jsem v tom přehlédl nějaký zásadní rozdíl? Resp. u abstratkních tříd dje jen o to, aby se nedali vytvořit, ale jinak jsou v zásadě podobné dědičnosti?
-
Tak přirozeně, tohle je příklad ze života, při programování konkrétních projektů je ta hranice celkem jasná. Hezký praktický příklad je třeba ve hrách - ve strategii budeš mít různé jednotky. Pro ně si uděláš nějakou abstraktní třídu Unit, od které bude každý konkrétní typ jednotky dědit. Unit pak bude obsahovat pole pro HP jednotky (každá jednotka má HP), bude implementovat základní metody pro pohyb jednotky (každá jednotka se pohybuje stejně, jen např. různou rychlostí, tu ale bude mít každý potomek nastavenou zvlášť), bude typicky implementovat prázdnou abstraktní metodu pro útočení (každá jednotka umí útočit, ale většinou to každá dělá nějak jinak). Zde by se třeba hodilo si vytvořit ještě další patro v hierarchii složené z abstraktních tříd jako MeleeUnit, RangedUnit, atd... kde každá tahle abstraktní třída už bude implementovat metodu pro útok, protože všichni potomci útočí stejně, jen třeba různou silou, což si opět nastaví každý zvlášť. Doufám, že je to pochopitelné.
-
Vyhoda Abstraktnej triedy je v tom - ze v nej mozes ciastocne naimplementovat niektore casti triedy a nemusis to zakazdym implementovat v potomkovi. Nevyhoda je ze mozes dedit len jednu abstraktnu triedu.
Naopak ked dedis rozhranie musis kod kazdej metody v kazdom potomkovi vzdy nanovo naimplementovat. Vyhoda rozhrania je v tom ze kazda trieda moze dedit viac rozhrani a moze sa na ne pretypovat.
(Toto obmedzenie plati len pre jazyky s jednoduchou dedicnostou)
-
Ja bych to popsal trochu jinak, alespon z meho pohledu jak jsem se s tim setkal, a omlouvam se za styl, javu jsem uz nejaky patek nevidel.
Rozhrani je vetsinou udelane tak ze bud dava objektu nejakou vlastnost, nebo standartizuje nejaky set funkci (vcetne vstupnich parametru a navratovych hodnot) ktere objekt musi mit aby s nim slo neco delat. Interface nepopisuje objekt co do informaci ktere uklada ale co do metod ktere obsahuje.
Dal bych za priklad tridu cislo. Ta ma rozhrani comparable, cili to rika ze trida jde s necim srovnavat. Rozhrani comparable, tak jak je definovane nekde jinde, vyzaduje, aby trida mela nekde metodu int compareTo(object input). kde bude definovane ze vraci retval<0 kdyz je cislo mensi nez parametr predany jako argument, nulu kdyz je stejne a retval>0 kdyz bude argument mensi.
Treba.
Objekt nemusi byt cislo, predstav si ze mas objekt co ma v sobe obrazek a ty muzes udelat funkci compareTo co srovna zda je obrazek tmavsi nez jinej atd atd.
Priklad s cislem :
public class Cislo implements Comparable {
double number;
public Cislo () {
numberl = 0.0;
}
public Cislo (double _number) {
number= _number;
}
public String toString() {
return number;
}
public int compareTo(Object o1)
{
if (this.number== ((Cislo) o1).number)
return 0;
else if ((this.number) > ((Cislo) o1).number)
return 1;
else
return -1;
}
}
Tehle vlastnosti muze byt hodne, napada me treba serializable, printable...
Pokud si dobre vzpominam tak v Jave muze objekt implementovat neomezene interfacu.
Abstraktni tridu si prestav jako sablonu. POZOR - abstraktni trida je tehdy, pokud v sobe ma alespon jednu abstraktni metodu. Me tohle ucili na obrazcich. Priklad >
Mas Abstrakni tridu Geometricky tvar.
Abstract class GeometrickyTvar
{
abstract int numStran; // jen promenna
abstract int draw(); // nejaka metoda
}
// no a zde se dedi :
class Trojuhelnik extends Geometricky tvar
{
int a,b,c; // strany.
}
...
...
if (trojuhelnik.numStran != 3) return "chyba"; //no a v kodu muzes pak pouzit num stran prestoze v tom trojuhelniku nikde neni
Koukam ze abstraktni tridou jsem vzal i dedicnost, takhle se rika tomu ze jedna trida se zkopiruje do druhe a prida vlastni vlastnosti.
Dedit se v jave da jen z jedne tridy, nicmene jedna trida muze mit kolik chce rozhrani. ( teda myslim, to si radsi over )
Skoukni knihu/net, uz nemam sil... snad jsem alespon trochu pomohl, GN =)
-
Ach, koukam ze jsem omylem vymazal jednu dulezitou cast - abstrakni trida/metoda je takova ktera nema alespon jednu metodu z implementovanou, ma napsane jen jeji jmeno a IO parametry.
-
Úplně polopaticky řečeno: abstraktní třída obsahuje nějaký kód, který potomci můžou zdědit, zatímco interface žádný kód neobsahuje - popisuje jenom, jaké metody má mít třída, která interface implementuje.
Proto taky ta terminologie: z abstraktní třídy se dědí, zatímco interface se implementuje.
-
Rozhrani je vetsinou udelane tak ze bud dava objektu nejakou vlastnost, nebo standartizuje nejaky set funkci (vcetne vstupnich parametru a navratovych hodnot) ktere objekt musi mit aby s nim slo neco delat. Interface nepopisuje objekt co do informaci ktere uklada ale co do metod ktere obsahuje.
<flame mode on>Kdyby nebyli všichni pomatení z C++ a java by to názvosloví částečně nepřejala, bylo by to daleko jasnější:<flame mode off> interface definuje, jakým zprávám má objekt rozumět. V Objective C se tomu celkem přiléhavě říká "protokol" ( viz http://goo.gl/YTv48 )
-
Na abstraktní třídy a dědičnost se vyser. Napiš si v Javě pár prográmků a tyhle věci přijdou samy.
-
A este doplnim ze u rozhrania si mozes zvolit 2 typy implementacie abstraktnych clenov:
1. implicitne
2. explicitne
(rozdiel je v nutnosti implicitneho a explicitneho pretypovania)
U abstraktnych tried je to vzdy implicitne.
-
A este doplnim ze u rozhrania si mozes zvolit 2 typy implementacie abstraktnych clenov:
1. implicitne
2. explicitne
(rozdiel je v nutnosti implicitneho a explicitneho pretypovania)
U abstraktnych tried je to vzdy implicitne.
Promiň, možná jsi jenom použil pro mě neobvyklou terminologii, ale vůbec jsem nepochopil, co jsi chtěl tímhle říct. Můžeš uvést nějaký příklad?
-
Úplně polopaticky řečeno: abstraktní třída obsahuje nějaký kód, který potomci můžou zdědit, zatímco interface žádný kód neobsahuje - popisuje jenom, jaké metody má mít třída, která interface implementuje.
Proto taky ta terminologie: z abstraktní třídy se dědí, zatímco interface se implementuje.
Není to úplně přesné - abstraktní třída může ale nemusí obsahovat kód.
Zásadní poznatek: interface je extrémní případ abstraktní třídy v tom smyslu že interface neobsahuje žádný kód - všechny metody jsou povinně abstraktní. Můžete u nich uvést identifikátor abstract ale nemusíte - stejně tam je (implicitně).
Další zásadní poznatek: interface nemůže (na rozdíl od abstraktních tříd) obsahovat žádné instanční proměnné (fieldy), což umožňuje aby třídy implementovaly více než jedno interface - googlujte "diamond problem" abyste pochopili o co jde.
A ještě třešnička na závěr: interface mohou mezi sebou dědit.
-
Úplně polopaticky řečeno: abstraktní třída obsahuje nějaký kód, který potomci můžou zdědit, zatímco interface žádný kód neobsahuje - popisuje jenom, jaké metody má mít třída, která interface implementuje.
Proto taky ta terminologie: z abstraktní třídy se dědí, zatímco interface se implementuje.
Není to úplně přesné - abstraktní třída může ale nemusí obsahovat kód.
Zásadní poznatek: interface je extrémní případ abstraktní třídy v tom smyslu že interface neobsahuje žádný kód - všechny metody jsou povinně abstraktní. Můžete u nich uvést identifikátor abstract ale nemusíte - stejně tam je (implicitně).
Další zásadní poznatek: interface nemůže (na rozdíl od abstraktních tříd) obsahovat žádné instanční proměnné (fieldy), což umožňuje aby třídy implementovaly více než jedno interface - googlujte "diamond problem" abyste pochopili o co jde.
A ještě třešnička na závěr: interface mohou mezi sebou dědit.
Třešnička na třešničce: V Javě 8 budou moci interfacy obsahovat i implementace instančních (a dokonce i statických) metod. Čili v název "interface" už nebude moc přesný, v podstatě půjde o mixiny. Ale i to je pořád nic proti scalovským traitům, což jsou v podstatě plnohodnotné abstraktní třídy, akorát bez konstruktoru.
-
A este doplnim ze u rozhrania si mozes zvolit 2 typy implementacie abstraktnych clenov:
1. implicitne
2. explicitne
(rozdiel je v nutnosti implicitneho a explicitneho pretypovania)
U abstraktnych tried je to vzdy implicitne.
Promiň, možná jsi jenom použil pro mě neobvyklou terminologii, ale vůbec jsem nepochopil, co jsi chtěl tímhle říct. Můžeš uvést nějaký příklad?
interface Foo
{
void f();
}
interface Bar
{
void f();
}
class MyClass1 : Foo, Bar
{
public void f() { Console.WriteLine('A'); } //Implicitna implementacia
void Foo.f() { Console.WriteLine('B'); } //Explicitna implementacia
void Bar.f() { Console.WriteLine('C'); } //Explicitna implementacia
}
var obj = new MyClass();
obj.f(); //Vypise A
(obj as Foo).f(); //Vypise B
(obj as Bar).f(); //Vypise C
-
A este doplnim ze u rozhrania si mozes zvolit 2 typy implementacie abstraktnych clenov:
1. implicitne
2. explicitne
(rozdiel je v nutnosti implicitneho a explicitneho pretypovania)
U abstraktnych tried je to vzdy implicitne.
Promiň, možná jsi jenom použil pro mě neobvyklou terminologii, ale vůbec jsem nepochopil, co jsi chtěl tímhle říct. Můžeš uvést nějaký příklad?
interface Foo
{
void f();
}
interface Bar
{
void f();
}
class MyClass1 : Foo, Bar
{
public void f() { Console.WriteLine('A'); } //Implicitna implementacia
void Foo.f() { Console.WriteLine('B'); } //Explicitna implementacia
void Bar.f() { Console.WriteLine('C'); } //Explicitna implementacia
}
var obj = new MyClass();
obj.f(); //Vypise A
(obj as Foo).f(); //Vypise B
(obj as Bar).f(); //Vypise C
Aha, no tohle platí možná v C# nebo C++, ale pokud zůstaneme v kontextu Javy dle autora tématu, tak tam nic podobného neexistuje. Všechny metody v Javě jsou vždycky virtuální, takže se vždy volá implementace podle skutečné třídy daného objektu bez ohledu na typ reference, na které je metoda volaná.
-
Není to úplně přesné - abstraktní třída může ale nemusí obsahovat kód.
A existuje nějaký rozumný důvod použít čistě abstraktní třídu bez proměnných, když můžu použít interfejs? (pod "kód" jsem nešikovně zahrnul i data - proměnné).
-
Triedna (aj abstraktna) je vztah typu is a(je). Rozhranie znamena can do (vie robit).
Napr. netopier je cicavec, holub je vtak, pterodaktyl je plaz. Toto je typicke zaradenie do tried a povedzme, ze triedna hierarchia kopiruje klasifikaciu zo zoologie. Pridajme k tomu lietadlo. Lietadlo je stroj. Co maju spolocne? Vedia robit podobny typ operacii - vzlietni, pristan, atd. Vztah, ktory definuje, co ma spolocne holub, pterodaktyl a lietadlo je typ vztahu can do (vie lietat), t.j. maju spolocne "rozhranie" pre lietajuce objekty.
Nez sa rozhodnem, ci je nieco trieda alebo rozhranie, polozim si presne tuto otazku, aky typ vztahu to reprezentuje. Ak by som si tuto otazku nepolozil, lahko dospejem k zlemu navrhu, kde holub aj lietadlo priamociaro dedia od nejakeho spolocneho predka - triedy. Dosledkom bude, ze sa stratim pri rozsirovani programu, ked sa budem stale budem borit s problemom, ze tie 2 veci vlastne nemaju nic spolocne, len v nejakej casti programu potrebujem vediet, ze oboje vedia lietat a vykonat s nimi nejaku "lietajucu" operaciu.
-
Nez sa rozhodnem, ci je nieco trieda alebo rozhranie, polozim si presne tuto otazku, aky typ vztahu to reprezentuje. Ak by som si tuto otazku nepolozil, lahko dospejem k zlemu navrhu, kde holub aj lietadlo priamociaro dedia od nejakeho spolocneho predka - triedy. Dosledkom bude, ze sa stratim pri rozsirovani programu, ked sa budem stale budem borit s problemom, ze tie 2 veci vlastne nemaju nic spolocne, len v nejakej casti programu potrebujem vediet, ze oboje vedia lietat a vykonat s nimi nejaku "lietajucu" operaciu.
To je sice hezká teorie, ale imho je to přesná ukázka přecenění OOP - představy, že "osekané" OOP je způsob myšlení, ktrerý je "přirozeným" základem jakýchkoli vztahů, abstrakcí.
Především to takhle těžko můžeš říct u jazyků, které nemají vícenásobnou dědičnost, protože jeden objekt samozřejmě může spadat do víc tříd, protože třída není nic jiného než abstraktní pojem, kterým pojmenováváme konkrétní objekty, které mají nějaké konkrétní vlastnosti. Neexistuje nic jako "přirozená kmenová třída", což by byla abstraktní skupina, do které objekt patří "primárně" a která by tak byla jediným "správným" předkem všech objektů libovolné třídy X.
Např. ropa je (is-a) stejně tak "mazlavá tekutina" jako "palivo" a "organická sloučenina". Jazyk bez vícenásobné dědičnosti mě donutí k tomu, že budu tvrdit, že ropa je (is-a) primárně "organická sloučenina" plus má vlastnosti (can-do) paliva a mazlavé tekutiny, což zjevně neodpovídá skutečnosti, protože to můžu klidně udělat opačně (is-a palivo, can-do mazlavá).
Tomu, jak normálně pojmy používáme, by bylo imho daleko bližší používat interfejsy (klidně uspořádané do hierarchie, když už to teda chceme) plus mixiny, které ty interfejsy implementují, protože když už nějak formalizovat naše vnímání/myšlení, tak tak, že máme nějaké objekty, které mají nějaké (víceméně volně kombinovatelné) vlastnosti. Bohužel běžný programátor používá dědění na úkor skládání, což je imho opět neblahý vliv C++, ze kterého se ani Java pořádně nevymanila.
-
A ještě dodatek: když se nad tím člověk zamyslí, může dojít k názoru, že vlastně vůbec nejlepší je se na celý OOP vykašlat a použít starý dobrý funkcionální přístup - objekt mám popsaný nějakou datovou strukturou plus k tomu mám nějaké funkce, které implementují vlastnosti a umí operovat nad datovými strukturami, které mají nějaké dané vlastnosti. A voilá, mám třídy interfejsů i mixiny, akorát tomu tak neříkám a musím si to trochu pohlídat sám, protože to není tak explicitní jako u OOP.
-
To je sice pravda, ze nic ako "univerzalne platna" klasifikacia neexistuje ale na druhej strane, toto nie je specificky problem pre programovanie ale problem, ktory musi riesit kazdy, kto sa snazi zaradit veci do nejakych kategorii. Ak sa bavime o "prirodzenosti" takejto kategorizacie, tak "prirodzenost" nejde nad ramec pragmatickeho pristupu - staci, ak to je vseobecne zrozumitelne a co najpresnejsie odraza spolocne vlastnosti. Mne viacnasobna dedicnost nikdy nechybala, to je minor problem a separatna tema. Ci je lepsie funkcionalne programovanie alebo oop je tiez separatna tema, bavime sa o tom, co je v oop trieda a rozhranie.
-
bavime sa o tom, co je v oop trieda a rozhranie.
Jasně, ale tak, jak jsi to napsal (vztahy se dělí na is-a a can-do) to prostě neodpovídá běžnému vnímání v případě, že vztahy is-a mám uspořádané do stromu, kde každý uzel může mít jenom *jednoho* rodiče. Tenhle způsob uvažování vede k tomu, že některé vztahy typu is-a *prohlásím* (uměle,libovolně) za can-do, protože nemůžu jinak. K čemu pak to rozdělení je?
Když už bych to takhle chtěl napsat, řekl bych, že konkrétní jazyk (např. Java) mi umožňuje jenom jednu vazbu is-a ke konkrétnímu rodiči. Nemůžu to ale postavit tak, že v rámci analýzy musím odlišit vztah is-a a can-do a podle toho to pak implementovat. Takhle to není a být nemůže.
-
bavime sa o tom, co je v oop trieda a rozhranie.
Jasně, ale tak, jak jsi to napsal (vztahy se dělí na is-a a can-do) to prostě neodpovídá běžnému vnímání v případě, že vztahy is-a mám uspořádané do stromu, kde každý uzel může mít jenom *jednoho* rodiče. Tenhle způsob uvažování vede k tomu, že některé vztahy typu is-a *prohlásím* (uměle,libovolně) za can-do, protože nemůžu jinak. K čemu pak to rozdělení je?
Když už bych to takhle chtěl napsat, řekl bych, že konkrétní jazyk (např. Java) mi umožňuje jenom jednu vazbu is-a ke konkrétnímu rodiči. Nemůžu to ale postavit tak, že v rámci analýzy musím odlišit vztah is-a a can-do a podle toho to pak implementovat. Takhle to není a být nemůže.
Tato diskusia je mimo, lebo ty nenamietas ci je popis vztahu medzi triedou a rozhranim vo filozofii OOP spravne popisany (a tu nejde o Javu ale vesobecne o OOP), ale ci je to spravna metoda. Ano, v zmysle OOP je musis rozhodnut o typu vztahu presne ako pisem. Ci mozes mat jedneho alebo viac triednych predkov je zalezitost jazyka. Rozdiel medzi rozhranim a triedou je principialny - je to rozdiel v koncepte a v uceli.
Ani v pripade tiredneho dedenia od vylucne jedneho predka nie je navrh hierarchie o nic umelejsi ako inde, kde taku stromovu hierarchiu definujes (zoologia, botanika, atd).
-
Nuz, toto su zakladne stavebne prvky OOP a tvojim cielom by malo byt naucit sa robit spravnu hierarchiu objektov.
To zaberie roky :)
Najprv treba zvladnut spravnu implementaciu troch zakladnych prvkov OOP, tj. spravne pouzivat zapuzdrenie, dedicnost a polymorfizmus.
Tu odporucam prejst viacero kapitol z knizky Effective Java (http://www.amazon.com/Effective-Java-Edition-Joshua-Bloch/dp/0321356683). Samozrejme cela kniha stoji za to, spada do kategorie 'must have' pre kazdeho javistu, myslim ze aj Gosling ju cital xD
Potom sa vrhnut na studium tzv. 'design patterns' (navrhove vzory).
Hlavne spravne pouzivanie design patterns a dobry odhad, kedy kde aky pattern implementovat je casto tazke rozhodnut, chce to skusenosti.
O navrhovych vzoroch su popisane kvanta clankov, staci sa spytat google, napr.: http://www.tutorialspoint.com/design_pattern/
-
Ani v pripade tiredneho dedenia od vylucne jedneho predka nie je navrh hierarchie o nic umelejsi ako inde, kde taku stromovu hierarchiu definujes (zoologia, botanika, atd).
Ano. Ale každý biolog si bude vědom, že to je jenom jeden z možných pohledů na realitu. Nebude tvrdit, že věci tak jsou, ale jenom, ře on si je tak roztřídil.
Rozdiel medzi rozhranim a triedou je principialny - je to rozdiel v koncepte a v uceli.
Naposledy se to pokusím ozřejmit: řekl jsi "Nez sa rozhodnem, ci je nieco trieda alebo rozhranie, polozim si presne tuto otazku, aky typ vztahu to reprezentuje."
Nepoložíš si otázku "jaký typ vztahu tam je" (v realitě), ale rozhodneš se, jaký typ vztahu tam uděláš.
Prostě tvrdím, že vztahy nemůžeš analyzovat přirozeně, nezávisle na jazyku a potom je implementovat, ale už u analýzy musíš myslet na to, co ti jazyk (v tomhle ohledu) umožňuje. A to je prostě špatně. A dvakrát špatně to je, pokud začneš tvrdit, že ty vztahy, které sis tam (kvůli omezení jazyka) dal, jsou tam i v realitě. Nejsou.
Howgh.
-
:o
Pěkně tomu nešťastníkovi pomáháte.
(příjdu, až tu bude deset stránek, snad už bude jasné, kdo ho má většího)
-
Ani v pripade tiredneho dedenia od vylucne jedneho predka nie je navrh hierarchie o nic umelejsi ako inde, kde taku stromovu hierarchiu definujes (zoologia, botanika, atd).
Ano. Ale každý biolog si bude vědom, že to je jenom jeden z možných pohledů na realitu. Nebude tvrdit, že věci tak jsou, ale jenom, ře on si je tak roztřídil.
Rozdiel medzi rozhranim a triedou je principialny - je to rozdiel v koncepte a v uceli.
Naposledy se to pokusím ozřejmit: řekl jsi "Nez sa rozhodnem, ci je nieco trieda alebo rozhranie, polozim si presne tuto otazku, aky typ vztahu to reprezentuje."
Nepoložíš si otázku "jaký typ vztahu tam je" (v realitě), ale rozhodneš se, jaký typ vztahu tam uděláš.
Prostě tvrdím, že vztahy nemůžeš analyzovat přirozeně, nezávisle na jazyku a potom je implementovat, ale už u analýzy musíš myslet na to, co ti jazyk (v tomhle ohledu) umožňuje. A to je prostě špatně. A dvakrát špatně to je, pokud začneš tvrdit, že ty vztahy, které sis tam (kvůli omezení jazyka) dal, jsou tam i v realitě. Nejsou.
Howgh.
Nie, ja si polozim otazku presne tak ako som ju naformuloval:-) Snad ma nechces presvedcit, ze to robim inak. To, ze tebe takto polozena otazka nedava postacujucu odpoved, moze byt vec cviku. Je to dobra otazka a vedie ma k dobrym odpovediam. Ked sa chces zlepsit v navrhu, tiez si ju klad.
Ano, suhlasim, ze model nie je realita. A?
-
:o
Pěkně tomu nešťastníkovi pomáháte.
(příjdu, až tu bude deset stránek, snad už bude jasné, kdo ho má většího)
+1... Taky jsem chtěl ještě upřesnit svojí odpověď když jsem to teď viděl při plném vědomí, ale když jsem viděl co tu je za diskusi, tak to asi nemá cenu.
-
Programuj a ak trochu premyslas, prides na to. Mne tie umele priklady nikdy nesedeli, vacsinou tych znalosti som ziskal v praxi premyslanim, citom a potom diskusiou s nejakym profikom. Programuj, ako najlepsie viac a casom na to prides.
-
Dobře, všem vám moc díky za odpovědi, myslím, že už to je pro mě trošku jasnější, ale kdyby náhodou měl někdo ještě nějaké pěkné vysvětlení, nebudu se mu bránit, budu jedině rád když ho sem někam napíšete :).
-
pri navrhu triedy casto zacnem navrhovanim interfejsu, kde poviem, CO ma trieda robit, teda ake metody ma poskytovat (ono je to dobre sparovane s pisanim unit testov, ale tym vas teraz trapit nebudem).
potom vytvorim triedu, ktora implementuje ten interfejs a obsahuje kod a instancne premenne, ktore zabezpecia, ze metody budu robit to, co maju
znalci urcite plestia oci, ale casom som samozrejme ziskal cit, ked viem, ze nie vzdy potrebujem zbytocny interfejs a triedu, ale staci mi len trieda.
abstraktne metody pouzivam vtedy, ak vyrobim abstraktnu triedu, kde niektore metody su naimplementovane a ostatne ponechane na podtriedy -- pekny priklad je, ked mate algoritmus o troch krokoch, uvod a zaver su furt rovnake, ale stred sa meni na zaklade rozlicnych okolnosti. uvod a zaver uvediete do dvoch abstraktnych metod abstraktnej triedy a "stred" bude abstraktna metoda, pricom kazda podtrieda si ten premenlivy stred implementuje po svojom. toto su vsak uz pokrocilejsie veci.
s dedicnostou treba dat velky pozor: sice ucebnice dokola tvrdia, ze dedicnost je IS-A (pes JE zvieratom, teda Pes extends Zviera), ale existuje kopa pripadov, ked to v OOP jednoducho nefunguje. (klasicky priklad je kruznica a elipsa, kde sice kruznica IS-A elipsa, ale navrhnut to tak v OOP je fail)
+ co vravi duro
-
klasicky priklad je kruznica a elipsa, kde sice kruznica IS-A elipsa, ale navrhnut to tak v OOP je fail
preco?
-
Základní chyba je pokoušet se objekty (resp. třídy) v programu modelovat na základě popisu pojmů v reálném světě.
Jako vysvětlení příkladů v učebnici ano, ale v reálném programu to málokdy dává dobré výsledky.
-
klasicky priklad je kruznica a elipsa, kde sice kruznica IS-A elipsa, ale navrhnut to tak v OOP je fail
preco?
Problém kružnice a elipsy (http://en.wikipedia.org/wiki/Circle-ellipse_problem) (případně čtverce a obdélníka) nastává pouze v případě, že budeš obě třídy modelovat jako mutable. Immutable třídy tímhle problémem netrpí, jenže immutabilita se stále ještě moc nenosí, že.
Nadefinujme si elipsu jako jednoduchou mutable beanu:
class Ellipsis {
private int x;
private int y;
Ellipsis(int x, int y) {
this.x = x;
this.y = y;
}
int getX() {
return x;
}
void setX(int x) {
this.x = x;
}
int getY() {
return y;
}
void setY() {
this.y = y;
}
}
Zatím žádný problém, že? Tak se podívejme na kružnici:
class Circle extends Ellipsis {
private int radius;
Circle(int radius) {
this.radius = radius;
}
int getRadius() {
return radius;
}
// nu a teď je potřeba něco udělat s metodami zděděnými z elipsy
// nejdřív gettery, s tím není problém
int getX() {
return radius;
}
int getY() {
return radius;
}
// no a co se settery?
// jakákoliv implementace buď poruší kontrakt kružnice nebo kontrakt elipsy
void setX(int x) { ??? }
void setY(int y) { ??? }
}
-
je to presne ako vravi predrecnik: ked mame elipsu, ta sa da natahovat v oboch osiach (= horizontalne i vertikalne cez setWidth() resp setHeight()). Zmena sirky neovplyvni zmenu vysky a naopak. Ta posledna veta definuje kontrakt, co je akasi "spolocenska zmluva", ktora sa dohodne medzi pouzivatelom triedy a samotnou triedou. Ak je kontrakt dodrzany, metoda dava spravne a ocakavane vysledky. Pre prekryte metody plati, ze musia dodrzat kontrakt rodicovskej metody (inak by to vracalo bludy).
A teraz zoberme kruznicu, co dedi od elipsy. Musi prekryt metody setWidth() aj setHeight(). Mame dve moznosti:
* bud so zmenou sirky sa musi menit aj vyska. To je porusenie kontraktu.
* alebo sa so zmenou sirky vyska nemeni, co sice dodrzi kontrakt rodicovskej metody, ale tym padom mame kruznicu, co zrazu prestane byt kruznicou ("zelipsovatie"), co je logicky blud (nechcete mat objekt typu Kruznica, ktory ma rozlicne polomery...)
jeden z principov prekryvania (override / dedenia) metod, je:
* predpodmienky (tvrdenia, ktore musia platit pred spustenim metody) nemozno v oddedenej metode pritvrdit. Ak pre elipsu plati, ze polosi mozu byt rozne, pre kruznicu nesmieme pred zmenou sirky vyzadovat, ze polosi (= polomer) musia byt rovnake
* postpodmienky (tvrdenia, ktore musia platit po dobehnuti metody) nemozno zoslabit. Ak pre elipsu plati, ze po zmene sirky sa vyska zachova, nemozeme v kruznici zrazu povedat, ze po zmene sirky sa moze zmenit vyska.
* invarianty (tvrdenia, ktore su nemenne pocas behu metody) sa nezmenia -- toto tu nema zmysel.
Cele to suvisi s Liskovovej substitucnym principom: co volne povedane hovori, ze ked mame v premennej typu elipsa nejaky objekt a nahradime ho napr. kruznicou, spravanie sa nezmeni. Je vidiet, ze to nie je pravda, lebo spravanie kruznice a elipsy su ine (staci si to predstavit na priklade vektoroveho editora a la Illustrator/Inkscape/Corel) a preto nema tam byt hierarchia dedicnosti, aj ked matematicka definicia hovori inak.
a ako hovori tiez predrecnik, cele je to o tom, ze trieda je navrhnuta ako premenliva (teda hodnoty jej instancnych premennych sa mozu menit), ale klasicky javoidny dizajn tried velmi neuvazuje o situaciach nemenlivych tried.
-
V jazycích s reflexí, kde mohu zjistit jednoznačný identifikátor třídy pro každou hodnotu, nemá neabstraktní třída žádný podtyp kromě sama sebe.
-
klasicky priklad je kruznica a elipsa, kde sice kruznica IS-A elipsa, ale navrhnut to tak v OOP je fail
Ale p*ču! Na tento přístup přesně sedí pořekadlo vylít s vaničkou i nemluvně. Tímhle způsobem akorát uměle vytváříte problémy a omezení, které ve skutečnosti vůbec neexistují. Komplikujete si tím život a žádnou výhodu to nepřináší - kromě toho, že máte pocit, že je to tak správnější. Mimochodem - odvození elipsy z kružnice by ve vašem pojetí OOP bylo sice naprosto čisté, ale prakticky je to úplně k ničemu.
-
V jazycích s reflexí, kde mohu zjistit jednoznačný identifikátor třídy pro každou hodnotu, nemá neabstraktní třída žádný podtyp kromě sama sebe.
Reflexe je pomalá (alespoň v Javě a C#).
Teda sorry. Pomalejší než pomalá. Udělej to 1x, nepoznáš rozdíl. Udělej to 1000x (což na opravdových projektech není nereálná hodnota) a nebudeš se stačit divit.
s dedicnostou treba dat velky pozor: sice ucebnice dokola tvrdia, ze dedicnost je IS-A (pes JE zvieratom, teda Pes extends Zviera), ale existuje kopa pripadov, ked to v OOP jednoducho nefunguje. (klasicky priklad je kruznica a elipsa, kde sice kruznica IS-A elipsa, ale navrhnut to tak v OOP je fail)
Nemusí být, záleží na tom, co "modeluješ".
-
Ale p*ču!
mozete to elaborovat?
---------
reflexia nie je nejake vykonnostne megaterno, ale v jave 5 sa dramaticky zvysila oproti prastarym verziam. ale to je tak vsetko, nejeden projekt na tom stoji a pada - taky spring je plny reflexie. a to nehovorim o dalsich zverinach, ako manipulacia bajtkodu za behu (vid serial pana Tisnovskeho), co je tiez domena springu a hibernate.
nejak by som to nehrotil.
-
ako manipulacia bajtkodu za behu (vid serial pana Tisnovskeho), co je tiez domena springu a hibernate.
V baště Javy Springu a Hibernate se manipuluje s bytecode ? :o Překvapit mě to nemůže, jenom bych se v takovém případě smíchy počůral ;D
-
A proč ne takto?
class Circle extends Ellipsis {
void setX(int x) { this.radius = x }
void setY(int y) { this.radius = y }
}
-
A proč ne takto?
class Circle extends Ellipsis {
void setX(int x) { this.radius = x }
void setY(int y) { this.radius = y }
}
Protože v tomhle případě nemůžu kružnici použít jako elipsu.
Ellipsis e = new Circle(5);
e.getX(); // 5
e.getY(); // 5
e.setX(10);
e.getX(); // 10
e.getY(); // 10 !!!
Poslední řádka porušuje kontrakt pro elipsu. Pokud změním velikost jedné poloosy, tak to druhou poloosu nesmí ovlivnit, což se ovšem tady stalo.
Tím pádem takováto kružnice je sice podtřída elipsy, ale už ne její podtyp, což je dost nepříjemné. V zásadě jde o porušení LSP (http://en.wikipedia.org/wiki/Liskov_substitution_principle).
-
ako manipulacia bajtkodu za behu (vid serial pana Tisnovskeho), co je tiez domena springu a hibernate.
V baště Javy Springu a Hibernate se manipuluje s bytecode ? :o Překvapit mě to nemůže, jenom bych se v takovém případě smíchy počůral ;D
resit to nativne = transformovat bytecode je nejrychlejsi (pri behu), je to opravdu takove prekvapeni? (navic si ani nejsem jistej, jestli by jen pomoci reflexe vse slo resit.)
https://github.com/hibernate/hibernate-orm/blob/master/libraries.gradle#L54
https://github.com/hibernate/hibernate-orm/blob/master/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java
V jazycích s reflexí, kde mohu zjistit jednoznačný identifikátor třídy pro každou hodnotu, nemá neabstraktní třída žádný podtyp kromě sama sebe.
Reflexe je pomalá (alespoň v Javě a C#).
Teda sorry. Pomalejší než pomalá. Udělej to 1x, nepoznáš rozdíl. Udělej to 1000x (což na opravdových projektech není nereálná hodnota) a nebudeš se stačit divit.
neni uplne pravda (alespon v pripade javy). pokud se vhodne pouziva MethodHandle tak je stejne rychla jako nativni kod.
-
resit to nativne = transformovat bytecode je nejrychlejsi (pri behu), je to opravdu takove prekvapeni? (navic si ani nejsem jistej, jestli by jen pomoci reflexe vse slo resit.)
Šťourat za běhu do bytecode je nepochybně nejrychlejší, ale taktéž je to důkaz že v jazyku něco chybí, proto je k smíchu že to používají přímo v baště Javy, místo toho aby si to za ty roky dodělali podporu rovnou do Javy.
-
resit to nativne = transformovat bytecode je nejrychlejsi (pri behu), je to opravdu takove prekvapeni? (navic si ani nejsem jistej, jestli by jen pomoci reflexe vse slo resit.)
Šťourat za běhu do bytecode je nepochybně nejrychlejší, ale taktéž je to důkaz že v jazyku něco chybí, proto je k smíchu že to používají přímo v baště Javy, místo toho aby si to za ty roky dodělali podporu rovnou do Javy.
Java se rozhodla jit cestou zpetne kompatibility a imo tohle jsou nasledky. Myslim, ze treba Scala je mnohem lepsi jazyk a bylo by krasne, kdyby brzo nahradila Javu (utopie :-\). Tolik veci co v Jave chybi oproti konkurencnimu C#, pritom snad vsechno (v nejake podobe) Scala obsahuje...
-
V baště Javy Springu a Hibernate se manipuluje s bytecode ?
cglib
za vsetko:
1. pri proxovany konkretnych tried nestaci java.lang.proxy, preto sa cglibuje. tym sa realizuje napr. aop
2. zistovanie nazvov parametrov v metodach - kedze reflexiou sa nemozno pytat na nazvy parametrov (tusim az od jdk8), bajtkod manipulaciou sa pozera do zakompilovanych nazvov parametrov.
dovody, preco je to tak, vysvetlil kolega vyssie: v jave je spatna kompatibilita nadovsetko.
mozete si vymenit plienku :-)
moze sa okolo toho rantovat do bezvedomia a kritizovat performance, ale funguje to dlhodobo a v praktickych projektoch som nejak nevidel, zeby to bolo az take pomale, aby nad tym bolo treba ronit krvave slzy.
-
Ale p*ču!
mozete to elaborovat?
Jediný problém je v tom, že nepřipouštíš zavedení nové vazby v potomkovi za přípustné, čehož důvod mi uniká. Kdybys myslel v intencích nějakého kultivovaného jazyka, jako třeba LISPu, nebo kdybys si prostě představil, že AxMinor a AxMaior nejsou obyčejné proměnné, ale obyčejné ukazatele, tak bys v konstruktoru kružnice jen zavedl vazbu AxMinor := AxMaior a rázem máš z elipsy kružnici. Že setMinor nastaví i AxMaior není nic nekorektního, protože z definice má kružnice obě poloosy stejné. Žádný logický fail to není a návrhový taky ne, neboť jde o podpřípad, s kterým elipsa samozřejmě počítá. Parametry elipsy nejsou žádným odkládacím prostorem pro dvě čísla, jsou to interní parametry objektu, co do nich bude uloženo resp. z nich přečteno záleží na interní definici třídy, uživateli po tom nic není a dokonce ani uživatel třídy elipsa nesmí spoléhat na to, že po setnutí jejích parametrů z nich getne ty samé parametry! Mohlo dojít k jejich limitaci, zaokrouhlení apod., v případě kružnice k jejich vzájemnému svázání.
Návrhová chyba je deklarovat, že nastavení hlavní poloosy nesmí mít žádný vliv na poloosu vedlejší. Proč? To je úplně zbytečná podmínka, vnitřní záležitost třídy elipsa. Setnutím požádám o nastavení takovýchto parametrů, getnutím získám reálně nastavené parametry.
-
Tomuto foru chyba administrator ako sol. Keby mazal alebo aspon oznacoval vsetky prispevky, ktore neodpovedaju na povodnu otazku, bola by tu toho tak 1/4. A hodnotu by to malo 4x vyssiu.
-
snazim sa to pochopit, ale bohuzial kultivovany lisp ma nik nenaucil.
mam dve objasnujuce otazky
1) tie prepodmienky a postpodmienky samozrejme mozem uplne vypustit, ale tu mi zase nie je jasne, ake racio v tej metode potom ostane, resp. je to uz potom vec dizajnu a dohody. vam sa moze zdat, ze to je zbytocna podmienka, mne ako hypotetickemu dizajnerovu inkscapu sa moze zdat, ze ak taham za lavy/pravy cierny stvorcek, tak sirka sa meni a vyska nie.
2) aku vyhodu potom bude mat elipsu a kruznicou vztah dedicnosti? v com to potom pomoze?
(samozrejme nepopieram, ze je povinnost getnut rovnaku vec ako sa setla)
-
Ale p*ču!
mozete to elaborovat?
Jediný problém je v tom, že nepřipouštíš zavedení nové vazby v potomkovi za přípustné, čehož důvod mi uniká. Kdybys myslel v intencích nějakého kultivovaného jazyka, jako třeba LISPu, nebo kdybys si prostě představil, že AxMinor a AxMaior nejsou obyčejné proměnné, ale obyčejné ukazatele, tak bys v konstruktoru kružnice jen zavedl vazbu AxMinor := AxMaior a rázem máš z elipsy kružnici. Že setMinor nastaví i AxMaior není nic nekorektního, protože z definice má kružnice obě poloosy stejné. Žádný logický fail to není a návrhový taky ne, neboť jde o podpřípad, s kterým elipsa samozřejmě počítá. Parametry elipsy nejsou žádným odkládacím prostorem pro dvě čísla, jsou to interní parametry objektu, co do nich bude uloženo resp. z nich přečteno záleží na interní definici třídy, uživateli po tom nic není a dokonce ani uživatel třídy elipsa nesmí spoléhat na to, že po setnutí jejích parametrů z nich getne ty samé parametry! Mohlo dojít k jejich limitaci, zaokrouhlení apod., v případě kružnice k jejich vzájemnému svázání.
Návrhová chyba je deklarovat, že nastavení hlavní poloosy nesmí mít žádný vliv na poloosu vedlejší. Proč? To je úplně zbytečná podmínka, vnitřní záležitost třídy elipsa. Setnutím požádám o nastavení takovýchto parametrů, getnutím získám reálně nastavené parametry.
Podtyp ve smyslu LSP to stejně nebude.
-
Ale p*ču!
mozete to elaborovat?
Jediný problém je v tom, že nepřipouštíš zavedení nové vazby v potomkovi za přípustné, čehož důvod mi uniká. Kdybys myslel v intencích nějakého kultivovaného jazyka, jako třeba LISPu
Já nejsem odborník na různé druhy Lispu, ale mám pocit, že je v nich vyžadováno anebo alespoň je dobrým zvykem používat neměnné datové struktury. Ve chvíli, kdy kružnice i elipsa budou immutable, tak zmiňovaný problém přestává existovat, protože zavoláním setteru (což už vlastně nebude v podtstě setter) neměním původní objekt, ale dostávám nový se změněnými vlastnostmi. Čili dostanu něco ve stylu:
class Ellipsis {
private final int x;
private final int y;
int getX() {
return x;
}
int getY() {
return y;
}
Ellipsis withX(int x) {
return new Ellipsis(x, this.y);
}
Ellipsis withY(int y) {
return new Ellipsis(this.x, y);
}
}
class Circle extends Ellipsis {
private final int radius;
Circle(int radius) {
this.radius = radius;
}
getRadius() {
return radius;
}
Circle withRadius(int radius) {
return new Circle(radius);
}
}
Parametry elipsy nejsou žádným odkládacím prostorem pro dvě čísla, jsou to interní parametry objektu, co do nich bude uloženo resp. z nich přečteno záleží na interní definici třídy, uživateli po tom nic není a dokonce ani uživatel třídy elipsa nesmí spoléhat na to, že po setnutí jejích parametrů z nich getne ty samé parametry! Mohlo dojít k jejich limitaci, zaokrouhlení apod., v případě kružnice k jejich vzájemnému svázání.
Návrhová chyba je deklarovat, že nastavení hlavní poloosy nesmí mít žádný vliv na poloosu vedlejší. Proč? To je úplně zbytečná podmínka, vnitřní záležitost třídy elipsa. Setnutím požádám o nastavení takovýchto parametrů, getnutím získám reálně nastavené parametry.
Sorry, ale s tímhle nejde souhlasit. Když elipse nastavím hodnotu dané poloosy na zvolenou hodnotu, tak samozřejmě je plně očekávatelné, že následným gettem tu samou hodnotu dostanu. Samozřejmě tu může docházet k nějakým zaokrouhlením nebo číselným konverzím, ale to je už je detail. Pokud setter bere int a getter vrací int, tak samozřejmě cokoliv jiného než dostat zpátky nastavenou hodnotu nedává smysl. Takhle by sis to představoval?
class Ellipsis {
/**
* Sets the size of X axis to the given value. Or perhaps doesn't.
* Or sometimes it also sets the value of Y axis.
*/
void setX(int x);
/**
* Returns the size of X. Or possibly some other unspecified value. Try your luck.
*/
int getX();
}
To je jako kdybys přidal prvek do seznamu a on tam pak nebyl. Anebo jsi nastavil jméno nějaké Person beaně a ona měla následně jméno jiné. To nejsou žádné vnitřní záležitosti, to je otázka komunikace s klientem mé třídy. Pokud kontakt třídy specifikuješ podobně nelogickým a způsobem, který neoodpovídá řekněme selskému rozumu, tak jsi blázen. A pokud ho nespecifikuješ vůbec, tak nejsi programátor, ale věštec z kávového lógru.
-
resp. je to uz potom vec dizajnu a dohody. vam sa moze zdat, ze to je zbytocna podmienka, mne ako hypotetickemu dizajnerovu inkscapu sa moze zdat, ze ak taham za lavy/pravy cierny stvorcek, tak sirka sa meni a vyska nie.
Urcite nekdo zase bude kvilet, ale neda mi to: Ten problem je presne v tom, co jsem psal - snaha koncepty OOP povazovat za "obecne platne". Tady koncept OOP "specialni pripady obecnejsich konceptu maji vsechny vlastnosti svych obecnejsich rodicu plus nejake navic" proste koliduje s poznatky z reality "kruh je specialni pripad elipsy".
A co se tyce toho inkscapu, proste budes muset pocitat s tim, ze kdyz natahnes kruh jednim smerem, vznikne ti elipsa. Coz je dalsi pripad toho, co jsem rikal: kdyby nebyli lidi zpovykani z C++, byli by zvykli na to, ze metody vraceji instance a neni nikde receno, ze to musi byt stejna instance stejne tridy. Takze kruh.setX(x) by vratilo elipsu a elipsa.setX(x).setY(x) by vratilo opet kruh. A puvodni instance by zmizela v propadlisti zapomneni :)
-
snazim sa to pochopit, ale bohuzial kultivovany lisp ma nik nenaucil.
mam dve objasnujuce otazky
1) tie prepodmienky a postpodmienky samozrejme mozem uplne vypustit, ale tu mi zase nie je jasne, ake racio v tej metode potom ostane, resp. je to uz potom vec dizajnu a dohody. vam sa moze zdat, ze to je zbytocna podmienka, mne ako hypotetickemu dizajnerovu inkscapu sa moze zdat, ze ak taham za lavy/pravy cierny stvorcek, tak sirka sa meni a vyska nie.
2) aku vyhodu potom bude mat elipsu a kruznicou vztah dedicnosti? v com to potom pomoze?
(samozrejme nepopieram, ze je povinnost getnut rovnaku vec ako sa setla)
ad 1. Pokud to budete chtít udělat s kružnicí, tak logičtější chování je, že výška se změní stejně jako šířka, resp. naopak. V případě odvození kružnice od elipsy je toto zaručeno jaksi samosebou, bez jediného písmenka kódu navíc.
ad 2. Pomineme-li výhodu popsanou v předchozím bodě, pak další přirozenou výhodou je např. recyklace vykreslovacího algoritmu.
Mě by spíš zajímalo, jaké výhody má v objektovém návrhu tvařit se, že kružnice nemá s elipsou nic společeného.
-
Mě by spíš zajímalo, jaké výhody má v objektovém návrhu tvařit se, že kružnice nemá s elipsou nic společeného.
Nahraditelnost - tam, kde je potřeba instance typu T, můžete dát instanci jeho podtypu S. Pokud dodržujete LSP, tak tím neovlivníte korektnost programu.
-
prekvapivo, wikipedia dava velmi pekny priklad vratane rieseni (a to dokonca so spomenutim lispu, kde je mozna zmena triedy).
http://en.wikipedia.org/wiki/Circle-ellipse_problem
-
(a to dokonca so spomenutim lispu, kde je mozna zmena triedy).
Neznam Lisp a uz vubec ne jeho objektovy system, ale tohle mi zrovna prave prislo jako dost prasacky reseni, aby se pod rukama menil typ jednoho objektu. A pritom se to da resit pekne funkcionalne - proste vratit jinej objekt... Nevim, proc tady lispisti od funkcionalniho pristupu odbocili.
-
To je jako kdybys přidal prvek do seznamu a on tam pak nebyl. Anebo jsi nastavil jméno nějaké Person beaně a ona měla následně jméno jiné. To nejsou žádné vnitřní záležitosti, to je otázka komunikace s klientem mé třídy. Pokud kontakt třídy specifikuješ podobně nelogickým a způsobem, který neoodpovídá řekněme selskému rozumu, tak jsi blázen. A pokud ho nespecifikuješ vůbec, tak nejsi programátor, ale věštec z kávového lógru.
Pokud přidávám prvek do konstantního seznamu a on tam pak není, pak se není čemu divit. Stejně jako by se nemělo být čemu divit, pokud např. zadávám parametry elipsy, jež nejsou povoleny v dané situaci, nebo se snažím nastavit hlavní a vedlejší poloosu kružnice na různé hodnoty.
Já se naopak výše popsaným způsobem snažím selský rozum neopouštět výměnou za rigidní výklad objektového modelu, který dědění chápe jen jako extenzi, což je právě ve sporu se selským rozumem. Jestliže objektu žárovka pošlu zprávu, aby se rozsvítila, ale ona je prasklá, tak nemůžu čekat, že obdržím stav, že je rozsvícena, když jsem ho tam přece jasně nastavoval. S tou elipsou je to stejné.
Mě by spíš zajímalo, jaké výhody má v objektovém návrhu tvařit se, že kružnice nemá s elipsou nic společeného.
Nahraditelnost - tam, kde je potřeba instance typu T, můžete dát instanci jeho podtypu S. Pokud dodržujete LSP, tak tím neovlivníte korektnost programu.
Já chtěl nějaký argument pro podporu nepříbuzenského vztahu elipsy a kružnice, ne k podpoře toho, co jsem napsal. :-) Ano, v případě odvození kružnice od elipsy ji můžete použít na jejím místě, což může mít větší či menší smysl, ale není to principiálně špatně.
(a to dokonca so spomenutim lispu, kde je mozna zmena triedy).
Neznam Lisp a uz vubec ne jeho objektovy system, ale tohle mi zrovna prave prislo jako dost prasacky reseni, aby se pod rukama menil typ jednoho objektu. A pritom se to da resit pekne funkcionalne - proste vratit jinej objekt... Nevim, proc tady lispisti od funkcionalniho pristupu odbocili.
Doporučuji k prostudování jako zajímavost. Totálně odlišný pohled na OOP, jaký představuje CLOS (Common Lisp Object System), může být pro otevřené hlavy v lecčems inspirující. Vracení jiného objektu je naprosto běžnou záležitostí v objektově velmi čistém Smalltalku. Funguje to dobře a spolehlivě a nikdo se nad tím vůbec nepozastavuje, skoro bych řekl, že o tom ani neuvažuje a bere to jako tu nejpřirozenější věc v programování. "678 asString" prostě vrátí string, ačkoli jde o objekt typu integer, vezmu-li ten nejprimitivnější příklad.
-
Pokud přidávám prvek do konstantního seznamu a on tam pak není, pak se není čemu divit. Stejně jako by se nemělo být čemu divit, pokud např. zadávám parametry elipsy, jež nejsou povoleny v dané situaci, nebo se snažím nastavit hlavní a vedlejší poloosu kružnice na různé hodnoty.
Já se naopak výše popsaným způsobem snažím selský rozum neopouštět výměnou za rigidní výklad objektového modelu, který dědění chápe jen jako extenzi, což je právě ve sporu se selským rozumem. Jestliže objektu žárovka pošlu zprávu, aby se rozsvítila, ale ona je prasklá, tak nemůžu čekat, že obdržím stav, že je rozsvícena, když jsem ho tam přece jasně nastavoval. S tou elipsou je to stejné.
Pokud budu mít list, který bude obsahovat metody pro modifikaci, ale zároveň bude možnost, aby existovala implementace, která je nějakým způsobem nepovoluje, tak mě napadají 3 možné situace:
- vyhodím výjimku typu UnsupportedOperationException
- nevyhodím výjimku, ale indikuji neúspěch návratovou hodnotou
- neudělám ani jedno, prostě jsem potichu a nechám klienta, ať se s tím nějak popere sám
Třetí možnost je očividně evil, ale ani ty první dvě (ač jsou v javoském collections API velmi používané) nejsou žádná výhra.
Koncept "optional" metod sice ve své době měl určité opodstatnění, ale stačí se podívat, kolikrát byla na Stack Overflow řešena [img=http://stackoverflow.com/questions/2965747/why-i-get-unsupportedoperationexception-when-trying-to-remove-from-the-list]http://tahle otázka[/img].
Kdysi jsem na tohle někde četl kritiku, kde byl v nadsázce uvedený takovýhle příklad:
/**
* Solves all of the word's problems, but may be unsupported.
*
void solveAllWorldsProblems() {
throw new UnsupportedOperationException("sorry");
}
Ve chvíli kdy člověk musí přemýšlet nad tím, že nějaká metoda někdy nemusí udělat to, co se od ní očekává, případně může vyrazit výjimku, je prostě další věc, která zvětšuje stavový prostor programu, a tím pádem dělá jeho tvorbu a údržbu složitější a nákladnější.
Když si člověk jednou zkusí, jaké to je, pracovat s neměnnými datovými strukturami a funkcemi bez vedlejších efektů, nemusí při tom přemýšlet nad komplikovanou stavovostí programu a ještě jako bonus nemusí jak opičák všude cpát null checky, tak se pak těžko vrací zpět. :)
-
Doporučuji k prostudování jako zajímavost. Totálně odlišný pohled na OOP, jaký představuje CLOS (Common Lisp Object System), může být pro otevřené hlavy v lecčems inspirující.
Mě už OOP přestalo bavit. A před Lispem dávám přednost Erlangu, což je jediný opravdu objektový jazyk ;) (citace z článku níž)
Vracení jiného objektu je naprosto běžnou záležitostí v objektově velmi čistém Smalltalku. Funguje to dobře a spolehlivě a nikdo se nad tím vůbec nepozastavuje, skoro bych řekl, že o tom ani neuvažuje a bere to jako tu nejpřirozenější věc v programování. "678 asString" prostě vrátí string, ačkoli jde o objekt typu integer, vezmu-li ten nejprimitivnější příklad.
No právě. Proto jsem psal o té zpovykanosti z C++ - spousta lidí si myslí, že umí OOP a přitom to je spíš taková parodie. Asi není potřeba jít přímo do Smalltalku, ten člověk moc v praxi nevyužije, lepší je Objective C - Apple produkty prožívají hype, programátoři nejsou :)
Ještě pro zajímavost:
Alan Kay himself wrote this famous thing and said "The notion of object oriented programming is completely misunderstood. It's not about objects and classes, it's all about messages". He wrote that and he said that the initial reaction to object oriented programming was to overemphasize the classes and methods and under emphasize the messages and if we talk much more about messages then it would be a lot nicer. The original Smalltalk was always talking about objects and you sent messages to them and they responded by sending messages back.
http://www.infoq.com/interviews/johnson-armstrong-oop
-
P.S. hodně zajímavě vypadá i Go. Že se úplně odstřihlo od všech bláznivin s dědičností, je odvážný krok správným směrem. A automatické ztotožnění interfejsů se stejným obsahem? No to je přesně to, co by člověk od rozumného jazyka očekával :)
-
Alan Kay himself wrote this famous thing and said "The notion of object oriented programming is completely misunderstood. It's not about objects and classes, it's all about messages".
Je to blázen a to prokazatelně. Dneska takových bláznů pobíhá po světě mnoho, musíme si zvykat.
Koncept zpráv samozřejmě není špatný, ale není to podstata OOP.
-
Alan Kay himself wrote this famous thing and said "The notion of object oriented programming is completely misunderstood. It's not about objects and classes, it's all about messages".
Je to blázen a to prokazatelně. Dneska takových bláznů pobíhá po světě mnoho, musíme si zvykat.
Koncept zpráv samozřejmě není špatný, ale není to podstata OOP.
Tipuji, že ty jsi ještě běhal po houbách v lese hodně hlubokém, když Kay běhal po světě a v Xeroxích laboratořích v PARC se v 70. letech spolupodílel na utváření objektového konceptu v programování a GUI konceptu v ovládání počítačů.
Jistě by ho velmi obohatilo, kdyby ho hejsek jako ty školil z toho, co on vymýšlel, osočuje ho přitom z nepochopení té věci.
-
Koncept zpráv samozřejmě není špatný, ale není to podstata OOP.
V podstate mas pravdu. Zpravy nejsou podstatou toho, co DNES vetsina lidi za OOP povazuje. Ovsem byly podstatou toho, co kdysi Kay pod pojmem OOP mel namysli, a porad jsou podstatou tech jazyku, kterym se obcas rika "opravdu objektove".
Jinak Kay bude urcite rad, kdyz mu napises email a vysvetlis mu, co to vlastne OOP je. Skoda, ze uz nezije pan Ford, mohl's mu taky napsat vysvetleni, co je to pasova vyroba :))
-
To mě nebaví, dej sem nějaký důkaz jak se dojde k tomu že OOP je cituji "it's all about messages", nebo mlč.
Ovsem byly podstatou toho, co kdysi Kay pod pojmem OOP mel namysli, a porad jsou podstatou tech jazyku, kterym se obcas rika "opravdu objektove".
OOP vzniklo za účelem usnadnění práce se strukturami a ke znovupoužití kódu, to je historický fakt. Pan Kay si může myslet co chce.
-
Mě by spíš zajímalo, jaké výhody má v objektovém návrhu tvařit se, že kružnice nemá s elipsou nic společeného.
Nahraditelnost - tam, kde je potřeba instance typu T, můžete dát instanci jeho podtypu S. Pokud dodržujete LSP, tak tím neovlivníte korektnost programu.
Já chtěl nějaký argument pro podporu nepříbuzenského vztahu elipsy a kružnice, ne k podpoře toho, co jsem napsal. :-) Ano, v případě odvození kružnice od elipsy ji můžete použít na jejím místě, což může mít větší či menší smysl, ale není to principiálně špatně.
Vždyť to je - pokud porušujete LSP (což záleží na specifikaci), tak byste neměl typ používat jako podtyp. Dědičnost sama o sobě nevadí.
-
P.S. hodně zajímavě vypadá i Go. Že se úplně odstřihlo od všech bláznivin s dědičností, je odvážný krok správným směrem. A automatické ztotožnění interfejsů se stejným obsahem? No to je přesně to, co by člověk od rozumného jazyka očekával :)
Ano, má strukturální podtypový polymorfismus místo nominálního. Občas je to však také nevýhoda.
OCaml používá řádkový polymorfismus (row polymorphism), což je slabší než podtypový polymorfismus, ale v praxi to typicky nevadí a lépe to funguje s typovou inferencí.
-
To mě nebaví, dej sem nějaký důkaz jak se dojde k tomu že OOP je cituji "it's all about messages", nebo mlč.
Ovsem byly podstatou toho, co kdysi Kay pod pojmem OOP mel namysli, a porad jsou podstatou tech jazyku, kterym se obcas rika "opravdu objektove".
OOP vzniklo za účelem usnadnění práce se strukturami a ke znovupoužití kódu, to je historický fakt. Pan Kay si může myslet co chce.
The Smalltalk language, which was developed at Xerox PARC (by Alan Kay and others) in the 1970s, introduced the term object-oriented programming to represent the pervasive use of objects and messages as the basis for computation.
Jinými slovy, názor pana Kaye je názorem jedním z otců-zakladatelů OOP. V tomto světle působí tvůj názor na jeho názor poměrně legračně, nezdráhal bych se říci přímo hloupě.
-
To mě nebaví, dej sem nějaký důkaz jak se dojde k tomu že OOP je cituji "it's all about messages", nebo mlč.
Ovsem byly podstatou toho, co kdysi Kay pod pojmem OOP mel namysli, a porad jsou podstatou tech jazyku, kterym se obcas rika "opravdu objektove".
OOP vzniklo za účelem usnadnění práce se strukturami a ke znovupoužití kódu, to je historický fakt. Pan Kay si může myslet co chce.
The Smalltalk language, which was developed at Xerox PARC (by Alan Kay and others) in the 1970s, introduced the term object-oriented programming to represent the pervasive use of objects and messages as the basis for computation.
Jinými slovy, názor pana Kaye je názorem jedním z otců-zakladatelů OOP. V tomto světle působí tvůj názor na jeho názor poměrně legračně, nezdráhal bych se říci přímo hloupě.
Pojmy se vyvíjejí. Podívejte se třeba na pojem funkce v matematice dnes a v době Eulera - dříve bylo běžné, že funkce může mít více různých hodnot v jednom bodě.
-
OOP vzniklo za účelem usnadnění práce se strukturami a ke znovupoužití kódu, to je historický fakt. Pan Kay si může myslet co chce.
Pan Kay ví i co se při vzniku OOP obědvalo. Být tebou, už bych ze sebe nedělal většího vola než už se ti podařilo. I když vlastně jako anonym to máš jedno - při nejhorším si dáš nový nick no...
Ano, má strukturální podtypový polymorfismus místo nominálního. Občas je to však také nevýhoda.
Jaké to má nevýhody? Že je složitější určit (pod)typ v úplně dynamických případech, kdy o objektu předem nic nevím?
Pojmy se vyvíjejí. Podívejte se třeba na pojem funkce v matematice dnes a v době Eulera - dříve bylo běžné, že funkce může mít více různých hodnot v jednom bodě.
To je sice pravda, ale posílání zpráv je silný koncept s mnoha důsledky. Od možnosti dynamického dispatchingu zpráv až po funkční oddělení modulů programu. To, že se na posílání zpráv pozapomnělo, je jenom ke škodě.
-
Ano, má strukturální podtypový polymorfismus místo nominálního. Občas je to však také nevýhoda.
Jaké to má nevýhody? Že je složitější určit (pod)typ v úplně dynamických případech, kdy o objektu předem nic nevím?
První nevýhoda je, že při (náhodné) shodě rozhraní mohu omylem dávat instance jednoho rozhraní místo instancí druhého rozhraní, i když spolu vůbec nesouvisí. Lepší by IMO bylo, aby se muselo explicitně někam vypsat, co je podtyp čeho, a kompilátor by to zkontroloval a udělal z toho reflexivní tranzitivní uzávěr (neboť relace býti podtyp je předuspořádání).
Druhá nevýhoda je, že implementace jsou méně efektivní. Problém je v tom, že různé instance mohou mít různé metody a ten, kdo chce tyto metody volat, musí nějak zjistit jejich adresy. Např. OCaml to dělá tak, že během kompilace zahashuje jména všech metod, a když se má nějaká metoda zavolat, tak se v hashtabulce dané instance najde její adresa. Go to dělá tak, že při přetypování instance na nějaké rozhraní vygeneruje vtabulku pro dané rozhraní - pokud to nejde staticky, tak se to dělá za běhu. Haskell to dělá tak, že pro každou typovou třídu (cca rozhraní) udělá vtabulku a tyto vtabulky předává implicitně jako další argumenty společně s hodnotou.
-
Díky za vysvětlení, aspoň neumřu blbej :)
-
Jestli chceš s něčím opravdu poradit, pokládej otázku třeba na stackoverflow.com. Když bude blbá, řeknou ti to tam hned. Když ti budou chtít poradit, udělaj to jasně a stručně, tady ta onanie, to je opravdu k ničemu.
-
Takto obecné otázky (= "učím se programovat") bych dal spíš na http://programmers.stackexchange.com/ - patří to pod stejnou skupinu (stackexchange), ale tato síť se zaměřuje spíš na obecné otázky, než na konrétní technické problémy.