OOP a pravidla pro konstruktor

SB

Re:OOP a pravidla pro kontruktor
« Odpověď #45 kdy: 04. 06. 2018, 12:12:06 »
Ok ještě jinak: máš třídu A a ta má vnitřní závislost na třídu B, která spustí laser, co vypálí díru do zdi. Ta B se nijak nesetuje zvenčí, je vytvářená uvnitř. No a ty sloučíš inicializaci i výpočet do konstruktoru. Jak to budeš potom testovat? Když si v testu zavoláš new A(), jsi v zadeki, protože se ti zavolá B a ty to nijak nemůžeš ovlivnit...

Zjevně děláte něco špatně. Má-li třída A zadrátováno, že si vytváří a užívá třídu B, je to její vnitřní vlastností a je nesmyslné se divit, jaktože nejde vytvořit mock.

Řešení je více:
Takže buďto vyrobíte mock i pro třídu A, a to buďo zcela znovu, což může být zbytečné, nebo stačí do A metoda, která např. vrací třídu k vytvoření, nebo vrací rovnou vyrobenou instanci. Pak MockA vytvoříte dědicem s překrytím oné metody.
Nebo vytvoříte A tak, že když nechcete MockB strkat do instance třídy A zvenku, nainicializujete inst. A třídou, kterou budete uvnitř tvořit, nebo pomocí dependency injection instanci A vložíte objekt či uzávěru, která MockB vyrobí.
Není na tom nic složitého.


SB

Re:OOP a pravidla pro kontruktor
« Odpověď #46 kdy: 04. 06. 2018, 12:18:59 »
...Alespoň v Javě platí, že konstruktor jako první věc musí zavolat konstruktor rodiče. U init metody žádné takové omezení není.

To je jednou z domrdávek u prasojazyků, že konstruktor paušálně nejdřív volá svého předka, i kdybyste nechtěli. Potřebujete-li předtím něco nastavit, abyste mohli využít logiky předků, nebo nevolat vůbec, protože to potřebujete úplně jinak, jste v pr-deli. Dá se to jednoduše ojebat voláním inicializační metody z konstruktoru a překrývat vždy ji, ale už je to práce navíc.
Tento "bonus" jsem zaznamenal např. u C#.
Takže implementace "OOP" jsou různé, a ne vždy je to dobré.

SB

Re:OOP a pravidla pro konstruktor
« Odpověď #47 kdy: 04. 06. 2018, 12:23:22 »

SB

Re:OOP a pravidla pro kontruktor
« Odpověď #48 kdy: 04. 06. 2018, 12:25:42 »
Když máte složitou logiku konstrukce objektů, vytvořte statickou metodu s příhodným názvem, třeba LoadFromConfigurationFile.

https://en.wikipedia.org/wiki/Softcoding

SB

Re:OOP a pravidla pro kontruktor
« Odpověď #49 kdy: 04. 06. 2018, 12:40:29 »
Já si "dědičnost" zreformuloval na "rozšiřování třídy". Slovo dědičnost v OOP tak, jak je popisováno v literatuře (ala  zvířátka v ZOO), mi přijde silně zavádějící a aplikace tohoto typu dědičnosti vede k problémům v kódu. Proto když přemýšlím nad dědičností, používám vždycky vztah Rozšiřovat/Specializace, a nikoliv Dědit/Potomek. ... Oproti tomu Rozšiřovat/Specializace je mnohem konkrétnější a vztahuje se více k praktické podobě programového kódu.

Termíny generalizace-specializace nejsou v čistém OOP novinkou, přičemž to první je slučování společných vlastností do nadtříd/nadobjektů, to druhé rozdělování rozdílných vlastností do podtříd/podobjektů.
Pozor, specializace často neznamená rozšiřování, ale obecně odlišení fungování!

Osobně si (zatím) myslím, že ty snahy úplně dát pryč Rozšiřívání tříd jsou druhý extrém. Je to podobné, jako když vznikají jazyky typu Go, že jako OOP je moc složité a nepotřebné a proto budoucnost patříd jazykům, kde se dají dělat pouze funkce.

Pochopitelně. Dědičnost může být jednoduchým prostředkem odvozování tříd, ale ne jediným či povinným, i když u prasojazyků je to těžší, protože jsou založeny na podtypovém polymorfismu, což je kamenem úrazu (řeší to částečně interfaces).


ded.kenedy

Re:OOP a pravidla pro konstruktor
« Odpověď #50 kdy: 04. 06. 2018, 12:54:33 »
Citace
Tak DOPRČIC, přece nebudeš mít v konstruktoru třídy parametr BufferedWriter jenom proto, abys to potom mohl otestovat? To je úplná kravina, porušuje to zapouzdřenost, vystavuješ ven vnitřnosti třídy. Prostě uděláš to, že bufferedWriter reflexí namockuješ!

Celou dobu jsem mel podezreni na to, zes jeste neobjevil DI a IoC a tady tim jsi mi to potvrdil.

Jak uz tu psali jini, konstruktor by mel objekt inicializovat do konzistentniho stavu, jestli se pritom ma pouzit vypocet nebo jen nastavit hodnoty je podruzna zalezitost.

Tvuj problem patrne tkvi v tom, ze se ti v konstruktoru zacne objevovat komplexni logika a tim padem se ti narusi dobry zvyk, ze kazda metoda (konstruktor v tomto pripade lze chapat taky jako metodu) dela pouze jednu vec.

Jedno reseni je pouzit statickou (factory) metodu.

Ja si myslim, ze v tvem pripade by bylo jeste zahodno zvazit pristup oznacovany jako dependency injection, kde vsechny slozitejsi objekty, ktere v konstruktoru vytvaris, se vytvori vne a pak je vlozis pomoci konstruktoru. Prinos je troji: kod bude jednodussi, testovatelnejsi a pruznejsi.

Citace
Tak DOPRČIC, přece nebudeš mít v konstruktoru třídy parametr BufferedWriter jenom proto, abys to potom mohl otestovat?

Ale takhle se to bezne dela. Akorat neuvadis nazev souboru, ale obecny writer.

Misto
Kód: [Vybrat]
private zapisSpecialneDoSouboru(String info) {
pouzijes

Kód: [Vybrat]
private zapis(Writer wr) {
Jednou jako writer predas FileWriter pro zapis do souboru. Jindy predas StringWriter pro zapis do bufferu, ktery pouzijes pro testovani. A pokud prijde zakaznik s nejakym uchylnym pozadavkem, muzes tam dat Writer, ktery bude posilat data po siti nebo zapisovat libovolnym zpusobem.

Citace
To je úplná kravina, porušuje to zapouzdřenost, vystavuješ ven vnitřnosti třídy.

Zadne vnirtnosti tridy nevystavujes, takze zapouzdrenost je neporusena. To paradoxne delas s tou reflexi.

Citace
Prostě uděláš to, že bufferedWriter reflexí namockuješ!

To je neskutecna prasarna.

Re:OOP a pravidla pro kontruktor
« Odpověď #51 kdy: 04. 06. 2018, 13:07:55 »

Tak DOPRČIC, přece nebudeš mít v konstruktoru třídy parametr BufferedWriter jenom proto, abys to potom mohl otestovat? To je úplná kravina, porušuje to zapouzdřenost, vystavuješ ven vnitřnosti třídy. Prostě uděláš to, že bufferedWriter reflexí namockuješ!

A jak se tam ta instance dostane? Snad nebudes delat new na neco, co potrebujes?!

SB

Re:OOP a pravidla pro kontruktor
« Odpověď #52 kdy: 04. 06. 2018, 13:20:08 »
Už se přesně tady na tento problém vedla samostatná diskuze. Ano, private field zvenčí neexistuje, ale doprčic, NE PRO UNIT TESTY! To je úplně normální a NUTNÉ, že máš třídu a v ní jako private atribut něco, co musíš namockovat. To nemusí být laserpal, stačí aby to byl třeba Socket nebo zápis do souboru na disku!

Ale houby! Účelem jednotkových testů není nic jiného než otestovat správnost objektu na jeho rozhraní. Jak si to vevnitř pořeší, je jeho problémem. Přeloženo: Reaguje-li objekt dle požadavku, je v pořádku. Z tohoto pohledu je nezajímavé, že vnitřně záleží na dalších objektech! Chcete-li testovat i je, musíte to udělat extra a jinde.

anonym

Re:OOP a pravidla pro kontruktor
« Odpověď #53 kdy: 04. 06. 2018, 13:23:15 »

Tak DOPRČIC, přece nebudeš mít v konstruktoru třídy parametr BufferedWriter jenom proto, abys to potom mohl otestovat? To je úplná kravina, porušuje to zapouzdřenost, vystavuješ ven vnitřnosti třídy. Prostě uděláš to, že bufferedWriter reflexí namockuješ!

A jak se tam ta instance dostane? Snad nebudes delat new na neco, co potrebujes?!

Jak to myslíš?

SB

Re:OOP a pravidla pro kontruktor
« Odpověď #54 kdy: 04. 06. 2018, 13:26:16 »

Ano, jenže tohle ti poruší zapouzdřenost. Řekněme, že máš třídu, ve které budeš mít metodu

Kód: [Vybrat]
private zapisSpecialneDoSouboru(String info) {
    // nejaky kod
    this.bufferedWriter.write(str);
    // nejaky kod
}

Tak DOPRČIC, přece nebudeš mít v konstruktoru třídy parametr BufferedWriter jenom proto, abys to potom mohl otestovat? To je úplná kravina, porušuje to zapouzdřenost, vystavuješ ven vnitřnosti třídy. Prostě uděláš to, že bufferedWriter reflexí namockuješ!

Samozřejmě že budete. Buďto v parametru, nebo taky v objektu. Jestliže to nemáte, pak jste vytvořit SPECIALIZOVANÝ objekt s metodou, který umí zapisovat VÝHRADNĚ přes buffered writer, a to ještě danou instanci, tj. objekt bez obecnějšího použití. Žádné vnitřnosti nevystavujete, instanci zapisovače může znát pouze generátor tohoto objektu.

Re:OOP a pravidla pro kontruktor
« Odpověď #55 kdy: 04. 06. 2018, 13:28:41 »

Tak DOPRČIC, přece nebudeš mít v konstruktoru třídy parametr BufferedWriter jenom proto, abys to potom mohl otestovat? To je úplná kravina, porušuje to zapouzdřenost, vystavuješ ven vnitřnosti třídy. Prostě uděláš to, že bufferedWriter reflexí namockuješ!

A jak se tam ta instance dostane? Snad nebudes delat new na neco, co potrebujes?!

Jak to myslíš?

Ze konstruovat si zavislost uvnitr objektu (tedy i v konstruktoru) je skoro vzdy spatne.

SB

Re:OOP a pravidla pro konstruktor
« Odpověď #56 kdy: 04. 06. 2018, 13:29:55 »
Citace
Prostě uděláš to, že bufferedWriter reflexí namockuješ!

To je neskutecna prasarna.

Jo, na toto jsem se chtěl zeptat: Co to jako má být to "mockování reflexí"???

anonym

Re:OOP a pravidla pro kontruktor
« Odpověď #57 kdy: 04. 06. 2018, 13:39:32 »

Ano, jenže tohle ti poruší zapouzdřenost. Řekněme, že máš třídu, ve které budeš mít metodu

Kód: [Vybrat]
private zapisSpecialneDoSouboru(String info) {
    // nejaky kod
    this.bufferedWriter.write(str);
    // nejaky kod
}

Tak DOPRČIC, přece nebudeš mít v konstruktoru třídy parametr BufferedWriter jenom proto, abys to potom mohl otestovat? To je úplná kravina, porušuje to zapouzdřenost, vystavuješ ven vnitřnosti třídy. Prostě uděláš to, že bufferedWriter reflexí namockuješ!

Samozřejmě že budete. Buďto v parametru, nebo taky v objektu. Jestliže to nemáte, pak jste vytvořit SPECIALIZOVANÝ objekt s metodou, který umí zapisovat VÝHRADNĚ přes buffered writer, a to ještě danou instanci, tj. objekt bez obecnějšího použití. Žádné vnitřnosti nevystavujete, instanci zapisovače může znát pouze generátor tohoto objektu.

Přesně! Je to objekt bez obecného použití! Konečně! Jsme obklopeni objekty bez obecného použití!

1. Třeba tohle je přesně ten shit, který nechci dělat:

Kód: [Vybrat]
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(xmlFile);

2. Proč, když to jde takhle jednoduše:

Kód: [Vybrat]
XmlDocument doc = new XmlDocument(); 
        doc.LoadXml(xmlFile); 

A že není 2 tak obecná? Dpč, 99,5% případů užití je jen pro ten příklad č. 2!

Re:OOP a pravidla pro kontruktor
« Odpověď #58 kdy: 04. 06. 2018, 14:14:14 »

Kód: [Vybrat]
XmlDocument doc = new XmlDocument(); 
        doc.LoadXml(xmlFile); 


Uz jenom to new XmlDocument() smrdi...

SB

Re:OOP a pravidla pro kontruktor
« Odpověď #59 kdy: 04. 06. 2018, 14:17:45 »
Přesně! Je to objekt bez obecného použití! Konečně! Jsme obklopeni objekty bez obecného použití!

1. Třeba tohle je přesně ten shit, který nechci dělat:

Kód: [Vybrat]
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(xmlFile);

2. Proč, když to jde takhle jednoduše:

Kód: [Vybrat]
XmlDocument doc = new XmlDocument(); 
        doc.LoadXml(xmlFile); 

A že není 2 tak obecná? Dpč, 99,5% případů užití je jen pro ten příklad č. 2!

Pak si ale nestěžujte, že nemůžete objekt použít i s jinými závislostmi, např. právě tím mockem při testech (kdy test je jen speciálním použitím obecného objektu).