Dekorování vráceného objektu podle kontaineru rubrika: Programování: Jiné

9 Taco
položil/-a 15.12.2014

Mějme dvě třídy se společným předkem:

interface Item
  String getName()
  Parent getParent()
 
class SimpleItem implements Item
  String getName() {...}
  Parent getParent() {...}
 
class ComplexItem implements Item
  String getName() {...}
  Parent getParent() {...}
  Complex getComplex() {...}

a jakýsi další objekt ve dvou verzích:

class ContainerA
  Item addItem(Item item) {}
  Item getItems() {}
 
class ContainerB
  Item addItem(Item item) {}
  Item getItems() {}

použití asi takovéto:

a = new ContainerA()
a.addItem(new SimpleItem())
a.addItem(new ComplexItem())
 
b = new ContainerB()
b.addItem(new SimpleItem())
b.addItem(new ComplexItem())
 
a.getItems()[0].getName()
b.getItems()[0].getName()
b.getItems()[0].getOptions()
  1. chci umožnit vytvářet specielní implementace.
  2. chtěl bych, aby ContainerB vracel všechny Item, a aby měli všechny Item krom stávajících metod ještě metody getAttribs() a getOptions() ale ContainerA nikoliv.
  3. getAttribs() a getOptions() jsou metody více méně obecné, které mohou a nemusí být přeimplementovány.

a/ Když udělám wrapper, upíšu se, a navíc mi všechny Item budou mět stejný typ. Třeba ItemB, ale nikoliv ComplexItem, či ComplexItemB.
b/ Když nacpu funkce do společného předka, budou mi tam zavadzet.
c/ Když si vynutím u ContainerB vlastní verzi ItemB (mající požadované metody), budu muset všechny implementace přepsat.
d/ Když odlehčím Item na jako jen obálku, a logiku budu vkládat kompozicí, tak to zase bude děsně neintuitivní při používání.
e/ Taky mě napadlo tam ty dvě metody nacpat pomocí extension method. Jenže si nejsem jist, zda to bude fungovat jen pro tu instanci, a obecně se mi to moc nepozdává.

víc mě nenapadá

Jak byste to řešili? Díky za reakce.

odkaz
9 Honza Břešťan
odpověděl/-a 15.12.2014

Pokud maji jen prvky ContainerB mit nejake metody navic, nemel by vracet stejny typ jako ContainerA. Definice rozhrani pro ContainerB takhle nedava moc smysl.

Kdyz budu mit jeden interface Item a druhy ExtendedItem (ktery implementuje Item a pridava ty dve metody getAttribs a getOptions), da se pro to celkem jednoduse napsat konkretni wrapper, ktery implementuje ExtendedItem, ale v konstruktoru vezme Item. Podle potreby si jeho vytvareni muze vzit na starost ContainerB (a dal prijimat Item) a nebo bude rovnou prijimat ExtendedItem a dekorovat budou jeho uzivatele sami. Stari uzivatele ContainerB muzou porad pouzivat jen interface Item a nijak je to nerozhaze.

Kdyz to vezmu poporade:

a) Dekorator nedava smysl, protoze rozsirujes ten interface. Pokud by bylo dekoratoru vic (pro Item i ExtendedItem), odekorovani ExtendedItemu dekoratorem pro Item by ztratilo kus typove informace. V Jave by mohla pomoct genericita, ale genericky dekorator smrdi.
b) Ve spolecnem predku nemaji IMHO co delat, kdyz nejsou spolecne. Item by pro ne pak musel vracet nejaky neuzitecny default nebo hazet vyjimku. Ani jedno nezni zrovna jako nejlepsi objektovy navrh.
c) I pokud ItemB bude implementovat Item a wrapovani si vezme na starost ContainerB? Existujici kod napsany pro Item by to mel normalne ustat.
d) To zni v OOP docela neprakticky, je to vlastne prechod na anemic model. Pokud te logiky ale neni moc, nemusi to nutne vadit. Jen to bude asi nekonzistentni se zbytkem kodu a nekoho to muze pri cteni kodu prekvapit.
e) Extension nebo default metody jsou pouzitelne last resort reseni, ale maji stejny problem jako bod b). Pokud bude ContainerB porad vracet Item, musely by rozsirovat vsechny Itemy.

Ja bych volil c) s moji upravou. Neni to nejcistsi a asi bude potreba kontrola proti vicenasobnemu wrapovani, ale zacleneni do existujiciho kodu by melo byt bez problemu a neovlivni to nijak samotny Item.

Komentáře

  • Taco : Jaký je rozdíl mezi Dekorátorem a Wraperem? 15.12.2014
  • Taco : Pokud zvolím c/ budu muset všechny implementace přepsat. Pokud si vezme na starost wrapování ContainerB (což jsem taky tak myslel), tak bude vracet co? IMHO Dekorátor/Wraper, klidně odvozený od Item, tedy třeba ExtendedItem, ale stále to bude mět problém v tom, že a/ ExtendedItem != ComplexItem, b/ volání metody getComplex() by muselo vypadat nějak takto a.getWrapered().getComplex(), a nebo použít magii a automaticky přemapovávat metody. 15.12.2014
  • Honza Břešťan : Trochu divoke nazvoslovi, no. Wrapper asi neni zadny standardni nazev. Beru ho jako obecne neco, co obalujea obohacuje wrapnuty typ. Muze nejak overridovat logiku, nebo rozsirovat interface, pridavat data apod. Decorator je "uzsi" wrapper, ktery se porad tvari jako ten wrapnuty typ, takze muze pridavat hodnotu jen tim, ze overriduje chovani. Vyhoda decoratoru je, ze se daji komponovat a clovek neprijde o typovou informaci, protoze vsecha jeho pridana hodnota je v metodach toho spolecneho interfacu. Vyhoda wrapperu je, ze toho muze vic, ale pak nejde je jen tak komponovat. 15.12.2014
  • Taco : Fajn, v tom případě jsem v bodu "a" mluvil o wrapperu. Upravím to v zadání. 15.12.2014
  • Honza Břešťan : Pokud tam nekdo zavisi na tom, ze Container(A|B) vrati konkretne ComplexItem a ne jen Item, je to vetsi problem. Kde ma vlastne ten Item (at uz Simple nebo Complex) vzit data/stav pro ty metody getAttribs a getOptions? Bojim se, ze to ukazuje na problem na vyssi urovni. 15.12.2014
  • Taco : ContainerA|B je kontainer, který přidává určitou logiku, víceméně související s metodami getAttribs() a getOptions(). Uživatel pracuje s kontainerem a pracuje s daty, které do něj vložil. Takže když první položka je SimpleItem, tak s tím pracuje jako se SimpleItem, když je to ComplexItem, tak ComplexItem. Když pracuje s ContainerB, tak navíc ví, že tam může očekávat ty metody getAttribs() a getOptions(). Navíc může položky filtrovat podle typu, takže si může vyfiltrovat ComplexItem, a zavolat nad tím extra metodu getComplex(). (Samozřejmě ideální by bylo, aby ten ContainerB dokázal do těch Itemů nacpat vlastní rozhraní.) A chtěl bych se vyvarovat toho, abych musel reimplementovávat všechny položky, jednou pro ContainerA, a jednou pro ContainerB. Zda mi metoda ContainerB::addItem() bude vracet SimpleItem, nebo ExtendedSimpleItem je mi buřt. Ale pak bych čekal, že bude analogicky vracet u ContainerA - ComplexItem a u ContainerB - ExtendedComplexItem. 15.12.2014
  • Taco : "Kde ma vlastne ten Item (at uz Simple nebo Complex) vzit data/stav pro ty metody getAttribs a getOptions?" - b.getItems()[0].getAttribs()[0].value = 42 15.12.2014

Pro plný přístup na Devel.cz se prosím přihlaste:

Rychlé přihlášení přes sociální sítě:

Nebo se přihlaste jménem a heslem:

Zadejte prosím svou e-mailovou adresu.
Zadejte své heslo.