1. Mintapélda Feladat: Egy ipari környezetben a gyártott szerelvények tetsz leges számú alkatrészekb l állhatnak, illetve önmagában is hat szerelvényeket, azaz hierarchikus felépítés ek, természetesen a legalsó szerkezeti szinten már csak alkatrészek állhatnak. Az alkatrészeke tekintetében több típust különböztetünk meg, melyek más-más attribútumokkal rendelkeznek. Feladatunk az egyes szerelvények szerkezetének részletes ábrázolása, és annak megállapítása, hogy az alkatrész milyen költségen állhat el, ha pusztán a benne lév alkatrészek árait nézzük. 1.1. Osztályszerkezet Mivel minden egyes alkatrésztípus külön árral rendelkezik, célszer az egyes alkatrészeket külön objektumként kezelni, így mindegyikhez önálló attribútumokat társíthatunk. Ezek az objektumok bár többféle alkatrészt írnak le, ugyanolyan szerkezettel rendelkeznek, hiszen minden alkatrészr l elég nyilvántartanunk annak nevét és cikkszámát az azonosításhoz, illetve az árát a kés bbi számításokhoz. Tehát az alkatrészeket egy önálló osztályba soroljuk a következ szerkezettel: Adattagok: cikkszám: egész szám típusú név: szöveg típusú ár: egész szám típusú M veletek: konstruktor és destruktor adattagok kiolvasása Mivel már az egyes alkatrészek létrehozásakor meg kell adnunk paramétereit, a konstruktor feladata lesz ezen paraméterek átadása az adattagoknak. Értelemszer en hiányos adatokkal rendelkez alkatrészt nem hozhatunk létre. Az osztálynak megfelel C++ kód: class { (string n, int csz, int a); ~(); string Nev() { return nev; } int Cikkszam() { return cikkszam; } int { return ar; } string nev; int cikkszam; int ar; ::(string n; int csz, int a) { nev = n; cikkszam = csz; ar = a; ::~() { // ez üresen marad, a beépített osztálytörlési eljárás // elegend, további adattagokat nem kell törölnünk 2006. november 19. 2. oldal
Az osztálynak megfelel UML osztálydiagram: string nev int cikkszam int ar string Nev() int Cikkszam() int Ezeknek megfelel en vegyünk egy példányt az osztályból, például hozzunk létre egy 1400-as cikkszámú, 30 Ft-os csavart a következ C++ utasítással: cs( csavar, 1400, 30); Így létrejön egy objektum az osztályhoz, melynek objektumdiagramja: cs: cikkszam = 1400 ar = 30 1.2. Adatismétl dés A szerelvények tehát végs soron alkatrészekb l állnak, azaz egy szerelvényben több ugyanolyan alkatrész is lehet. Bár ezek az alkatrészek ugyanazon tulajdonságokkal rendelkeznek, mégis több külön objektumként kell kezelnünk ket az összeszerelés során, így több példányt is létrehozunk azonos attribútumokkal. cs1( csavar, 1400, 30); cs2( csavar, 1400, 30); Jól látható, hogy a két objektum paramétereiben teljesen megegyezik, és mivel ugyanazon osztály példányai, felépítésükben és m veleteikben is megegyeznek, azaz csupán nevük (azonosítójuk) különbözteti meg ket, de mivel két külön objektumként kezeljük, a memóriában két külön adatterületet fog elfoglalni. Ezen objektumok létrehozása során így a következ problémák merülnek fel: Ugyanilyen paraméterekkel rendelkez objektumból ennél sokkal több példányt hozhatunk létre, ami pazarláshoz, redundanciához vezetne, hiszen a memóriában sokszor tárolnánk el ugyanazokat az adatokat. Ha a kés bbiekben módosítani akarnánk egy alkatrésztípus attribútumát (pl. emelkedett az ára), az összes bel le meglév objektumot módosítanunk kéne, ami rengeteg munkát jelent. Ha egy alkatrésztípusból az összes objektumot töröljük a rendszerb l, azok attribútumai is elvesznek, pedig kés bb lehet, hogy létre kívánunk hozni újabb példányokat. Célszer bb lenne tehát az egyes alkatrésztípusok tulajdonságait külön eltárolni, egy másik objektumba, melyb l csak egy létezne, és mely kapcsolatban állna az összes típusnak megfelel alkatrészpéldánnyal. Ezek a ó objektumok pusztán csatolt információkat tárolnak, így nevezzük ket csatolóknak. Ezzel a megközelítéssel mindhárom problémát megoldhatjuk: Az adatokat csak egy helyen tároljuk, megsz nik a redundancia. 2006. november 19. 3. oldal
Az esetleges módosítást csupán egy helyen kell elvégeznünk. A csatoló a konkrét alkatrészekt l függetlenül létezhet, így az adatok bármikor megtalálhatóak a rendszerben. 1.3. Kapcsolatok A módosított tervbe tehát be fog kerülni egy második osztály is, hiszen a csatolóknak is önálló struktúrával kell rendelkezniük, ugyanakkor minden alkatrésznek kapcsolatban állnia egy objektummal, hiszen az alkatrészek ezek után önállóan már nem nak adatokat, azaz önállóan egy alkatrészobjektum már nem használható. A két objektum így kapcsolatban lesz egymással, nevezetesen, hogy a csatoló ja az alkatrészt. Ennek UML objektumdiagramja: cs1 : csat cikkszam = 1400 ar = 30 cs2 : Az ennek megfelel egyszer sített osztálydiagram: Ezek alapján már implementálhatjuk a két osztályt. Nyilván a csatoló osztály felépítése megegyezik az eddigi alkatrészével, hiszen ugyanazon adattagokkal kell rendelkeznie, és csak az ezeket kiolvasó m veleteket kell implementálnunk. Az osztály elveszti eddigi adattagjait, pusztán annyi információt kell tárolnia, hogy elérhesse a hozzá tartozó csatolót, amit egy objektumra történ hivatkozással teszünk lehet vé. Ezt persze már az alkatrész objektum létrehozásakor meg kell adnunk, így a konstruktornak csak a csatolóra történ hivatkozást (mutatót) kell továbbadnia. Emellett az osztály szerkezete nem változik, így a küls viselkedése megegyezik a korábbival, azaz ezen osztály objektumaihoz való hozzáférések a programban változatlanok maradhatnak. class { (string n, int csz, int a); ~(); string Nev() { return nev ; } int Cikkszam() { return cikkszam; } int { return ar; } string nev; int cikkszam; int ar; ::(string n; int csz, int a) { nev = n; cikkszam = csz; ar = a; ::~() { class { 2006. november 19. 4. oldal
( cs) { csat = cs; } ~ (); string Nev() { return csat->nev(); } int Cikkszam() { return csat->cikkszam(); } int { return csat->; } csat; Ennek folyományaként az alkatrészek létrehozása több lépésb l fog állni. El ször létre kell hoznunk a megfelel csatoló objektumot, amelyben eltároljuk tulajdonságait, majd ez után hozhatjuk létre az adott típusú alkatrészt. Persze, amint egy adott alkatrész típushoz létrehoztuk a csatolót a programban, a többi ugyanilyen típusú alkatrészhez már nem kell újabb csatolót létrehoznunk. A korábbiakban látott konstrukció tehát a következ képpen valósítható meg: csatolo1( csavar, 1400, 30); cs1(&csatolo1); cs2(&csatolo1); Persze ebben a megvalósításban az alkatrész tulajdonságait továbbra is az alkatrészt l kérdezzük le, amely továbbítja a lekérdezést a hozzákapcsolt csatolóhoz. A csatolón keresztül viszont nincs módunk a hozzákapcsolt alkatrészek eléréséhez, vagyis ez egy asszimetrikus kapcsolat, amit az osztálydiagramon is ábrázolhatunk: 1.4. Üzenetátadás Az osztály els dleges feladata természetesen továbbra is az, hogy adatokat szolgáltasson magáról, amit üzenetküldéssel valósít meg. Az üzenetküldés során a program valamely más komponense, egy kliens objektum lekérdezheti például az alkatrész árát az m velettel. Az alkatrész megvalósításában fogadja az ár m veletet, ám azt rögtön tovább is adja a hozzá tartozó csatolónak, hiszen saját maga nem rendelkezik az adattal, a csatoló objektum viszont már képes visszaadni az adatot, amely így eljut a klienshez. Az üzenetküldés ábrázolása objektumdiagramban (mivel a kliens tetsz leges osztályhoz tartozhat, így nem jelöljük az osztálynevét az objektumdiagramban, illetve a továbbiakban elhanyagoljuk az egyes objektumok megnevezését, illetve egyes attribútumokat, amennyiben azok feleslegesek): kliens : 1.5. Polimorfizmus A programnak azonban szerelvényekkel is dolgoznia kell, így az alkatrészek adatai mellett az egyes szerelvények pontos felépítését is el kell tárolnunk. Szükségünk lesz egy új osztályra, mely a szerelvény objektumok m ködését megvalósítja. A Szerelveny osztály megvalósítását többféleképpen is elképzelhetjük, például a Szerelveny objektumok olyan adatszerkezetekkel rendelkezhetnek, amelyek több más objektumra is képesek hivatkozni, így míg egy alkatrész csak egy csatolóval állt kapcsolatban, a szerelvények több más objektummal is kapcsolatban lehetnek, legyenek azok alkatrészek, vagy más szerelvények (hiszen megengedtük, hogy a szerelvények szerelvényekb l is felépülhessenek). Vegyünk példaként egy egyszer szerelvényt, amely egy tartógerendából és két csavarból áll: 2006. november 19. 5. oldal
: : : nev = "tartógerenda" Ugyanakkor mivel szerelvény hat szerelvényt is, megtehetjük, hogy el bb két alkatrészt szerelünk össze, majd az így kapott szerelvényt és a harmadik alkatrészt összeszerelve megkapjuk az el bbi szerelvényt, ám az szerkezetileg mégis eltér lesz, ahogy ezt a diagrammon is ábrázoljuk: : : : nev = "tartógerenda" Tehát a hierarchikus szerkezet lehet vé teszi a többszint összeépítést is, ami a modellezés szempontjából annyit jelent, hogy a kapcsolat fennállhat két szerelvény között, valamint egy alkatrész és egy szerelvény között, azaz polimorfizmus lép fel. Ez jól ábrázolható az osztálydiagramon is: Szerelveny Ha ténylegesen így implementálnánk ezt az osztályt, az azt jelentené, hogy két külön megoldás, illetve adatszerkezet kellene a szerelvényekkel és az alkatrészekkel történ összekapcsoláshoz, ráadásul ezzel az UML diagrammunk is elveszítené típusosságát, hiszen ugyanazt a kapcsolatot két különböz osztállyal tartanánk fent. A gyakorlatban ez annyit tesz, hogyha például módosítani szeretnénk egy szerelvény összeszerelését, akkor egy alkatrész helyére nem rakhatunk szerelvényt, és fordítva, így ezzel jelent sen korlátoznánk lehet ségeinket. A megoldás itt egy olyan általános osztály létrehozása, melyre alkalmazhatjuk a asszociációt, ugyanakkor az általános osztály speciális esetei lehetnek a konkrét osztályok, melyeket 2006. november 19. 6. oldal
alkalmazunk. Így vegyük be a programunkban az Alkotoelem osztályt, melynek specializációi lesznek az és a Szerelveny osztály, így az osztálydiagram a következ képpen módosul: Alkotoelem Szerelveny 0..1 A specializáció implementálásához örökl dést alkalmazunk, ahol az sosztály szerepét az Alkotoelem veszi át, és ebb l származtatjuk két speciális osztályunkat. Nyilván a tényleges alkalmazásban alkotóelemeket sohasem fogunk betenni szerelvényekbe, hanem csak a konkrét alkatrészt, vagy szerelvényt. Így nem is fognak keletkezni önálló Alkotoelem objektumok, hiszen ez csak egy általános fogalmat képvisel, és csupán a kapcsolatot reprezentálja a két speciális alkotóelem között. Ezért az Alkotoelem absztrakt osztályként szerepel a programban, a m veleteinek egy része nem lesz értelmezve, csak a specializációiban. Már csak a rendszer m ködésének felülvizsgálata, az üzenetküldés megvalósítása maradt hátra. Az ármeghatározás esetében egy kliens egy szerelvényt l kérdezi le az árát. A szerelvény ezt az üzenetet továbbküldi az t alkotó alkatrészeknek és szerelvényeknek. Nyilván a szerelvény alkotóelemek is hasonlóan járnak el, míg az alkatrészek csatolójuktól kérdezik le a kívánt adatot, majd a kapott adatokat az egyes Szerelveny objektumok összegzik, és visszaadják. : : : nev = "tartógerenda" Azonban egy szerelvény nem tudhatja, hogy milyen alkotóelemek tartoznak hozzá, hiszen az csak absztrakt Alkotoelem objektumokat lát, ezért már az absztrakt osztálynak is nia kell a megfelel m velet felületét, ám megvalósítását nem, hiszen mint azt láttuk, a két alkotóelem fajta teljesen máshogy valósítja meg például az lekérdezést. Az alkotóelemek hozzárendelését az egyes szerelvényekhez - az alkatrész-csatoló párosításhoz hasonlóan - pointer segítségével valósítjuk meg, a különbség csak annyi, hogy mivel a szerelvény tetsz legesen sok ilyen pointert hat, a mutatókat egy tömbben kell tárolnunk. Erre a feladatra kit n a C++ STL (Standard Template Library) vector típusa, melyben tetsz leges mennyiség 2006. november 19. 7. oldal
tetsz leges adattípust tárolhatunk (jelen esetben Alkotoelem objektumokra mutató pointereket). Ezen kívül használunk három beépített függvényt: a tömbbeli elemek hozzáadására szolgáló push_back(), a tömb méretét lekérdez size(), valamint tömb kiürítésére szolgáló clear() m veletet. class { (CString n, int csz, int a); ~() {} string Nev() { return nev; } int Cikkszam() { return cikkszam; } int { return ar; } string nev; int cikkszam; int ar; class Alkotoelem { virtual ~Alkotoelem() {} virtual int = 0; virtual void Betesz(Alkotoelem a) {} virtual string Nev() { return ; } virtual int Cikkszam() { return 0; } Alkotoelem() {} class Szerelveny : public Alkotoelem { Szerelveny(); {} ~Szerelveny() { elemek.clear(); } int ; void Betesz(Alkotoelem a) { elemek.push_back(a); } vector<alkotoelem> elemek; class : public Alkotoelem { ( cs) { csat = cs; } ~ (); string Nev() { return csat->nev(); } int Cikkszam() { return csat->cikkszam(); } int { return csat->; } csat; :(string n; int csz, int a) { nev = n; cikkszam = csz; ar = a; int Szerelveny:: { int sum = 0; for ( int i = 0; i < elemek.size(); i++ ){ sum = sum + elemek[i]->; } return sum; 2006. november 19. 8. oldal