11. Osztályok Közötti Kapcsolatok és az UML Osztálydiagram
Amikor egy komplexebb szoftvert tervezünk, egyetlen osztály ritkán elegendő. A valós világ modellezése során az objektumok kapcsolatban állnak egymással, és a programunk akkor lesz jól karbantartható, ha ezeket a kapcsolatokat a megfelelő tervezési minták alapján építjük fel.
Az osztályok közötti kapcsolatoknak két fő típusa van: a specializáció ("is-a" kapcsolat) és az asszociáció/tartalmazás ("has-a" kapcsolat).
1. Specializáció (is-a kapcsolat)
A specializáció a hagyományos öröklődés (inheritance). Azt fejezi ki, hogy az egyik osztály egy speciálisabb változata a másiknak (például a Car is a Vehicle - Az autó egy jármű).
Ilyenkor a leszármazott osztály mindent megörököl az ősosztályától, amit kiegészíthet vagy módosíthat. Ezt a megoldást akkor használjuk, ha egyértelmű logikai alá-fölé rendeltségi viszony van az osztályok között.
2. Asszociáció és Tartalmazás (has-a kapcsolat)
Nagyon sokszor a specializáció nem logikus, vagy a memóriakezelés szempontjából pazarló. Ilyenkor a tartalmazást használjuk: az egyik osztály egy referenciát (adattagot) tartalmaz a másik osztályra. A tartalmazásnak is két fontos válfaja van az alapján, hogy az objektumok élettartama mennyire függ össze:
A) Aggregáció (Laza kapcsolat)
Az aggregáció egy "laza" tartalmazás. Azt jelenti, hogy a tartalmazott objektum önállóan, a tartalmazó objektum nélkül is képes létezni és van értelme a rendszerben (például Car has-a Engine).
-
Az aggregációt általában úgy valósítjuk meg, hogy a tartalmazott objektumot egy paraméterként adjuk át a tartalmazó osztály konstruktorának.
-
Példa: Ha egy autó (Car) megsemmisül a programban, a benne lévő motor (Engine) objektumot még átszerelhetjük egy másik autóba, tehát nem kötelező megsemmisülnie vele együtt.
B) Kompozíció (Erős kapcsolat)
A kompozíció egy elválaszthatatlan, erős kapcsolat. A tartalmazott objektum önmagában nem létezhet, csak mint az egésznek a része (például University has-a Senate - Az egyetemnek van szenátusa).
-
A kompozíciót gyakran példányszintű tagosztályokkal (beágyazott osztályokkal), vagy az objektum létrehozásának beágyazásával valósítják meg. Azaz a szenátus objektum magában az egyetem konstruktorában születik meg (
this.senate = new Senate();). -
Példa: Ha az egyetem megszűnik, a szenátus sem létezhet tovább önállóan.
3. Kapcsolatok számossága
A "has-a" kapcsolatokban a résztvevő objektumok száma alapján három esetet különböztetünk meg:
-
Egy-egy (1-1): Az egyik osztály pontosan egy példánya kapcsolódik a másik egy példányához (pl. egy autónak egy motorja van). Ilyenkor egyszerű adattagként tároljuk a referenciát.
-
Egy-több (1-N): A tartalmazó osztály egy objektumhoz több tartalmazott példány is tartozhat (pl. egy intézethez több tanszék is tartozik). Ezt általában egy tömb (
Típus[]) vagy egy dinamikus lista (pl.ArrayList<Department>) adattaggal valósítjuk meg a tartalmazó osztályban. -
Több-több (N-M): Mindkét oldal több példánnyal kapcsolódhat a másikhoz (pl. egy diák több tantárgyat felvehet, és egy tantárgyat több diák is felvehet). Ilyenkor mindkét osztálynak tartalmaznia kell egy listát a másik osztály példányaiból, és figyelni kell az integritási szabályokra (a listák szinkronizálására).
4. Tervezési Alapelv: Kompozíció az Öröklődés helyett
Az OOP világában ismert ökölszabály a "Composite Reuse Principle" (Kompozíció az öröklődés felett). Ez kimondja, hogy az öröklődést csak akkor használjuk, ha feltétlenül muszáj, minden más esetben a kompozíciót/aggregációt érdemes előnyben részesíteni a polimorfizmus és a kód újrahasznosíthatósága érdekében.
Példa: Könyv (Book) és Könyvpéldány (BookInstance).
Ha öröklődést használnánk (BookInstance extends Book), akkor minden egyes könyvpéldány létrehozásakor újra memóriát kéne foglalnunk a címnek, szerzőnek stb.
Ehelyett az aggregáció a helyes megoldás: A Book osztály tárolja a címet és a szerzőt. A BookInstance osztály pedig csak egy leltári számot (inventoryNo) és egyetlen referenciát (Book book) tartalmaz, ami a megfelelő könyv objektumra mutat. Ezzel rengeteg memóriát spórolunk, és elkerüljük az adatanomáliákat.
5. Az UML Osztálydiagram
A komplex rendszereket a Unified Modeling Language (UML) segítségével tervezzük meg. Az osztálydiagramon az osztály egy téglalap, amely tartalmazza a nevét, attribútumait (adattagjait) és metódusait.
-
Láthatóság: A
+jelzi a public, a-a private, a#pedig a protected elérést. -
Specializáció (Öröklődés): Egy folyamatos, üres, háromszög alakú nyíl mutat a leszármazotttól az ősosztály felé.
-
Interfész implementálása: Ugyanolyan üres nyíl, de szaggatott vonallal.
-
Aggregáció: Üres rombuszfejű nyíl, amely a tartalmazó osztály felé mutat.
-
Kompozíció: Fekete (kitöltött) rombuszfejű nyíl, ami szintén a tartalmazó osztályhoz mutat. A vonalak mellé írt számok (pl. 1, N, M) mutatják a kapcsolat számosságát.
Gyakorló feladat
Készíts egy példát, amely bemutatja az Egy-több (1-N) Aggregációt C#-ban!
-
Hozz létre egy
Konyvosztályt egy Címmel! -
Hozz létre egy
Konyvtarosztályt, aminek van neve, és egy dinamikus listát (List<Konyv>) használ a könyvek tárolására! -
Írj egy metódust a könyvtárba (
UjKonyv), ami egy meglévő könyv-referenciát fogad el (aggregáció), és hozzáadja a listához. -
A főprogramban példányosíts két könyvet és egy könyvtárat, majd add hozzá a könyveket a könyvtárhoz.
A feladat megoldása
using System;
using System.Collections.Generic; // A List<T> használatához kötelező
class Konyv
{
public string Cim { get; set; }
public Konyv(string cim)
{
this.Cim = cim;
}
}
class Konyvtar
{
public string Nev { get; set; }
// 1-N Aggregáció: Egy könyvtárnak több könyve is lehet
private List<Konyv> konyvek = new List<Konyv>();
public Konyvtar(string nev)
{
this.Nev = nev;
}
public void UjKonyv(Konyv ujKonyv)
{
konyvek.Add(ujKonyv);
Console.WriteLine($"A '{ujKonyv.Cim}' című könyv bekerült a(z) {this.Nev} könyvtárba.");
}
}
class Program
{
static void Main(string[] args)
{
// Könyvek létrehozása (Önállóan is léteznek)
Konyv k1 = new Konyv("Harry Potter");
Konyv k2 = new Konyv("A Gyűrűk Ura");
// Könyvtár létrehozása
Konyvtar varosiKonyvtar = new Konyvtar("Városi Főkönyvtár");
// Az objektumok összekapcsolása (Aggregáció)
varosiKonyvtar.UjKonyv(k1);
varosiKonyvtar.UjKonyv(k2);
Console.ReadKey();
}
}