Návrhové vzory - Specializace třídy pro dva různé use case – C / C++ – Fórum – Programujte.com
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu

Návrhové vzory - Specializace třídy pro dva různé use case – C / C++ – Fórum – Programujte.comNávrhové vzory - Specializace třídy pro dva různé use case – C / C++ – Fórum – Programujte.com

 
Hledat
Moderní platforma pro vytvoření vašeho nového webu – Wix.com.
Nyní už můžete mít web zdarma.
Vybavení pro Laser Game
Spuštěn Filmový magazín
Laser Game Brno
Laser Game Ostrava

Toto vlákno bylo označeno za vyřešené — příspěvek s řešením.
Doomista+1
Stálý člen
28. 3. 2017   #1
-
0
-

Ahoj, potřeboval bych trochu pomoct s návrhovými vzory pro následující úkol: Mám naprogramovat hru Solitaire Klondike a to jak ve verzi pro cmd, tak ve verzi s grafickým rozhraním přes Qt.

Můj prvotní nápad bylo vytvořit si třídu GameMgr, který bude obstarávat logiku hry a který bude pracovat nad třídami Library, Piles a Foundations, které v sobě budou spravovat zásobníky a seznamy objektů Card. Každá třída by se pak dala specializovat pro CLI a pro QT verzi hry, při čemž specializace by spočívala v implementaci metod pro vykreslování na obrazovku. Zde jsem ale narazil na problém.

Jak jednoduše v C++ zajistit, aby GameMgr pracoval buď nad CLI nebo QT verzemi objektů Library, Piles a Foundations a jak zajistit, aby tyto objekty pak pracovaly se správnou verzí objektů Card (či případně i jiných objektů seznamů a zásobníků, pokud budu chtít vykreslovat i ty)?

Napadla mě pouze jedna velice ošklivá varianta, kdy by všechny třídy využívaly šablon a GameMgr objekt by se pak vytvářel nějak tato: GameMgr<Library_Cli<Card_Cli>, Piles_Cli<Card_Cli, Stack_Cli>, Foundations_Cli<Card_Cli>> .... což mi nepřijde jako dvakrát elegantní řešení.

Tipnul bych si, že lepší řešení asi vyžaduje úplně jiný systém dědění, žádný mě ale nenapadá.

Díky předem za pomoc

Nahlásit jako SPAM
IP: 2001:67c:1220:809::93e5:9...–
Na vše stačí iostream...
Řešení
Ovrscout
~ Anonymní uživatel
104 příspěvků
28. 3. 2017   #2
-
+1
-
Zajímavé
Vyřešeno Nejlepší odpověď

#1 Doomista
Jsem teď nějaký líný nějak hlouběji promýšlet architekturu :) takže spíš jen pár poznámek:

Pro vytváření bych popřemýšlel nad factory které bude vracet buď cli nebo qt verzi objektů. Tj na začátku si vytvoříš správné factory a pak už při vytváření objektů neřešíš jakou verzi chceš.

Taky by možná bylo dobré uvažoval o oddělení prezentace do samostané třídy (případně více tříd).
Možností jak propojit GM s vizualizací bude více.

Nahlásit jako SPAM
IP: 193.165.79.–
Doomista+1
Stálý člen
29. 3. 2017   #3
-
0
-

#2 Ovrscout
Factory pattern vidím prvně v životě, ten se budu učit, až na to bude čas ;)

Nápad o oddělení vykreslování od logiky je dobrý, použiju ho.

Nahlásit jako SPAM
IP: 2001:67c:1220:809::93e5:9...–
Na vše stačí iostream...
Staon0
Návštěvník
29. 3. 2017   #4
-
0
-

Oddělení logiky od vykreslování určitě udělejte. Zkuste se například kouknout na klasický návrhový vzor MVC (Model View Controller), který přesně takhle pracuje. Má logický model a asynchronně k němu jsou k němu připojené dvojice View/Controller, které se starají o zobrazení, regují na změny v modelu a reagují na uživatelské události. Pokud půjdete podobnou cestou, pravděpodobně nebudete potřebovat továrnu, protože typy budou jasně dané zvoleným View.

Ve vašem návrhu nevím, jak byste chtěl zobrazení do Qt a CLI schovat pod jedno rozhraní, protože pro zobrazení budete potřebovat rozdílné prostředí. Aby to šlo, musel byste vytvořit společné rozhraní pro vykreslování do Qt a CLI.

Jedna architektonická. Pokud dědíte z třídy, která má implementaci, v 90 % případů to značí, že máte chybu v návrhu. Postup obvykle je definice rozhraní (v C++ třída, která má pouze pure virtual metody), a pak jejich implementace. Ještě horší situace je, když dědíte z třídy s implementací a ještě přetěžujete už naimplementovanou metodu. Pak si zaděláváte na vážné problémy, ocančované obvykle jako fragile base class.

Nahlásit jako SPAM
IP: 94.142.234.–
Doomista+1
Stálý člen
30. 3. 2017   #5
-
0
-

#4 Staon
MVC model znám, vůbec mě nenapadlo ho uvažovat kdekoliv jinde, než při vývoji webu :)

Schování Qt a CLI pod jedno rozhraní jsem chtěl řešit tak, že specializované třídy by implementovaly vlastní sady metod pro fungovaní v daném prostředí. Každé prostředí bude separátní binárka a separátní zdrojový kód, takže tam by mě odlišná rozhraní ve specializacích nevadila. Byl to nicméně špatný nápad, což jsem si uvědomil, až při implementaci metod.

S návrhovými vzory se teprve učím a musím si je zažít. Osobně jsem ještě neprogramoval nic, co by vyžadovalo pure virtual třídu (ale vím, co to je i jak se v C++ dělá), zatím jsem dědění využíval jen ve formě mám Bod, který specializuji na Kruh a Obdélník, apod.

Nahlásit jako SPAM
IP: 2001:67c:1220:809::93e5:9...–
Na vše stačí iostream...
Staon0
Návštěvník
30. 3. 2017   #6
-
0
-

#5 Doomista
Jj, máte pravdu, MVC se ve webu hodně uchytilo. Nicméně, jedná se o vzor, který byl vymyšlený kolem roku 1978 ve Smalltalku pro implementaci jejich GUI toolkitu. A co tak pozoruji posledních 10 let, tak prakticky všechny GUI toolkity k němu pomalu konvergují.

Proboha, tohle je asi ten nejhorší příklad, jaký se v učebnicích OOP vyskytuje. Podle mého názoru už zničil myšlení tak 3 až 4 generací programátorů. Ani Obdélník, ani Kruh nejsou speciálním případem bodu! Matematicky se jedná o množinu bodů. V programátorské praxi bude obdélník pravděpodobně reprezentovaný dvěma body a kruh jedním bodem a poloměrem. Ale ani jedno není bod!

Ta "správná" hierarchie pro tento příklad (předpokládám implementaci jednoduchého grafického editoru) je rozhraní GrafickýObjekt (což by v C++ byla ta pure virtual třída) a pro něj implementovat (v C++ podědit) třídy Bod (kreslil by se jako puntík), Obdelnik a Kruh, případně další.

Nahlásit jako SPAM
IP: 94.142.234.–
Doomista+1
Stálý člen
30. 3. 2017   #7
-
0
-

#6 Staon
Já vím, že můj další dotaz možná bude působit jako zabedněnost, ale není jedním ze smyslů dědičnosti i to, abych zobecnitelný kód psal jenom jednou? Bod, Obdélník i Kruh budou pravděpodobně mít ve svém rozhraní funkce getPosition, setPosition a move, tedy za předpokladu, že obdélník je zadaný levým horním rohem a rozměry a kruh středem a poloměrem.

Pokud tyto metody naimplementuji v rámci Bodu a pak si od něj podědím a rozšířím rozhraní (a atributy) specializovaných objektů, tak ten kód bude implementovaný jenom jednou a bude tudíž snadno udržovatelný. Pokud budu specializovat třídu GrafickyObjekt, tak Kruh a Obdélník v sobě mohou mít jako atribut instanci Bodu, ale stejně budou muset znovu implementovat všechny metody, při čemž jediné, co udělají, tak bude volání těch samých metod nad objektem Bod. Navíc v nekompilovaném jazyku by toto mohlo zvýšit režii při volání těchto metod nad objekty Kruh, Obdélník.

Chápu Váš nesouhlas s vyjádřením, že Kruh a Obdélník jsou specializacemi Bodu, ale není to ve výsledku praktičtější řešení? Kéž by s námi takovéto věci řešili na přednáškách z OOP, ale bohužel se tak neděje.

Nahlásit jako SPAM
IP: 78.102.108.–
Na vše stačí iostream...
KIIV
~ Moderátor
+43
God of flame
30. 3. 2017   #8
-
0
-

#7 Doomista
pro kruh, obdelnik a ostatni, se v relaci s bodem hodi kompozice. Posun objektu a podobne zalezitosti jsou zase zalezitost interface tridy GrafickyObjekt (pure virtual class). Jednotlive zdedene tridy to musi akorat spravne implementovat.

Mimochodem Staon nenapsal, ze geometricke obrazce jsou specializace bodu. Napsal presny opak.

Nahlásit jako SPAM
IP: 212.47.3.–
Program vždy dělá to co naprogramujete, ne to co chcete...
P
~ Anonymní uživatel
210 příspěvků
30. 3. 2017   #9
-
0
-

Pokud maji Bod, Obdelnik i Kruh implementace nekterych vlastnosti spolecne, je mozne je implementovat v bazove tride GrafickyObjekt, ktera bude abstraktni. Nemluvim ted konkretne o C++, ale o OOP.

Nahlásit jako SPAM
IP: 212.47.6.–
Doomista+1
Stálý člen
30. 3. 2017   #10
-
0
-

#8 KIIV
Však jsem napsal, že nesouhlasí s tím vyjádřením, protože napsal přesný opak. Každopádně děkuji za cenné poznámky, OOP je fajn, ale chce to čas, než se v tom člověk naučí správně přemýšlet.

Nahlásit jako SPAM
IP: 78.102.108.–
Na vše stačí iostream...
Staon0
Návštěvník
30. 3. 2017   #11
-
+2
-
Zajímavé

#7 Doomista
Dědičnost povětšinou není ten nejlepší nástroj pro znovupoužívání kódu, k tomu je většinou vhodnější, jak píše KIIV, kompozice. Dědičnost by měla být používaná spíše pro specializaci rozhraní (proto taky UML má vztah generalizace/specializace, nikoliv dědičnost). Zkusme si tedy vzít váš návrh: 

class Souradnice {
  int x, y;
  void move(const Souradnice& s_);
};

class Bod {
  Souradnice p;
  virtual void prekresliSe();
  virtaul void move(const Souradnice& s_) {
    p.move(s_);
    prekresliSe();
  }
};

class Obdelnik : public Bod {
  Souradnice v;  /* -- rozmery obdelniku */
};

Všechno bude fungovat, tak jak chcete, svět bude růžový. Pak ale budete chtít svůj editor rozšířit o možnost kreslit křivky. Bezierovy kubiky se reprezentují pomocí 4 bodů. Tzn. nový objekt bude vypadat nějak takto: 

class Krivka : public Bod {
  Souradnice p2, p3, p4;
  virtual void move(const Souradnice& s_) {
    Bod::move(s_);
    p2.move(s_);
    p3.move(s_);
    p4.move(s_);
    prekresliSe();
  }
};

Na první pohled jsou tu problémy. S jedním bodem pracujete jinak, než s ostatními, což programátorovi přidělá pár nových vrásek. Navíc se vám dvakrát zavolá prekresliSe. A ještě jste se dostal k mnou zmiňovanému fragile base class problému, pokud metodě Bod::move nějak výrazně změníte chování (např. přidáte nějaký další side effect).

Můžete namítnout, že zbylé body je možné vyjádřit relativně vůči tomu prvnímu. Ano, to je pravda. Ale také to znamená, že při každé práci s křivkou si body nejdříve budete muset dopočítat. Takže jste si sice ušetřil práci s posunem, ale přidělal jste si práci všude, kde potřebujete s křivkou pracovat.

Problém je, že tímto návrhem jste si do architektury zavedl omezení, že každý grafický objekt musí být vyjádřitelný jedním absolutním bodem. Asi sám cítíte, že obejít toto omezení u křivky už docela skřípe. A stejný pocit bude mít každý programátor, který bude váš kód rozšiřovat.

To je tak ve zkratce to, co jsem měl na mysli.

Nahlásit jako SPAM
IP: 94.142.234.–
Doomista+1
Stálý člen
31. 3. 2017   #12
-
0
-

#11 Staon
Ano, chápu Vaši myšlenku. Je škoda, že takto kvalitní vysvětlení nemůžu upvotovat vícekrát. Znovu děkuji za obšírné vysvětlení :)

Nahlásit jako SPAM
IP: 78.102.108.–
Na vše stačí iostream...
Zjistit počet nových příspěvků

Přidej příspěvek

Toto téma je starší jak čtvrt roku – přidej svůj příspěvek jen tehdy, máš-li k tématu opravdu co říct!

Ano, opravdu chci reagovat → zobrazí formulář pro přidání příspěvku

×Vložení zdrojáku

×Vložení obrázku

Vložit URL obrázku Vybrat obrázek na disku
Vlož URL adresu obrázku:
Klikni a vyber obrázek z počítače:

×Vložení videa

Aktuálně jsou podporována videa ze serverů YouTube, Vimeo a Dailymotion.
×
 
Podporujeme Gravatara.
Zadej URL adresu Avatara (40 x 40 px) nebo emailovou adresu pro použití Gravatara.
Email nikam neukládáme, po získání Gravatara je zahozen.
-
Pravidla pro psaní příspěvků, používej diakritiku. ENTER pro nový odstavec, SHIFT + ENTER pro nový řádek.
Sledovat nové příspěvky (pouze pro přihlášené)
Sleduj vlákno a v případě přidání nového příspěvku o tom budeš vědět mezi prvními.
Reaguješ na příspěvek:

Uživatelé prohlížející si toto vlákno

Uživatelé on-line: 0 registrovaných, 47 hostů

Moderátoři diskuze

 

Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032019 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý