Devel.cz Lupa Měšec Podnikatel Root Zdroják.cz DigiZone Slunečnice Vitalia TopDrive KupDnes Navrcholu NovýTarif Dobrý web Weblogy Woko Jagg Computer.cz SK: MojeLinky

Hlavní navigace

Vlákno názorů k článku
Třídy, dědičnost a OOP v Javascriptu - I

junix
junix (neregistrovaný) ---.sun.com
17. 3. 2010 17:15

OOP, dedicnost

Podlehl jsem stejnemu pocitu, jako vetsina diskutujicich, ze vsemu rozumim nejvice :) a mam potrebu se vyjadrit jak k clanku, tak k diskusi.

1. K diskusi – vede se tu plamenna debata o tom, jestli ma javascript tridy, o nejakem „klasickem OOP“ a „prototypickem OOP“ a ze jedno ma polymorfismus a druhe ne a podobne nesmysly.

OOP je jedno. Jedna se o obecny pristup, ne o konkretni podobu v nejakem jazyce. Vychazi predevsim ze dvou veci: objektu a jejich chovani – polymorfismu. Nic vic.
Pak jsou tu dva sekundarni principy – zapouzdreni, a dedicnost.
U dedicnosti vidime rozdily, ktere tu zatim nikdo spravne nepojmenoval.

Mame tu tridne-instancni dedicnost (C++, Java, Ruby, Python, SmallTalk, CLISP atd.), kde je dedicnost staticka, definovana mezi tridami. Objekty jsou pouze instance. To je mimochodem princip velmi vzdaleny realite, ale jednoduseji realizovatelny.

Druhy typ je dedicnost prototypicka (self, IO), ktera je dana primo vztahy mezi objekty. Objekt A je potomek objektu B, protoze B je jeho prototyp. To je urcite neco blizsiho realite.

Ano, dedime nektere rysy lidskeho druhu, ktery je zdedil z opice (tridne-instancni pripad). Spis ale dedime konkretni rysy a vlastnosti od nasich rodicu, kteri je zdedili od svych a az nekde hloub byl nejaky predek konkretni opice atd (prototypicky pripad).

Ve skutecnosti jsou tyto dva pripady pouze hranicni body, a konkretni implementace dedicnosti v ruznych jazycich je posunuta k tedne, nebo druhe strane:

C++: Ciste tridne-instancni, tridy jsou pouze definice, pracuje s nimi pouze kompilator, nejsou to objekty.
Instance nejsou vytvareny tridami, ale specialni konstrukci jazyka – operatorem new.

Java: Uz je o krucek jinde. Tridy maji sve zapouzdrujici objekty, umoznujici s nimi „nejak“ pracovat za behu.
Objekty jsou stale vytvareny operatorem new

SmallTalk, Ruby: Tridy uz jsou plnohodnotne objekty. Vytvareji sve instance svymi metodami a ne zadnym spec. operatorem.

JavaScript: Je bliz protoypicke dedicnosti, ale neni to uplna PD. Proc? Protoze prototyp neprirazujeme objektu, ale jeho konstruktoru.

IO, Self: Ciste prototypicka dedicnost – objekty se vytvareji klonovanim existujicich objektu a pridavani dalsich vlastnosti.
Klonovanim vznikne objekt pouze s referenci na prototyp. Nove vlastnosti se ukladaji do nej.

Kdyz se tedy vratim k tomuto problemu v JavaScriptu, tak je videt, ze je napul cesty. Neni ani tridne-instancni, ani striktne prototypicky (prestoze se tak vetsinou oznacuje).
Kdyz v JS vytvarime novy objekt, neni to „z jineho objektu“ ale „nejakym zpusobem“, pomoci funkce oznacene jako konstruktor. Tento princip je stale blizsi tridne-instancnimu modelu a tudiz neni nijak zavadejici, pouzivat pro funkci, kterou pouzivam jako „vytvarejici“, oznaceni Trida. Je to naopak vhodnejsi, nez mast ctenare hned komplikovanou prototypickou dedicnosti, ktera stejne nektere prvky te tridne-instancni obsahuje.

I v ostatnich vecech, ktere jsou clanku vytykany, se vetsinou jedna o omyly. Snad byly vsechny vyjasneny ostatnimi prispevateli.

Osobne si myslim, ze to, co bylo predmetem tohoto dilu clanku, bylo vysvetelno dobre. Jestli je clanek spravne usporadan, to asi kazdy vnima jinak. Myslim, ze pokud ho docte cely, tak uz mu porozumi, a urcite bude lepsi uceleny dojem, az vyjdou i ostatni dily.

Co bych vytkl ja, je krome drobnosti (to je spis doplneni), ze ve scopu najdete i parametry nadrazene funkce (cili zavadeni lokalni promenne, do ktere se tyto ukladaji, je zbytecne a da se pracovat s parametry name primo), predevsim hodnoceni, ze nektere pristupy jsou spatne a nektere dobre.

V teto diskuzi jsou vsichni odbornici provereni praxi, a tak se i ja budu ohanet tou svou :), ve ktere jsem se uz setkal
s nescetne situacemi, kdy byly vyhodne ruzne pristupy k reseni
dedicnosti. At uz vytvareni vzdy novych objektu, jako je to v prvnim spatnem prikladu – mimochodem pokud chcete rozsirovat nektere built-in objekty prenositelne, tak se tomu nevyhnete, tak jsem casto vyuzil uzavery a metody jako jejich rozhrani.

Je to otazka konkretni situace a ne, ze jedno je spatne, a druhe dobre (resp. treti, co bude prezentovano priste).

Na dalsi dily se urcite tesim a i tento prvni hodnotim kladne.

Daniel Steigerwald aura:52
17. 3. 2010 17:56

Re: OOP, dedicnost

Díky ze vynikající a zevrubný komentář. Jak prototypová dědičnost v Javascriptu přesně funguje, tomu se budu věnovat v druhém článku. A myslím, že vypíchnu jednu charakteristiku, která je naprosto zásadní, ale jenom málokdo si ji uvědomuje.

S tím zdvojením name a p_name máte samozřejmě pravdu, name není nutné. Zde však slouží jako ilustrace, protože v „tom správném“ příkladě (který bude v druhém díle), se lokální name stane instanční vlastností ( this.name = name).

K hodnocení že tyto techniky jsou špatné, mělo asi zaznít (velkými písmeny a červeně ;), že jsou špatné pro tvorbu tříd. Ještě přesněji, „zneužití closure“ má význam pouze pro funkcionální programování, v nějakém algoritmu, nebo tam kde potřebuji callback. I v takových případech pak ale nehovoříme o tvorbě tříd. Co se týká techniky druhé, vytváření metod v konstruktoru. Tato technika je dle mého špatná vždy. Respektive nevybavuji si jediné její oprávněné použití. Ale rád se nechám poučit, pokud o nějakém takovém využití víte, sem s ním.

Co se týká dědění vestavěných objektů, které máte na mysli? Funkci podědit nelze. Pole ano, ale nebude správně fungovat v Internet Exploreru. Čísla, řetězce, ani booleany jsem dědit nikdy nezkoušel.

junix
junix (neregistrovaný) ---.sun.com
17. 3. 2010 18:26

Re: OOP, dedicnost

Pokud trida je takova funkce, kterou pouzivame pri vytvareni objektu pomoci operatoru ‚new‘, pak prvni zpusob nejenze je spatne, ale ani nelze pouzit. To mate pravdu.
Na druhou stranu vytvareni objektu pomoci ‚new‘ neni jediny univerzalni zpusob. Kuprikladu DOM objekty muzete vytvaret pouze pomoci funkci, takze pro vytvoreni konkretniho DOM objektu (napr. DIV element s konkretni css tridou a id) budete muset vytvaret necim podobnym, jako je ten prvni priklad.

Druhy priklad se s vyhodou pouzije nejen u callbacku, ale rozhodne pri elegantnim reseni navrhoveho vzoru state, cili implementaci stavoveho polymorfismu.

Rozhodne se mi libi, ze v JS muzete mit stavovy polymorfismus nejen pro konkretni objekt, ale pro celou „tridu“. Ale to uz nesouvisi s uvednymi priklady.

Vít Šesták (v6ak) aura:72
17. 3. 2010 18:38

Re: OOP, dedicnost

No rozhodně není nutné, abych instanci vytvářel přes new. Existuje tu Factory, což umožňuje některé zajímavé věci v přesunu zodpovědnosti.

Daniel Steigerwald aura:52
17. 3. 2010 18:53

Re: OOP, dedicnost

První odstavec, souhlas (o DOM elementech ale nebyla řeč, to je jaksi mimo Javascript).

Druhý odstavec, chci vidět kód, stačí ilustrativní. Myslím totiž, že nemáte pravdu. Neznám pattern, jehož implementace by vyžadovala vytváření method v konstruktoru (o tom by druhý špatný příklad). Možná se pletu, ale bez ukázky kódu se nepohneme dál.

Třetí odstavec. Přesouhlasím, viz. druhý díl.

junix
junix (neregistrovaný) ---.174.broadband13.iol.cz
17. 3. 2010 23:52

Re: OOP, dedicnost

Ano, s tim stavovym polymorfismem pomoci uzaveru jsem se spletl.

aprilchild
aprilchild (neregistrovaný) ---.zapcechy.adsl-llu.static.bluetone.cz
17. 3. 2010 21:33

Re: OOP, dedicnost

muzes Dane prosim rozvest, „zneuziti closure“ pro funkcionalni programovani? Resp. uvest nejaky priklad? Mam na mysli ciste funkcionalni priklad, bojim se, ze pouziti closure jde presne proti jeho duchu, zaklinadlem budiz immutable state a no side effects. Neni mi jasne, k cemu bych v cistem navrhu potreboval videl do scope o uroven vys. Ale to je jen spis takova perlicka na zaver – pro puristy:).

Timy _ aura:74
18. 3. 2010 1:16

Re: OOP, dedicnost

Co třeba toto? Je to v Lispu, ale mělo by to být srozumitelné (násobí to všechny prvky seznamu coef), mapcar dělá prakticky totéž co array.each v MooTools nebo jQuery.

(defun multiply (coef list)
        (mapcar (lambda(x)
                   (* x coef))
           list))

> (multiply 2 '(1 2 3 4))
(2 4 6 8)

Do toho mapcaru (each) posílám anonymní funkci, která musí vidět o scope výše, aby se dostala ke  coef.

aprilchild
aprilchild (neregistrovaný) ---.zapcechy.adsl-llu.static.bluetone.cz
18. 3. 2010 12:26

Re: OOP, dedicnost

Diky!

Daniel Steigerwald aura:52
18. 3. 2010 15:21

Re: OOP, dedicnost

Petře, já tvou otázku neignoruji, jen mám rozečteného Tomáše Petříčka, a chtěl bych svou odpověď čeknou, jen co jej dočtu (někdy dnes;)

Daniel Steigerwald aura:52
20. 3. 2010 23:23

Re: OOP, dedicnost

Nepovažuji se za odborníka na funkcionální programování, ale mám pocit, že koncept closure se nijak nebije s immutable state. Ani Tomáš Petříček to ve své knize nikde nezmiňuje.

8.2.2 Capturing state using closures in F#
In this section we're going to talk about closures, which is an important concept in functional programming. Closures are very common and most of the time they aren't used with mutable state. However, working with mutable state is sometimes needed for the sake of pragmatism and closures give us an excellent way to limit the scope of the mutable state.

(Functional Programming for the Real World)

Docela mě potěšilo, že Tomáš zmiňuje vlastně to samé, co jsem zmiňoval v úvodu svého seriálu. OOP a funkcionální programování nejdou proti sobě. Kdy je tedy správné použít closure? Prostě kdykoliv se mi hodí, aby funkce viděla do scope vyšší úrovně. Dejme tomu, že máme funkci, která využívá jQuery. To, že funkce vidí jQuery z globálního scope je přeci úplně intuitivní, ne? Dále, funkce pracuje s nějakými daty, které jsou definované vně funkce. To, že na ně vidí, je opět velmi praktické. Ale ty chceš příklady, ne kecy, rozumím :)
Tak třeba ten each, přepsaný do Javascriptu:

var multiply = function(number, array) {
    return array.map(function(item) {
        return item * number; // na number je nutné vidět :)
    });
};
alert(multiply(2, [1, 2, 3, 4])); 

Lze si představit mnoho obměn, typicky array.sort třeba. Další příklady jsou variantou techniky známé jako currying. V javascriptu ji používáme hodně, tak často, že si to už ani uvědomujeme ;) Většina příkladů na webu jsou totiž podivně nepraktické příklady typu: chci mít funkci, která mi vytvoří funkci, která bude přičítat nějaké číslo. Třeba takto:

var makeAddFn = function(number) {
    return function(value) {
        return value + number;
    };
};
var add2 = makeAddFn(2);
alert(add2(1)); 

K čemu je takový zápis dobrý? K ničemu! ;) Skládat algoritmus z předpřipravených generovaných funkcí je (až na výjimky) cesta do pekla. Tohle je práce pro třídy, dědičnost, agregaci atd.

Nutnost, nebo spíše krásu closure si uvědomíme v okamžiku, kdy píšeme asynchronní kód. Typicky volání serveru, animace, opožděné zobrazení něčeho atd.. díky closure můžeme provázat vyvolání akce s asynchronním obsloužením výsledku, aniž bychom museli vytvářet nějakou novou datovou strukturu, do které bychom vazbu explicitně ukládali.

var save = function(id, data) {
    server.call('user.save', data, function() {
        User.lookUp(id).update(data);
    });
}; 

Díky closure si nemusíme ukládat id někde bokem, případně si jej vracet ze serveru.

Petr Krontorad
21. 3. 2010 23:27

Re: OOP, dedicnost

Diky, vicemene se tvuj (a ostatnich) pragmatismus ohledne closures shoduje s mym nazorem, byt treba v Erlangu bys asi brzy narazil (ne kazdy jazyk je tak ohebna bestie jako JavaScript). Closure pro mutable state je nicmene vytvareni side effects a „ciste“ funkcionalni programovani to neni. Ale za definice nikdo hlavy utinat nebude. Pragmatismus necht (z)vitezi..;).

Vít Šesták (v6ak) aura:72
22. 3. 2010 15:36

Re: OOP, dedicnost

Pořád nechápu, co je na tom za side effect.

Petr Krontorad
23. 3. 2010 9:27

Re: OOP, dedicnost

Pokud pomoci closure ctu neco v odlisnem scope, side effect nevznika (splnena podminka nemennosti). V pripade, ze stav menit budu (kdybych napr. o uroven vys zvysoval counter apod.), side effect vytvarim a purista by o mem kodu rekl, ze neni funkcionalni. Jsou jazyky, kde jsem tohoto pokuseni usetren (promenne jsou automaticky immutable), v nekterych (js) to mozne je (a v nekterych si restrikce urcuju sam – Scala).

Nekdy se to „necista“ praktika zasahu do stavu jineho scope muze hodit – pokud znam dusledky takoveho pouziti, ale obecne je lepsi se ji vyhybat (lze ji vicemene bezpecne pouzit jen tehdy, kdy jsem si jist, ze dany kod pobezi ‚synchronized‘ pouze v jednom procesu v jeden cas).

Ale to uz je debata o necem jinem, diskuzni vlakno vzniklo jen jako reakce na pouziti closures ve funkcionalnim programovani, ktere Dan zminil.

Vít Šesták (v6ak) aura:72
23. 3. 2010 16:45

Re: OOP, dedicnost

Jo, ale stejnětak může side effect vzniknout bez closure, třeba v párech (Scheme).

Zasílat nově přidané příspěvky e-mailem