Triedne vs. Prototypové OOP

Logik

  • *****
  • 1 049
    • Zobrazit profil
    • E-mail
Re: Triedne vs. Prototypové OOP
« Odpověď #15 kdy: 14. 03. 2011, 18:36:27 »
Ještě doplním, že ale možná svým způsobem máš pravdu - velmi často takové vnitřní stavové proměnné, které je pak v potomku třeba modifikovat - ukazují na chybu návrhu. Daná funkcionalita pravděpodobně měla být "vyjmuta" z objektu jako samostatný podobjekt a přidaná agregací (a tedy bezpečně zapouzdřená a přitom snadno měnitelná)


Sten

Re: Triedne vs. Prototypové OOP
« Odpověď #16 kdy: 15. 03. 2011, 12:35:26 »
S tim public const nemáš IMHO pravdu. Jeden z cílů, proč je to tak udělané je, aby uživatelé nepoužívali místo veřejných a zaručených metod střeva. Protože střeva se můžou klidně změnit. Používáním "nedokumentovaných" střev si zakládáš na pořádnej problém... a protected proti tomu chrání.... (nicméně někdy, např. pro účely ladění, by se to hodilo, to zas jo).

public const jsem myslel místo
Kód: [Vybrat]
public:
    const std::string& getName() const {
        return this->name;
    }

protected:
    std::string name;

V tomhle případě by name mohlo být dokumentované a const přístupné a nemusel bych psát getter. Na druhou stranu nechci, aby mi to někdo měnil, a const std::string name použít nemohu, protože by to znemožnilo přiřazování.

Stejně tak private má svůj smysl. Pokud máš nějakou vnitřní stavovou proměnou a chceš zajistit, aby Ti ho žádný potomek obejktu nerozbil, tak to použiješ. Něco podobného je i v o dost mladší javě (final).

Zatím jsem nikdy nepotřeboval něco skrývat před potomky (a ani před programátory, kteří mé knihovny používali). Stejně tak jsem nepochopil, k čemu je dobrý final, než naštvat programátora, který přijde po vás, že si to nemůže podědit a upravit, ale musí si to napsat celé znovu, když se vaše úžasná třída přesně nehodí jeho potřebám.

Pokud si to potomek rozbije, je to jeho zodpovědnost a jeho problém; pokud potomek sáhne do nealokované paměti, tak celý program taky spadne a je to problém toho potomka. Spíše mám opačné zkušenosti — už několikrát se mi stalo, že by se mi hodilo v konkrétním případě zasáhnout do vnitřní stavové proměnné v poděděném objektu (často cizím) a nešlo to, takže jsem tam před #include musel dát
Kód: [Vybrat]
#define private protected
Co se týče sahání do nedokumentovaných střev, tak s tím bych problém neviděl, moje objekty nedokumentovaná střeva nemají a když se nějaká střeva změní, tak se holt rozbije program (nebo překlad) stejně jako když se změní veřejné metody (stejně i změna private memberů znamená novou verzi ABI).

Třeba STL má implementation specific střeva, ale to neznamená, že by se na ně nesmělo sahat. Napadá mě jedno řešení: některá střeva označit, že jsou implementation specific, a když se je pokusíte použít implicitně (dědičností), tak by program vyvolal varování (nebo klidně i error), ale explicitně (se jménem objektu) by použít šly, takže by pořád bylo možné je upravovat:
Kód: [Vybrat]
class SomeObject
{
protected:
    specific std::string _data;
};

class Inherited
    : public SomeObject
{
public:
    void someFunc()
    {
        this->_data = "a"; // Varování, použito implicitně
        this->SomeObject::_data; // OK, použito explicitně
    }
};

Sten

Re: Triedne vs. Prototypové OOP
« Odpověď #17 kdy: 15. 03. 2011, 12:58:56 »
Ještě doplním, že ale možná svým způsobem máš pravdu - velmi často takové vnitřní stavové proměnné, které je pak v potomku třeba modifikovat - ukazují na chybu návrhu. Daná funkcionalita pravděpodobně měla být "vyjmuta" z objektu jako samostatný podobjekt a přidaná agregací (a tedy bezpečně zapouzdřená a přitom snadno měnitelná)

Teď mě napadl jeden případ, kdy by to vyjmout nešlo a bylo potřeba sahat do vnitřních stavových proměnných: HTTP klient.

Běžný HTTP klient má nějakou stavovou proměnnou ukazující, jestli jestli odesílá hlavičky nebo data nebo jestli je přijímá. Samozřejmě private, do toho mu přeci nemá kdo sahat, ne?

Jenže já jsem potřeboval toho HTTP klienta upravit tak, aby umožňoval HTTP autorizaci. Nejlépe poděděním, aby se dal onen HTTP klient snadno nahradit ve funkcích, které ho přijaly referencí (ty zase byly v jiné knihovně a já neměl možnost je upravovat). A tam jsem narazil, protože pro HTTP autorizaci musíte odeslat dotaz nejméně dvakrát (v první odpovědi se dozvíte, jak vůbec autorizovat). Jak tohle chcete řešit jinak než po přečtení hlaviček zahodit odpověď, resetovat klientovi jeho vnitřní stav a poslat nový požadavek s vyplněnými autorizačními hlavičkami, ale zároveň být schopen vrátit přečtená data, pokud server HTTP autorizaci nepožaduje?

alefo

Re: Triedne vs. Prototypové OOP
« Odpověď #18 kdy: 15. 03. 2011, 13:44:54 »
Stavové premenné si môžete odosielať cez premenné. Je to síce skoro ako procedurálne programovanie, ale je to thread-safe a navyše trieda môže slúžiť ako singleton.

Takto napr. funguje Spring: tam je tona tried, ktorá v stavových premenných udržuje len konfiguráciu a závislé inštancie.

Logik

  • *****
  • 1 049
    • Zobrazit profil
    • E-mail
Re: Triedne vs. Prototypové OOP
« Odpověď #19 kdy: 15. 03. 2011, 13:47:31 »
Citace
...public const jsem myslel místo
Jenže jsou případy, kdy to z nějakého důvodu mít dokumentované nechceš (třeba právě proto, abys měl svobodu to v další verzi třídy změnit) a pak to prostě vystavit nesmíš. Proto má protected tak, jak je, smysl.
Navíc nikdy nevíš, kdy bude třeba jednoduchou vlastnost najednou nahradit komplexnější logikou (takže najednou nebude prostá proměnná stačit, protože ji např. nestihneš rozumně aktualizovat). Takže prostě z principu vystavit to jako vlastnost je špatně.

Samozřejmě, že se to v praxi často dělá, protože zabalovat každou blbinu je "zbytečná práce", nese to s sebou ale riziko toho, že si člověk přidělá práce daleko víc. Určitým řešením by byly property, to ale v tak jednoduchym jazyce jako C++ nejde udělat moc dobře zaměnitelně s s prostou hodnotou.

---

Ad úprava střev: samozřejmě, za "bezpečnost" kódu se platí. Někdy je výhodnější udělat kód bezpečnej, někdy ne. Liší se to i mj. podle metodiky vývoje a dalších hafo věcí (velikosti pracovního týmu, výši pokuty od zákazníka za chybu :-)). To, že ty to nepoužíváš (já třeba také v podstatě ne) ale neznamená, že to nemá smysl. Jen to prostě není mechanismus vhodný pro každý případ.

Co se týčen utnosti šahat do střev - pokud je knihovna kvalitně udělaná, tak by to nemělo být potřeba....


ondra.novacisko.cz

Re: Triedne vs. Prototypové OOP
« Odpověď #20 kdy: 15. 03. 2011, 14:56:20 »
Namísto public const bych viděl smysl v propertách. To jsou (pro ty, co nevědí o co jde) virtuální proměnné, které maskují getter a setter. Těm se dá třeba definovat jen getter, pak fungují jako const, a přitom se jejich vnitřní hodnota může měnit.

Property pak jsou cestou, jak se zbavit getterů a setterů úplně. Nevýhodou je, že programátor pak nemá jasnou představu, co vlastně přiřazení udělá, jak je náročné a zda nemá nějaké sideefekty. Ale to je v C++ normální, a platí zlaté pravidlo, že přetížená operace by měla primárně dělat to, na co byla původně určena. Přiřazení přiřazuje, hvězdička násobí šipítka pošoupají (ať už bity, nebo do streamu)

Bone Flute X

Re: Triedne vs. Prototypové OOP
« Odpověď #21 kdy: 15. 03. 2011, 20:35:42 »
Třída, potažmo instance z ní může mět různé užití, a podle toho ji postavím. Například mohu mět konfigurační třídu, která má všechny members public, páč je to jen přepravka a žádnou validaci tam neřeším. Na druhou stranu ve většině dalších kategorií tříd (jakých, teď z fleku nedám) veřejné members nepoužiju a místo nich mám gettery a settery. Zvláště v jazycích jako je C++. V případě C#, kde se již dá dělat magie, to je jiná.

Public metody slouží jako veřejné api, pro užití instancí. Protected metody slouží k předefinování vnitřních věcí, jako jsou template metody, nebo creatory a podobně a definují tedy jakési vnitřní api. Ale rozhodně to není to samé jako private metody, které slouží k schování konkrétní implementace. Tedy na příkladu, když budu implementovat nějakou třídu a v ní budou některé logické protected, nebo public metody, a pro jejich implementaci si vytvořím nějaké pomocné další metody, tak je určitě nebudu dělat protected, ale private.

Vyjádření, že všechny metody by měly být protected, když nejsou veřejné, a nepoužívat private beru jako úlet. Všechno má své místo a svůj smysl.

Logik

  • *****
  • 1 049
    • Zobrazit profil
    • E-mail
Re: Triedne vs. Prototypové OOP
« Odpověď #22 kdy: 15. 03. 2011, 20:38:34 »
Osobně víc než property se mi líbí princip automatického generování setterů/getterů pomocí anotací. Právě kvůli tomu, že se to netváří jako něco jiného.

Obzvlášť v C++, před property se např. nedá dát referenčítko, takže nahrazení proměné pomocí property není "bezpečná" věc.

Inkvizitor

Re: Triedne vs. Prototypové OOP
« Odpověď #23 kdy: 16. 03. 2011, 03:01:30 »
Už je to asi trochu out, ale dlužím odpověď na příspěvek. Modifikace cizích knihoven je hnus, ideální samozřejmě je, když lze chování změnit například v potomkovi třídy. Docela zajímavé možnosti skýtá třeba implicitní konverze ve Scale: http://paulbarry.com/articles/2009/04/17/implicit-conversions-scalas-type-safe-answer-to-rubys-open-class - to samozřejmě neřeší všechno.

Po X letech (není to římská číslice, ale skoro to sedí) s Pythonem můžu říct, že bych se některých cool dynamických vlastností rád zbavil - už asi půl roku přemýšlím, že bych napsal článek do blogu, kde bych vysvětlil, proč IMO hlavní výhoda dynamických jazyků není v jejich dynamičnosti, nýbrž že ty statické (vesměs) zaspaly dobu; v praxi jsem zažil jenom jeden jediný případ, kdy bych například chtěl měnit metody nějakého objektu - případ je zčásti srovnatelný s tou knihovnou, ale jde o online update běžícího programu.

Mixiny jsou fajn a že některé OO jazyky nepodporují ani opravdovou násobnou dědičnost ani mixiny, to je trestuhodná věc.

Já chápu, že v praxi se uplatní leccos včetně chlíváren typu goto a drsného monkey patchingu, ale ta možnost zneužití a zavlečení chyby je obrovská.

Logiku, dlouho jsem nečetl na Rootu příspěvek, který by mě doopravdy vyděsil. Až teď ten Tvůj.  ;D
:-) a co z toho je špatně? Vím, že třeba modifikace cizích knihoven je "nestandardní" postup, pro kterej by měl mít člověk opravdu, ale opravdu důvod - ale někdy rozumější cesta není. Nebo Ty máš nějakej návrh, jak lépe modifikovat cizí knihovny? Přijde Ti lepší varianta modifikovat přímo její kód a při příštím upgradu knihovny strávit další hodiny aplikací patche a kontrolou, že vše proběhlo tak, jak má? A nebo radši tu cizí knihovnu nepoužiješ a ztrávíš x hodin psaním jejího duplikátu?

Co máš např. proti implementaci mixings už nevím vůbec :-) A pokud nic, tak jak ho budeš implementovat v jazyce, kterej to neumí nativně? (např. javascript)?

Logik

  • *****
  • 1 049
    • Zobrazit profil
    • E-mail
Re: Triedne vs. Prototypové OOP
« Odpověď #24 kdy: 16. 03. 2011, 12:12:54 »
Modifikace cizí knihovny je sice hnus, ale když objekty z té knihovny vytváří někdo, koho taky nemůžeš modifikovat, tak buďto ve výsledku přepíšeš celou knihovnu, nebo změníš tu jednu metodu "inplace". Tady to považuju i za bezpečnější variantu, protože procházet cizí knihovnu a hledat, který všechny metody vytvářej objekt A, přepsat je, aby vytvářely objekt B - a protože tim se změnila implementace dalších tříd, tak furt dokola....

Ale jinak souhlasím, todle je "extrémní technika" a člověk by ji měl použít pouze v extrémním případě - už jsem se ale setkal s případem, kdy kdybych ji mohl použít, tak by mi to ušetřilio dny práce.

S tím, že ochrana objektů je užitečná souhlasím, ale jde něco za něco. Někdy se to hodí a někdy je to na obtíž.

Jinak daleko důležitější vlastnost prototypových jazyků je možnost předefinovat danou metodu nikoli u třídy, ale u konkrétní instance. A to má hafo použití - kvůli každý blbině se nemusí definovat vlastní třída: notabene když chce člověk hromadu objektů, kdy každej z těch objektů má některý metody předefinovaný (různý validátory, callbacky apod.). Samozřejmě, že to jde obejít pomocí ukazatelů na funkce - ale prototypový přístup je jednodušší a čistší.
Další využití je např. u různých vyvýjejících se objektů: místo aby byl v metodě switch přes stav, tak se při změně stavu objektu změní patřičnej handler apod.

Výhody dynamických jazyků jsou, akorát člověk nesmí myslet staticky. Samozřejmě, když bude člověk v dynamickém jazyku programovat jako v pascalu, tak mu zbydou jen nevýhody.

Inkvizitor

Re: Triedne vs. Prototypové OOP
« Odpověď #25 kdy: 17. 03. 2011, 10:03:07 »
Jinak daleko důležitější vlastnost prototypových jazyků je možnost předefinovat danou metodu nikoli u třídy, ale u konkrétní instance. A to má hafo použití - kvůli každý blbině se nemusí definovat vlastní třída: notabene když chce člověk hromadu objektů, kdy každej z těch objektů má některý metody předefinovaný (různý validátory, callbacky apod.). Samozřejmě, že to jde obejít pomocí ukazatelů na funkce - ale prototypový přístup je jednodušší a čistší.
Další využití je např. u různých vyvýjejících se objektů: místo aby byl v metodě switch přes stav, tak se při změně stavu objektu změní patřičnej handler apod.

Výhody dynamických jazyků jsou, akorát člověk nesmí myslet staticky. Samozřejmě, když bude člověk v dynamickém jazyku programovat jako v pascalu, tak mu zbydou jen nevýhody.

No tak můžu Tě ujistit, že jako v Pascalu nepíšu. Podle mě ale dynamické jazyky většinu problémů prostě obcházejí. Do pole je možné vložit za sebe reference na libovolné objekty, ale v praxi prakticky nikdy nepotřebuješ do pole vkládat libovolné objekty, ale spíš objekty z poměrně malé množiny. Daleko lepším řešením pak jsou například algebraické typy.

Řešení callbacku referencí na funkci nebo anonymní třídou mi přijde naprosto v pohodě.

Logik

  • *****
  • 1 049
    • Zobrazit profil
    • E-mail
Re: Triedne vs. Prototypové OOP
« Odpověď #26 kdy: 17. 03. 2011, 13:33:00 »
Řešení callbackem sice jde, ale srovnej kód:

Kód: [Vybrat]
function nothing() {};
class C
   {
   void (* callback)();
   C() { callback = nothing; }
   }
oproti

Kód: [Vybrat]
class C
   {
   callback() {};
   }
Druhá možnost je daleko jednodušší a bez rizika, že zapomeneš callback inicializovat (byť v nějakém jazyku s anonymními fcemi by to asi nebyl tak velký rozdíl). Spotřeba paměti s callbackem je také větší (musíš držet všechny callbacky, ne jen ty předefinované). No a javovské anonymní třídy maj ještě složitější syntax, byť mají také své přednosti...

Vypadá to jako blbnutí o pár znacích, ale když má daná třída těch callbacků 10, každej s jinejma argumentama, ale z toho se ale reálně použije vždy tak jeden dva, tak je rozdíl v čitelnosti značnej.

Na co narážíš aritmetickými typy nevím, v každém případě směšuješ silné a slabé typování se statickým a dynamickým jazykem. Je sice pravda, že většina slabě typovaných jazyků je dynamických a vice versa, ale platit to nemusí.

PS: Já netvrdím, že to v statickém jazyce nejde, jen tvrdím, že dynamický přístup je prostě přímočařejší. Řešení s callbackem je prostě řešení "za roh", až na to, že jsou na něj všichni zvyklý a tak nám nepřipadá divný. Proč ale když měním chování objektu, tak bych měl měnit vlastnost? Proč jsou měnitelné a neměnitelné metody objektu implementovány úplně jinou technikou?

Inkvizitor

Re: Triedne vs. Prototypové OOP
« Odpověď #27 kdy: 17. 03. 2011, 22:14:43 »
Ještě existuje 3. možnost a tu preferuji já; předat ukazatel (referenci v Pythonu apod.) na tu funkci přímo v konstruktoru. To zaručuje, že inicializovat nezapomeneš. V praxi se mi to velice osvědčilo. Výborné je to v Pythonu, který má lambdy atd.

O aritmetických typech jsem nemluvil, toliko o algebraických: http://en.wikipedia.org/wiki/Algebraic_data_type.

Co se týče statických a dynamických typových jazyků, samozřejmě, že mají výhody i nevýhody. U dynamických jazyků mi vadí hlavně to, že jejich dynamičnost většinou nelze omezit explicitní deklarací typu. Některé to, pravda, dovedou, ale u jiných to je problém.

Inkvizitor

Re: Triedne vs. Prototypové OOP
« Odpověď #28 kdy: 17. 03. 2011, 22:22:39 »
Nevím, co jsi měl, Logiku, na mysli tím směšováním dynamických a slabých typových systémů. Podle definice, kterou znám já, je Python silně typový jazyk, ale moje výtka pro něj na 100% platí.

Logik

  • *****
  • 1 049
    • Zobrazit profil
    • E-mail
Re: Triedne vs. Prototypové OOP
« Odpověď #29 kdy: 17. 03. 2011, 23:49:47 »
Předat referenci v konstruktoru můžeš, pokud je ta reference jedna. Pokud je těch callbacků x.... Někde to lze vyřešit pojmenovanými parametry, ale pořád zůstává naprosto zbytečně "nafouknutej" konstruktor a naprosto nečitelné jeho volání. Známá programátorská pravda říká, že víc než cca 3 argumenty metody znamená, že je něco špatně....

V každym případě prostě není jediný důvod, proč by měli být metody, které jdou za běhu měnit, implementované úplně jiným způsobem, než metody, které měnit nejdou.

Co se týče zaměňování, tak ty definice jsou asi různý - to co píšeš je něco mezi silnym/slabym a statickym/dynamickym typováním, podle autora. V každém případě to, že do seznamu můžeš nacpat cokoli naprosto nesouvisí s tím, jak je řešená resoluce volaných metod. Můžu mít klidně silně staticky typovanej jazyk s prototypovou dědičností a možností modifikovat objekt za běhu. Anebo jazyk v podstatě bez jakékoli typové kontroly, kterej ale modifikovat objekty za běhu neumkožňuje (např. některé verze visual basicu).