Programozás II gyakorlat 7. Példák a polimorfizmus alkalmazásaira
Probléma class A { public: ~A() { cout << "A destruktora" << endl; } }; class B: public A { public: ~B() { cout << "B destruktora" << endl; } }; int main() { A * a = new B; delete a; }
Melyik destruktor hívódik meg? 2
Probléma • Az a változó A * típusú • A fordító azt hiszi, hogy egy A típusú objektumot kell felszabadítani • Csak az A osztály destruktora hívódik meg • Ha a B osztálynak is fel kell szabadítania erőforrásokat, akkor probléma lép fel • Tegyük virtuálissá a destruktort! 3
Virtuális destruktor class A { public: virtual ~A() { cout << "~A" << endl; } }; class B: public A { public: ~B() { cout << "~B" << endl; } }; int main() { A * a = new B; delete a; }
Először a B destruktora fut le, majd az meghívja az ős destruktorát is 4
Feladat • Egy olyan szoftvert fejlesztünk, amelynek fájlba kell mentenie a különböző eseményeket, illetve meg is kell jelenítenie azokat • 3 fajta esemény van: általános esemény, rendszerhiba, és figyelmeztetés, minden fajta eseményt ugyanabba a fájlba mentünk • A szoftver megrendelője azt szeretné, hogy ezt a naplót két fajta formátumban lehessen elmenteni, és megjeleníteni 5
Feladat • A támogatandó fájlformátumok: • Egyszerű szöveges fájl: – A fájl minden sora egy eseményt tartalmaz, és egy kódot (0-2-ig), amely jelzi az esemény típusát – Ha ezt meg akarjuk jeleníteni a szoftverben, akkor fekete karakterekkel jelenítünk meg eseményt
6
Feladat • A támogatandó fájlformátumok: • HTML fájl: – Egy weboldalként kell elmenteni a naplót, ahol egy sor egy eseményt jelez – A sor fekete karakterekből áll, ha sima eseményről van szó – A figyelmeztetés színe a sárga – A rendszerhiba színe a piros – A szoftverben ezt a naplót színekkel együtt kell megjeleníteni
7
Tervezés: Builder (építő) minta • Szükség lesz egy osztályra, amely képes kezelni a naplót • Két különböző formátumot kell kezelni • Célszerű a két formátumra két osztályt írni: HtmlLog és TextLog • A két osztály származzon a Log ősosztályból • Ezen osztályokat lássuk el virtuális függvényekkel
8
Tervezés A szoftver csak a Log felületét használja Log
HtmlLog
TextLog
Log.html
Log.txt 9
Builder • A TextLog és a HtmlLog képes elmenteni a naplót valamilyen fájlba • A fájlok felépítése teljesen eltér egymástól • A Log osztály biztosít egyszerű virtuális függvényeket az új események hozzáadásához, és azok fájlba mentéséhez • Elég csupán a Log felületét használni, az izzadság szagú részleteket elrejtjük a gyerek osztályokban 10
Builder minta használata int main() { int valasz; cout << "Html vagy text?" << endl; cin >> valasz; Log * log = 0; if (valasz == 0) { log = new TextLog; } if (valasz == 1) { log = new HtmlLog; } log->addNewError("A file nem letezik!"); log->addNewEvent("Letrejott az uj dokumentum"); log->saveToFile("2013_05_12.log"); delete log; } 11
Tervezés • Szükség lesz egy osztályra, amely képes megjeleníteni a kezelőfelületen a naplót • Különböző formátumoknál különböző kezelőfelületre van szükség • Írjunk egy LogViewer osztályt, amelyből származtassuk a HtmlLogViewer és a TextLogViewer osztályokat • Szintén alkalmazzunk virtuális függvényeket 12
Tervezés Log
HtmlLog
TextLog
LogViewer
HtmlLogViewer
TextLogViewer 13
Tervezés: Az osztályok használata
int main() { int valasz; cout << "Html vagy text?" << endl; cin >> valasz; Ez miért Log * log = 0; LogViewer * viewer = 0; if (valasz == 0) { log = new TextLog; viewer = new TextLogViewer; } if (valasz == 1) { log = new HtmlLog; viewer = new HtmlLogViewer; } delete log; delete viewer; }
veszélyes?
14
Tervezés: Az osztályok használata • Több formátumnál több kódot kell beírni a különféle objektumok létrehozására • Előfordulhat, hogy hibázunk, és véletlenül egy TextLogViewer-t hozunk létre egy HtmlLog mellé • Bízzuk a különböző típusú objektumok létrehozását speciális osztályokra
15
Tervezés: Abstract factory • Írjunk egy LogFactory osztályt, amelynek van egy createLog, és egy createLogViewer függvénye • Ezek a függvények legyenek tisztán virtuálisak • A createLog egy Log *-al tér vissza • A createLogViewer egy LogViewer *-el tér vissza • Származtassunk ebből egy TextLogFactory és egy HtmlLogFactory osztályt 16
Tervezés: Abstract factory • A TextLogFactory-ban implementáljuk a létrehozó függvényeket: • A createLog egy Log *-al tér vissza, amely egy dinamikusan létrehozott TextLog objektumra mutat • A createLogViewer egy LogViewer *-el tér vissza, amely egy dinamikusan létrehozott TextLogViewer objektumra mutat 17
Tervezés: Abstract factory • A HtmlLogFactory-ban implementáljuk a létrehozó függvényeket: • A createLog egy Log *-al tér vissza, amely egy dinamikusan létrehozott HtmlLog objektumra mutat • A createLogViewer egy LogViewer *-el tér vissza, amely egy dinamikusan létrehozott HtmlLogViewer objektumra mutat 18
Abstract factory (elvont gyár) class LogFactory { public: virtual Log * createLog() const = 0; virtual LogViewer * createLogViewer() const = 0; };
19
Abstract factory (elvont gyár) class TextLogFactory: public LogFactory { public: Log * createLog() const { return new TextLog; } LogViewer * createLogViewer() const { return new TextLogViewer; } };
20
Abstract factory (elvont gyár) class HtmlLogFactory: public LogFactory { public: Log * createLog() const { return new HtmlLog; } LogViewer * createLogViewer() const { return new HtmlLogViewer; } };
21
Abstract factory (elvont gyár) […] LogFactory * factory = 0; if (valasz == 0) { factory = new TextLogFactory; } if (valasz == 1) { factory = new HtmlLogFactory; } Log * log = factory->createLog(); LogViewer * viewer = factory->createLogViewer(); 22
Abstract factory • Használatával könnyű új formátumokat hozzáadni a szoftverhez • Elkerülhető az a hiba, hogy egymással nem kompatibilis objektumokat hozzunk létre • Általában elég belőle egy is, ezért sokszor Singleton-ként alkalmazzák
23
Feladat • Egy olyan programot írunk, amely a memóriában tárolja a merevlemez könyvtárszerkezetének másolatát • Legyen lehetőség a könyvtár hierarchia kirajzolására • Egy könyvtár tartalmazhat: – Egyszerű fájlokat – Újabb könyvtárakat 24
Tervezés: Composition minta (összetétel) • Írjunk egy File és egy Directory osztályt • Írjunk egy Item osztályt, amely tartalmazza azt a felületet, amelyet a File-nak és a Directory-nak is tudnia kell (név beállítása, és a megjelenítés) • Az Item a File és a Directory őse • A Directory képes eltárolni Item * típusú változókat egy listában • A Directory azonos módon tudja 25 megjeleníteni a fáljait és alkönyvtárait
Composition class Item { protected: string name; void showSpaces(int spaces) const { int index; for (index = 0; index < spaces; index++) { cout << " "; } } public: Item(const string & name = ""): name(name) { } […]
26
Composition […] virtual ~Item() {} virtual void show(int level = 0) const = 0; void setName(const string & name) { this->name = name; } const string & getName() const { return name; } };
A showSpaces függvényt a hierarchia megjelenítésekor fogjuk használni 27
Composition class File: public Item { public: File(const string & name = ""): Item(name) { } void show(int level = 0) const { showSpaces(level); cout << "FILE: " << name << endl; } }; 28
Composition class Directory: public Item { Item ** items; unsigned int count; public: Directory(const string & name): Item(name) { items = 0; count = 0; } […] 29
Composition ~Directory() { unsigned int index; for (index = 0; index < count; index++) { delete items[index]; } delete [] items; } void show(int level = 0) const { showSpaces(level); cout << "DIRECTORY: " << name << endl; unsigned int index; for (index = 0; index < count; index++) { items[index]->show(level + 2); } }
30
Composition void add(Item * newItem) { Item ** temp = new Item*[count + 1]; unsigned int index; for (index = 0; index < count; index++) { temp[index] = items[index]; } temp[index] = newItem; count++; delete items; items = temp; } }; 31
Composition Directory root("/");; Directory * home = new Directory("home"); Directory * boot = new Directory("boot"); File * kernel = new File("kernel.img"); Directory * docs = new Directory("documents"); Directory * recipes = new Directory("recipes"); home->add(docs); docs->add(recipes); boot->add(kernel); root.add(boot); root.add(home); recipes->add(new File("csokis_suti.txt")); recipes->add(new File("halaszle.txt")); root.show();
32
Kimenet DIRECTORY: / DIRECTORY: boot FILE: kernel.img DIRECTORY: home DIRECTORY: documents DIRECTORY: recipes FILE: csokis_suti.txt FILE: halaszle.txt 33
Composition • A File és a Directory mellé tetszőleges gyerek osztályokat is felvehetünk, így összetettebb hierarchiákat is könnyen felépíthetünk • Még egy példa: Weboldalon összetett elemeket is leírhatunk vele, például egy táblázat adott cellája tartalmazhat képeket, szöveget, vagy akár újabb táblázatot is 34