OOP jazyk - problém klasického stříhu

anonym

OOP jazyk - problém klasického stříhu
« kdy: 06. 06. 2018, 17:51:27 »
Pravidelně se s OOP dostávám do problému, který se mi konečně podařilo podchytit a formalizovat. Zjistil jsem, že onen učebnicový příklad aplikace OOP sám o sobě smrdí. Jde o tohle:

Máme třídu User, která je v základu jakési POJO. No a je potřeba mít možnost uložit Usera do DB. Učebicové OOP říká, že máme dát metodu save() přímo do User. Na první pohled to zní logicky a elegantně, ale je to v principu kravina. Ten User bude mít vazby na další třídy, třeba na Company a Address atp. Co bude v metodě save(), když byl vytvořen rovněž i Company a Address objekt a User na to má návazovnost, tzn. nemá smysl vytvořit Usera bez nové Company a Address? Jenže v modetě save() v Userovi se přece nemůže zpracovávat i Company a Address, to je kravina.

To vede  k architektuře, která z vysoka kálí na takovéto OOP paradigmata: Z User se stane obyčejné POJO, které bude mít dejme tomu závislost na jinačí POJO, a vytvoří se třída DBUtil, která bude pro jejich zpracování.

No a ta architektura je víte jaká? Daleko více připomíná procedurální programování než OOP: to POJO je datová struktura a to DBUtil je zpracovávající mechanismus.

Budiž mi důkazem, že v drtivě nejpoužívanějším frameworku Javy, ve Springu, to přesně takhle funguje. Třídy obsahující metody s jsou v podstatě všechny bezestavové a v jedné jediné instanci, úplně stejně jako kdyby to byly jen funkce. A POJO třídy jsou jediné, ze kterých se dělají instance.

No a proč to píšu, protože už si hodně dlouho, jak chuj, lámu hlavu s tím, proč kdykoliv když se snažím udělat si dobře architekturu aplikace, narazím na nejrůznější dilemata. Tak teď už to vím, jeden z těch hlavních důvodů je, že se snažím dělat OOP, a ono to přitom ani nejde!


Re:OOP jazyk - problém klasického stříhu
« Odpověď #1 kdy: 06. 06. 2018, 18:03:55 »
U vsech malych bohu, ktera ucebnice ma takovehle "ucebnicove" reseni?

Co myslis tim "nejde"?

gll

  • ****
  • 429
    • Zobrazit profil
    • E-mail
Re:OOP jazyk - problém klasického stříhu
« Odpověď #2 kdy: 06. 06. 2018, 18:21:29 »
Učebicové OOP říká, že máme dát metodu save() přímo do User.

která učebnice to říká?

Jenže v modetě save() v Userovi se přece nemůže zpracovávat i Company a Address, to je kravina.

proč nemůžeš?


Honza

Re:OOP jazyk - problém klasického stříhu
« Odpověď #3 kdy: 06. 06. 2018, 18:24:07 »
V daném příkladu na metodě User::save() nic špatného nevidím.
To co se ale nebude dít, je, že se v té metodě opravdu nebude zpracovávat Company, ani Address.
Správný OOP přístup k tomu je ten, že úkon uložení objektu User se deleguje jinam.
Oddělení datové struktury, a "zpracovávacího mechanismu" (algoritmu) do jiné třídy, to je také "best-practice" v OOP, tady konkrétně podle návrhového vzoru Visitor. Určitě to ale nebude zpracovávat žádný Singleton typu DBUtil!

Ale líbí se mi, že je tady konečně nějaký konkrétní příklad...

Re:OOP jazyk - problém klasického stříhu
« Odpověď #4 kdy: 06. 06. 2018, 18:55:41 »
Jenže v modetě save() v Userovi se přece nemůže zpracovávat i Company a Address, to je kravina.
Také to tak v čistém OOP být nemá. Metoda User.save() má uložit stav uživatele – asi to bude znamenat i uložit nějaké vazby na Company a Address, např. jejich ID. A Company a Address zase budou mít své metody save(), které se postarají o uložení konkrétních objektů.


anonym

Re:OOP jazyk - problém klasického stříhu
« Odpověď #5 kdy: 06. 06. 2018, 19:11:50 »
V daném příkladu na metodě User::save() nic špatného nevidím.
To co se ale nebude dít, je, že se v té metodě opravdu nebude zpracovávat Company, ani Address.
Správný OOP přístup k tomu je ten, že úkon uložení objektu User se deleguje jinam.
Oddělení datové struktury, a "zpracovávacího mechanismu" (algoritmu) do jiné třídy, to je také "best-practice" v OOP, tady konkrétně podle návrhového vzoru Visitor. Určitě to ale nebude zpracovávat žádný Singleton typu DBUtil!

Ale líbí se mi, že je tady konečně nějaký konkrétní příklad...

No, další problém té metody User::save() je, že kp dáš sendEmail()? Taky do User? A kam dáš orderHimToMakeMeCoffe()? Ŕekněme že User bude obsahovat taky profilovou fotku. A ty chceš mít funkcionalitu pridejLegracniKnirek(). To das taky do User? Prostě tohleto nedává smysl a vzniká tím dilema, kdy na jednu stranu to tam nemůžeš dát, protože je to chaos, ale na druhou stranu proč bys nemohl, když už tam máš save()? Prostě řešíš ted v OOP něco, co v procedurálním programování je úplně jasně dané jak to bude.

A nedá se z toho ani nějak vyvodit jednoznačně to, jak to teda správně poskládat. Proto mi přijde lepší říct si seru na to a udělat to tak, jak se to dělá ve Springu, a to je velice podobné tomu, jak se to dělá u procedurálního programování, tzn. rozlišuju Service (stateless instance) a to ostatní (POJO objekty s daty). V ten moment to máš taky správně, je to přehledné, a nemusíš se zbytečně zamýšlet nad tím, jak to správně komponovat.

Re:OOP jazyk - problém klasického stříhu
« Odpověď #6 kdy: 06. 06. 2018, 19:12:42 »
Oddělení datové struktury, a "zpracovávacího mechanismu" (algoritmu) do jiné třídy, to je také "best-practice" v OOP
Nikoli, OOP je pravý opak, tedy spojení dat a s nimi souvisejících funkcí do jednoho objektu.

Jenže dnes se málokde programuje skutečně v OOP – spíš se používá strukturované programování s oddělením datových struktur a výkonného kódu, přičemž obojí (struktury i výkonný kód) je na úrovni programovacího jazyka implementován pomocí objektů. Často z toho vzniká zmatení, protože programovací jazyky obvykle mají jenom jeden typ objektů, takže na úrovni zdrojového kódu se nedá přímo rozlišit, co jsou struktury a co výkonný kód. Rozlišuje se to pojmenováním – datovým strukturám se říká třeba datové třídy, POJO, entity nebo přepravky, výkonnému kódu pak třeba služby. Ještě zřetelnější je to u vzdálených služeb, kdy výkonný kód je implementován jako služba na vzdáleném serveru, a ta služba na vstupu bere a na výstupu dává datové struktury, např. XML nebo JSON.

Akorát mi připadá, že to není moc teoreticky zpracované, protože programovací paradigma „datové struktury a vedle oddělený kód, který s nimi pracuje“ je podle mne považováno za obecné best-practice (ať už se tak programuje ve strukturovaném C nebo v objektové Javě), přitom pokud vím nemá ani žádné jméno. A to, že se ty datové struktury a služby interně implementují pomocí OOP podle mne taky nemá žádné jméno – přitom je to jen klasické zapouzdření, protože i u struktury je lépe definovat ji rozhraním, implementace uvnitř se ale může měnit; stejně tak služba, i bezestavová, často potřebuje mít uložena nějaká data.

anonym

Re:OOP jazyk - problém klasického stříhu
« Odpověď #7 kdy: 06. 06. 2018, 19:14:33 »
Jenže v modetě save() v Userovi se přece nemůže zpracovávat i Company a Address, to je kravina.
Také to tak v čistém OOP být nemá. Metoda User.save() má uložit stav uživatele – asi to bude znamenat i uložit nějaké vazby na Company a Address, např. jejich ID. A Company a Address zase budou mít své metody save(), které se postarají o uložení konkrétních objektů.

Ano, to by bylo krásné, jenže nemůžete uložit jen tak Usera s id Company, když Company ještě není uloženo a tudíž ten klíč zatím v DB neexistuje.

Re:OOP jazyk - problém klasického stříhu
« Odpověď #8 kdy: 06. 06. 2018, 19:20:35 »
Ve slušné učebnici bude spíš popsán rozdíl mezi Active Record a Data Mapperem. Na to se tazatel asi ptá. IMHO každý přístup má svoje výhody a nevýhody:


Navíc obě varianty mají různé implementace, které se liší v jednotlivostech (například nakládání se session - zda se rozlišuje mezi attached a ne-attached instancí).
« Poslední změna: 06. 06. 2018, 19:23:17 od Ondrej Nemecek »

Re:OOP jazyk - problém klasického stříhu
« Odpověď #9 kdy: 06. 06. 2018, 19:28:48 »
Ano, to by bylo krásné, jenže nemůžete uložit jen tak Usera s id Company, když Company ještě není uloženo a tudíž ten klíč zatím v DB neexistuje.
To už se ale nebavíme o OOP, ale o tom, že relační databáze a OOP jsou dva různé světy, mezi kterými nejde jednoduše mapovat, a už vůbec ne automaticky. Což je vidět na věcech jako JPA, které vedou k tomu, že nemáte dobře ani relační model ani OOP.

Ale já tu rozhodně „čisté“ OOP nehájím, ani nevím, jestli vlastně takhle bylo OOP myšleno, nebo jestli se tohle stalo z OOP až v učebnicích. Nemyslím si, že by se objekt User sám měl umět i uložit do databáze – je to porušení principu jedné odpovědnosti.

Honza

Re:OOP jazyk - problém klasického stříhu
« Odpověď #10 kdy: 06. 06. 2018, 19:32:20 »
No, další problém té metody User::save() je, že kp dáš sendEmail()? Taky do User? A kam dáš orderHimToMakeMeCoffe()? Ŕekněme že User bude obsahovat taky profilovou fotku. A ty chceš mít funkcionalitu pridejLegracniKnirek(). To das taky do User? Prostě tohleto nedává smysl a vzniká tím dilema, kdy na jednu stranu to tam nemůžeš dát, protože je to chaos, ale na druhou stranu proč bys nemohl, když už tam máš save()? Prostě řešíš ted v OOP něco, co v procedurálním programování je úplně jasně dané jak to bude.

A nedá se z toho ani nějak vyvodit jednoznačně to, jak to teda správně poskládat. Proto mi přijde lepší říct si seru na to a udělat to tak, jak se to dělá ve Springu, a to je velice podobné tomu, jak se to dělá u procedurálního programování, tzn. rozlišuju Service (stateless instance) a to ostatní (POJO objekty s daty). V ten moment to máš taky správně, je to přehledné, a nemusíš se zbytečně zamýšlet nad tím, jak to správně komponovat.
Metoda save() ukládá objekt User, ta tam být může. Metoda sendEmail() ale posílá zprávu, s objektem User nemá společného nic, sendEmail bude odpovědnost jiné třídy, která umí poslat e-mail.
Profilová fotka patří jednoznačně uživateli, ale operace nad tou fotkou už také patří do jiné třídy, žádná věda.

Oddělení datové struktury, a "zpracovávacího mechanismu" (algoritmu) do jiné třídy, to je také "best-practice" v OOP
Nikoli, OOP je pravý opak, tedy spojení dat a s nimi souvisejících funkcí do jednoho objektu.
Důležité je, že se jedná o související funkce, na tom se shodneme. Ale já mám namysli konkrétní implementaci algoritmu, která provede uložení toho objektu User, a ta patří v každém případě jinam! Teprve potom totiž můžu ukládat do databáze, do souboru, nebo třeba do HTML. Právě tento princip např. Active Record porušuje.

Re:OOP jazyk - problém klasického stříhu
« Odpověď #11 kdy: 06. 06. 2018, 19:34:55 »
Ano, to by bylo krásné, jenže nemůžete uložit jen tak Usera s id Company, když Company ještě není uloženo a tudíž ten klíč zatím v DB neexistuje.

To záleží na tom, jak má ten který ORM framework vyřešeny závislosti entit. Může například uložit nebo aktualizovat všechny závislosti nebo naopak žádnou (pak je musíte uložit ve správném pořadí sám). Navíc může rozlišovat, zda je ta entita attached:


Re:OOP jazyk - problém klasického stříhu
« Odpověď #12 kdy: 06. 06. 2018, 19:38:47 »
Ale já mám namysli konkrétní implementaci algoritmu, která provede uložení toho objektu User, a ta patří v každém případě jinam! Teprve potom totiž můžu ukládat do databáze, do souboru, nebo třeba do HTML. Právě tento princip např. Active Record porušuje.
Já si také myslím, že služba uložení objektu User nepatří do objektu User. Ale podle učebnicového OOP by právě do toho objektu patřila, a konkrétní implementace ukládání (databáze, soubor) by se řešila dědičností. Mimo jiné i proto, že v databázi bude mít uživatel asi nějaký identifikátor (primární klíč), který by User neměl nikam vystavovat, ale metoda pro uložení do databáze ho bude muset znát.

Jsem zvědav, co nám na to řekne Kit…

Kit

Re:OOP jazyk - problém klasického stříhu
« Odpověď #13 kdy: 06. 06. 2018, 19:40:15 »
Jenže v modetě save() v Userovi se přece nemůže zpracovávat i Company a Address, to je kravina.
Také to tak v čistém OOP být nemá. Metoda User.save() má uložit stav uživatele – asi to bude znamenat i uložit nějaké vazby na Company a Address, např. jejich ID. A Company a Address zase budou mít své metody save(), které se postarají o uložení konkrétních objektů.

Ano, to by bylo krásné, jenže nemůžete uložit jen tak Usera s id Company, když Company ještě není uloženo a tudíž ten klíč zatím v DB neexistuje.

Tohle se dá řešit vzorem Observer. Objekty tříd Company a Address se zaregistrují do objektu třídy User. Když zavoláš User.save(), tak o tom ty objekty dostanou oznámení a data uloží také.

Kit

Re:OOP jazyk - problém klasického stříhu
« Odpověď #14 kdy: 06. 06. 2018, 19:51:40 »
Ale já mám namysli konkrétní implementaci algoritmu, která provede uložení toho objektu User, a ta patří v každém případě jinam! Teprve potom totiž můžu ukládat do databáze, do souboru, nebo třeba do HTML. Právě tento princip např. Active Record porušuje.
Já si také myslím, že služba uložení objektu User nepatří do objektu User. Ale podle učebnicového OOP by právě do toho objektu patřila, a konkrétní implementace ukládání (databáze, soubor) by se řešila dědičností. Mimo jiné i proto, že v databázi bude mít uživatel asi nějaký identifikátor (primární klíč), který by User neměl nikam vystavovat, ale metoda pro uložení do databáze ho bude muset znát.

Jsem zvědav, co nám na to řekne Kit…

Databázovou proxy můžeš do objektu injektovat stejně dobře jako logování nebo odeslání mejlu. Stačí, aby tyto služby měly stejné rozhraní. Objekt User vůbec nemusí tušit, komu ta data posílá metodou save().

Dědičnost sem však vůbec nepatří. Co bys tady chtěl dědit? User přece není ani Databáze, ani Soubor.