oop alapismeretek

30
Budapest, 2011. Óbudai Egyetem Neumann János Informatikai Kar Szoftvertechnológia Intézet Objektum Orientált Programozás - Rövid összefoglaló Szántó Balázs [email protected]

Upload: norbert-deberling

Post on 22-Oct-2015

61 views

Category:

Documents


2 download

DESCRIPTION

Objektum orientált programozás alapismeretek

TRANSCRIPT

Budapest, 2011.

Óbudai Egyetem

Neumann János Informatikai Kar

Szoftvertechnológia Intézet

Objektum Orientált Programozás

-

Rövid összefoglaló

Szántó Balázs

[email protected]

Szántó Balázs ÓE-NIK-IAR, 2011

2

1 Bevezető

A rövid összefoglaló azzal a céllal készült, hogy összegyűjtse, egy helyen rendszerezze azt a tudást (AAO-OOP), melyeket mind a PPT és mind a VEP kurzuson elvárnak a hallgatóktól. Az első lépések sokszor elég nehézkesek, csak úgy, mint e tárgy keretében. Remélem mindenkiben minél hamarabb kialakul egy pozitív kép.

Az anyag megértéshez szükséges a C# alapvető szintaxisának ismerete. A dokumentum felépítése nagy részben követi a Miklós Árpád AAO-s előadássorozatát.

2 Az objektum orientált program

Egy (algoritmikus) feladatot sok féleképpen megközelíthetünk. Az objektum orientált program az adatok oldaláról közelíti meg a problémát. A szükséges absztrakt rendszerelemeket meg kell határozni, majd ezeknek az elemeknek az adatait és műveleteit kell definiálni és összerendelni. Tehát az egyes elemeket csoportokba soroljuk. A probléma megoldását az egyes objektumok közötti kommunikáció, és az egyes műveletek állapotváltozásoktól függő műveletek végrehajtása adja meg. Az objektumok kapcsolódási felülettel rendelkeznek, melynek segítségével üzeneteket váltanak egymással (Az egyes objektumok magukban foglalják az algoritmusokat). Mit is jelent ez? Nézzünk meg egy rövid példát. Célunk egy képszerkesztő program megvalósítása. A fenn leírtak szerint a következők történnek (persze ezt lehetne mélyebben is elemezni):

Meghatározzuk a szükséges absztrakt rendszerelemeket, adatait és műveleteit:

1) Kép: a) Adatai: maga a kép (pointer a memóriában helyezkedő kezdőcímre), felbontás,

fájlformátum,… b) Műveletei: Kép betöltése, hisztogram készítése, …

2) Megjelenítés: a) Adatai: ablak neve, mérete, elrendezése, … b) Műveletei: kép megjelenítése, hisztogram megjelenítése, …

3) Feldolgozás: a) Adatai: a feldolgozó műveletek (szűrők, effektek,…) paraméterei b) Műveletei: adott képet áttranszformálni a megfelelő művelet segítségével

1. ábra - Az objektumok világa.

Szántó Balázs ÓE-NIK-IAR, 2011

3

3 Objektum orientált programozás C# nyelven

3.1 Az objektum orientált paradigma alapelemei

A paradigma alapelemei

3.1.1 Osztály és objektum

Az objektum állapottal rendelkező entitás, amely a benne tárolt adatok felhasználásával feladatokat hajt végre és egyéb objektumokkal kommunikál. Adatokat és algoritmusokat tartalmaz. Saját feladatait önállóan végzi. Az objektum saját adatait mezőnek, beépített algoritmusait metódusnak nevezzük. Az objektumok metódusaikkal vesznek részt üzenetváltásokban. Az üzenetek elemei: célobjektum, metódus, paraméterek és eredmény.

Az osztály egy adott objektumtípust határoz meg annak adataival (mezők) és beépített algoritmusaival (metódusok). Az osztály egyfajta sablont, mintát adnak az objektumokhoz. Az osztályok tehát azonos adatszerkezetű és viselkedésű objektumokat írnak le. Más kifejezéssel élve az egyes objektumok azon osztályok példányai, amelyekhez tartoznak.

class MyClass {

//Ez egy üres osztály }

Osztályokat a class kulcsszó segítségével hozhatunk létre. A mezők megfelelői a változók (típusok), ezeken keresztül tárolódik az adat. Ahogy az ábrán láthatjuk, hogy egy mező lehet például int típusú változó, vagy egy osztálytípus is (tehát egy másik objektum). Nézzünk meg egy rövid példát (Szerepel két osztály: Hallgato és Leckekonyv, a Hallgato tartalmaz egy Leckekonyv típusú objektumot. Mellékesen nem számít, hogy a kódban melyik osztályt írjuk le először).

Szántó Balázs ÓE-NIK-IAR, 2011

4

//Egy felsorolás típus enum Nem {

Férfi, Nő } //Hallgató class Hallgato {

//Hallgató osztály által leírt objektum sablon adatmezői int életkor = 10; //Lehet egyből inicializálni, a későbbiekben erre //még nézünk példát. Ha nem inicializálunk, akkor a változó default //értékét veszi fel az adott mező (változó)

Nem nem;

//Objektum típus Leckekonyv leckekönyv; } //Leckekönyv class Leckekonyv { }

A mezők két típusa az érték- és referenciatípus. Az értéktípusok és a referenciatípusok elsősorban az adatok kezelésében és a hozzáférés módjában különböznek egymástól. Különféle referenciatípusokba tartozó változók (az értéktípusoktól eltérően) nem maguk tárolják az adatokat, csak hivatkoznak rájuk, azaz „referenciát” tartalmaznak a tényleges adatokra. Egy referenciát tartalmaznak, mely egy memóriacímre mutat, ahol az adott értéket tárolják (ahol allokálták a heapen). Ez a megoldás elsősorban nagyobb adatmennyiségnél jelent előnyt, mivel így paraméterátadáskor vagy helyi változók használatánál nincs szükség a nagy mennyiségű adat többszöri másolására (elég, ha a hivatkozások másolódnak le), ami sokkal nagyobb teljesítmény elérését teszi lehetővé.

A különféle értéktípusokba tartozó változók ezzel szemben közvetlenül tartalmazzák adataikat, így kezelésük sokkal egyszerűbb, létrehozásuk és megszüntetésük sokkal gyorsabban végrehajtható. Nagyobb mennyiségű adatot azonban nem célszerű így kezelni, elkerülendő a programok logikája szempontjából mindenképpen szükséges másolási műveleteket. A metódusok során újra elő fogjuk venni az érték és referencia típusok szerepét.

Szántó Balázs ÓE-NIK-IAR, 2011

5

2. ábra - Az alapvető különbség megfigyelhető az ábrán.

3. ábra - A típusok csoportosítása.

A következőben a metódusokról fogunk beszélni. A metódusok felelősek az adott osztály funkcionalitásáért. Az objektumok metódusaikkal vesznek részt üzenetváltásokban. Nézzük meg egy metódus paraméteres szignatúráját.

Szántó Balázs ÓE-NIK-IAR, 2011

6

<visszatérés típusa> <Metódus neve>(<adat típus> <név>, <adat típus> <név>, …) {

//Metódus test }

Visszatérés típusa:

void: Ebben az esetben a metódus nem tér vissza értékkel.

<változó típus>: float, string, …,<osztálytípus>

class MyClass { int Összeadás(int a, int b) { return a + b; }

void AktuálisIdő() { Console.WriteLine("Az aktuális idő: {0}", DateTime.Now); }

}

Létrehoztunk egy MyClass nevű osztályt, mely tartalmaz egy Összeadás metódust. Ez két bemeneti paramétert vár, és visszatér ezek összegével. A visszatérés a return kulcsszó segítségével lehetséges. A másik metódus kiírja a képernyőre az aktuális időt, és nem tér vissza értékkel.

3.1.2 Speciális metódusok: konstruktor, destruktor

A fejezetben ismertetésre került, hogy hozhatunk létre osztályokat. Az osztályok azonos funkcionalitású objektumokat írnak le. Hogy lesz az osztályból objektum? Hogyan hozhatunk létre új objektumokat? A válasz egyszerű, a new kulcsszó segítségével hozhatunk létre. Nézzük meg lépésről lépésre az objektum születését, előtte azonban megismerkedünk két speciális metódussal, a konstruktorral és a destruktorral.

Ahhoz, hogy az objektumokat használhassuk, először létre kell hozni őket. Ez a művelet a példányosítás. A konstruktor egy olyan metódus, amelynek segítségével példányok hozhatóak létre egy osztályból. Minden osztályhoz tartozik konstruktor (ha külön nem definiálunk, akkor is létrejön). Feladata az új objektum létrehozása, a hozzá tartozó mezők kívánt kezdőértékeinek beállítása (inicializálás), egyéb szükséges műveletek végrehajtása.

A konstruktor metódusra a következőknek kell teljesülnie (konstruktor metódusra példa a kiemelt kódrész):

Ugyan az a neve, mint az adott osztály neve

Nincs visszatérési értéke

Automatikusan meghívódik, amikor új példány létrejön az osztályból

Minden objektumot egyedien inicializálhat

Egy osztály tartalmazhat több konstruktort is, más paraméterezéssel

Szántó Balázs ÓE-NIK-IAR, 2011

7

Az alábbiakban egy példa tekinthető meg (lentebb a részletes kód). Létrehoztunk egy … osztályt, mely a következő adatmezőkkel és metódusokkal rendelkezik:

int objektumID; int Összeadás(int a, int b){…} void AktuálisIdő(){…}

Ezen felül rendelkezik két konstruktor metódussal is:

1. public MyClass(int objektumID){…} 2. public MyClass(){…}

Az első egy paraméteres konstruktor. A második pedig paraméter nélküli. Hogyan használhatjuk a konstruktor metódusokat új objektumok létrehozásánál, mi a kapcsolat? Amikor létrehozunk a Visual Studio segítségével egy új projektet, akkor már van egy osztályunk készen, amely tartalmaz egy metódust, mely a program belépési pontja, itt lesz az első lehetőségünk objektum létrehozására:

Szántó Balázs ÓE-NIK-IAR, 2011

8

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication2 { class Program { static void Main(string[] args) { } } }

Első lépésben létrehozunk egy új MyClass változót, és a new kulcsszó segítségével ténylegesen is létrehozzuk az új objektumot.

MyClass sajátObjektumParaméteres = new MyClass(10);

Ilyenkor a paraméteres konstruktor metódus fog lefutni. A kódban találkozhatunk a this kulcsszóval. Ezzel a kulcsszóval hivatkozunk az objektumunkra (arra az objektumra, ami éppen futtatja/végrehajtja az adott metódust), az osztály definíción belül. Használata nem mindig kötelező, de ha az osztály tartalmaz olyan adatmező neveket, amely valamelyik metódus (akár konstruktor) paraméter listáján szerepel, akkor egyértelműen meg kell különböztetni melyik az objektum adatmezője és melyik a paraméterlista változója.

Szántó Balázs ÓE-NIK-IAR, 2011

9

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { //Létrehoztunk egy új objektumot a paraméteres konstruktor segítségével MyClass sajátObjektumParaméteres = new MyClass(10); //Létrehoztunk egy új objektumot a paraméter nélküli konstruktor //segítségével MyClass sajátObjektum = new MyClass(); Console.ReadLine(); } } class MyClass { int objektumID; //Paraméteres konstruktor public MyClass(int objektumID) { Console.WriteLine("Létrejött az objektum, objektum ID: {0}",objektumID); //FONTOS this.objektumID = objektumID; } //Paraméter nélküli konstruktor public MyClass() { Console.WriteLine("Létrejött az objektum, objektum ID: {0}", this.objektumID); } int Összeadás(int a, int b) { return a + b; } void AktuálisIdő() { Console.WriteLine("Az aktuális idő: {0}",DateTime.Now); } } }

Az objektumokat utolsó felhasználásuk után meg kell szüntetni. Erre alkalmas a destruktor metódus, melynek segítségével létező objektumok elpusztíthatóak. Ez lehet a programozó feladata, vagy rendszerautomatizmus is. A konstruktornak nincs visszatérési értéke. Ritkán alkalmazzák.

Szántó Balázs ÓE-NIK-IAR, 2011

10

class MyClass { ~MyClass() { Console.WriteLine("Vége az objektumunknak!"); } }

3.2 Egységbezárás, adatrejtés

Az objektumok adatait és a rajtuk végezhető műveleteket szoros egységbe zárjuk. Az adatok csak a definiált műveletek által elérhetőek. Más műveletek nem végezhetők az objektumokon. Az egységbezárás védi az adatokat a téves módosításoktól.

Az absztrakciók megvalósításának részleteit elrejtjük a „külvilág” elől, ezt az elvet nevezzük adatrejtésnek. Az objektumokon belül elkülönítjük a belső (privát) és a külső (nyilvános) adatokat és műveleteket. A privát adatok és műveletek csak a megvalósításhoz szükségesek. A nyilvános adatok és műveletek a szoftverrendszer többi objektuma számára (is) elérhetők.

A következő fejezetekben ezekre láthatunk majd konkrét példákat.

3.2.1 Névterek

Minden osztálynak, objektumnak, mezőnek, metódusnak egyedi nevet kell adni, hogy a későbbiekben hivatkozni lehessen rá. Látható, hogy a program növekedésével egyenesen arányos nő a névütközések valószínűsége. Névtereket hozunk létre, melyben az elnevezéseket hierarchikusan csoportosítjuk. Minden elnevezésre csak a saját névterén lehet hivatkozni, ezért a névtéren belül minden elnevezés egyedi. Tetszőleges mélységben egymásba ágyazhatóak. CSAK logikai csoportosítást jelentenek (JAVA esetén a külön névterekhez tartozó fájlok külön mappákban helyezkednek el).

Szántó Balázs ÓE-NIK-IAR, 2011

11

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication2 { class Program { static void Main(string[] args) { MyClass objektum = new MyClass(); MyNamespace.MyClass másikNévtér = new MyNamespace.MyClass(); Console.ReadKey(); } } class MyClass { } } namespace MyNamespace { class MyClass { } }

4. ábra - Példa a névterek használatára.

Szántó Balázs ÓE-NIK-IAR, 2011

12

3.2.2 Láthatósági szintek

Az egyes osztályok és hozzá tartozó mezők (adatok+metódusok) láthatósági szinttel rendelkeznek. Ezzel lehet befolyásolni, hogy az egyes elemeket ki használhatja (kód melyik részén, melyik objektum), ki módosíthatja. A .NET láthatósági szintjei:

public: Bárki hozzáférhet az adott elemhez.

protected: Csak a tartalmazó osztályon vagy struktúrán belül lehet hozzáférni az elemhez. Ezen felül, az osztályból származtatott osztályban is hozzá lehet férni az elemhez. (Későbbiekben lesz rá példa)

private: Csak a tartalmazó osztályon vagy struktúrán belül lehet hozzáférni az elemhez.

internal: Assemblyn belül hozzáférhető bárhol. Azon kívüli assemblyben nem.

protected internal:

Nézzünk meg egy példát a public és protected mezőkre.

Szántó Balázs ÓE-NIK-IAR, 2011

13

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication3 { class Program { static void Main(string[] args) { Tanulo Jani = new Tanulo("Macska János", 20, 2, 5, 5); Console.WriteLine(Jani.töriJegy); Console.WriteLine(Jani.matekJegy); Console.WriteLine(Jani.angolJegy); //Console.WriteLine(Jani.életkor); ---> Erre hibát dob, mivel a //láthatóság private, csak a Tanulo osztályon belül lehet használni. //Console.WriteLine(Jani.név); ---> Erre hibát dob, mivel a láthatóság //private, csak a Tanulo osztályon belül lehet használni. Jani.matekJegy = 5; //Jani.Hello(); ---> Erre hibát dob, mivel a láthatóság private, csak a //Tanulo osztályon belül lehet használni. } } class Tanulo { string név; private int életkor; public int matekJegy; public int angolJegy; public int töriJegy; double átlag; public Tanulo(string név, int életkor, int matekJegy, int angolJegy, int töriJegy) { this.név = név; this.életkor = életkor; this.matekJegy = matekJegy; this.angolJegy = angolJegy; this.töriJegy = töriJegy; átlag = (this.matekJegy + this.angolJegy + this.töriJegy) / 3.0; //this.Hello(); vagy csak egyszerűen így //Erre nem fog hibát dobni, még az osztályon belül vagyunk Hello(); } private void Hello() { Console.WriteLine("Sikeres függvényhívás!"); } } }

Szántó Balázs ÓE-NIK-IAR, 2011

14

Ha nem írunk semmit a láthatóságnak, akkor adatmezők és metódusok esetében alapértelmezett a private láthatóságot vesznek fel. Pár adatmezőt bárki elérhet, mint például a matekJegy –et. Az objektum létrehozása után, beállítjuk a megfelelő adatokat, de ezek közül a későbbiekben párat meg lehet változtatni.

A láthatósági szintek segítségével különítjük el az osztály belső használatra szánt, illetve kívülről is elérhető tagjait. Nyilvános láthatósággal látjuk el az osztály külvilág felé látható felületét. A privát elemek pedig szigorúan belső jellegű funkcionalitást végeznek el. A láthatósági szintek segítségével hatékonyan, biztonságosan (adatrejtéssel együtt) valósítható meg az egységbezárás.

3.2.3 Osztály és példány szintű tagok

Megkülönböztetünk osztály és példány szintű tagokat. Az eddigiekben csak példány szintű tagokkal (mezők, metódusok, …) foglalkoztunk. Az objektum példány szintű tagjának számit a saját adatmezői és saját adatain műveleteket végző metódusok. A példány szintű mezők tárolják a példányok állapotát. Tehát ha létrehozunk egy új objektumot, a példány szintű mezői csak az objektumon keresztül elérhető (az objektumhoz tartozik).

Milyen lehetőség van az olyan adatok tárolására, amely minden létrehozott objektumra egyformán igaz vagy az adott osztályról szolgáltat adatot? Ezeket a tagokat hívjuk osztály szintű tagoknak. Az osztály szintű tagok az egyes osztályokhoz tartozó egyedi adatmezők, valamint az ezeken (csak az osztály szintű adatmezőkön) műveleteket végző metódusok.

Ha egy adatmezőt vagy metódust osztályszintűként szeretnénk használni, akkor el kell látni a static kulcsszóval. Nézzünk meg egy példát. Most már az osztályunk tartalmaz két darab statikus tagot. Egy statikus mezőt, mely minden egyes objektum létrehozásakor növekedik, ezt a konstruktorban hajtjuk végre. A statikus metódus segítségével pedig eldöntjük, hogy két megadott (paraméterlista) Tanulo közül melyiknek van jobb átlaga. Meg kell jegyezni, hogy osztály szintű metódus csak a paraméterként átadott típusokkal és az osztály statikus (osztály szintű) adatmezőivel végezhet műveleteket. Mivel a paraméter listán két Tanulo objektum szerepel, és a statikus metódus is a Tanulo osztályon belül helyezkedi el, így a privát adatmezőket is elérhetjük ilyen módon.

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication3 { class Program { static void Main(string[] args) { Tanulo Jani = new Tanulo("Macska János", 20, 3, 5, 5); Tanulo Gergo = new Tanulo("Mózes Gergő", 21, 3, 4, 5);

//Példa statikus mező elérésére Console.WriteLine("A létrehozott tanulók száma: {0}",Tanulo.darab); //Példa statikus metódus hívására Tanulo.KiAJobbTanulo(Jani, Gergo); Console.ReadLine(); }

Szántó Balázs ÓE-NIK-IAR, 2011

15

} class Tanulo { int id; string név; private int életkor; public int matekJegy; public int angolJegy; public int töriJegy; public static int darab = 0; double átlag; public Tanulo(string név, int életkor, int matekJegy, int angolJegy, int töriJegy) { this.név = név; this.életkor = életkor; this.matekJegy = matekJegy; this.angolJegy = angolJegy; this.töriJegy = töriJegy; átlag = (this.matekJegy + this.angolJegy + this.töriJegy) / 3.0; this.id = darab; darab++; } public static void KiAJobbTanulo(Tanulo a, Tanulo b) { if (a.átlag>b.átlag) { Console.WriteLine("A jobb tanuló: {0}", a.név); } else if (b.átlag < a.átlag) { Console.WriteLine("A jobb tanuló: {0}", b.név); } else { Console.WriteLine("Ugyan olyan szinten vannak mindketten!"); } } } }

Statikus lehet akár egy osztály is. Ilyenkor az adott osztályból nem lehet példányosítani (új objektumot-> változót előállítani). Az ilyen osztály nem tartalmaz példány szintű tagokat, csak osztály szintűeket. A statikus osztályok használata kényelmes, ha csak input adatokon kell műveleteket végezni és nem kell közbülső állapotokat tárolni. A .NET Framework is számos statikus osztály tartalmaz, pár példa:

System.Console - A System névtérbe tartozó Console statikus osztály

System.Math

Szántó Balázs ÓE-NIK-IAR, 2011

16

Egy példa statikus osztályra. Ez is tartalmazhat konstruktor, mellyel kezdőértékeket lehet beállítani.

public static class MyStatiClass { static int a; //Konstruktor – egyszer fut le static MyStatiClass() { a = 0; } }

Érdemes azért figyelni pár dologra, amikor statikus metódusokat használunk:

Ne rakjunk sok statikus metódust egy osztályba, csak jó indokkal

Statikus metódusok használata során le kell mondani az objektum orientált paradigma pár hasznos funkciójáról (öröklés, erről később)

3.2.4 Tulajdonságok

A tulajdonság olyan nyelvi elem, amely felhasználás szempontjából adatmezőként, megvalósítás szempontjából metódusként viselkedik. Lényegében az adatok hozzáférésével kapcsolatosak. Nézzük meg a szintaktikát egy példán keresztül. Van egy átlag nevű változónk (privát hozzáférésű) és az alatta lévő kódsor jelenti maga a tulajdonságot. Látszólag teljesen új szintaktika. De lényegében két metódus összeépítve egy szintaktikai elemmé.

double átlag;

public double Átlag { get { return átlag; } set { átlag = value; } }

Az első része: get { return átlag; } lényegében ennek felel meg a működését tekintve: public double get() { return átlag; }

A második része: set { átlag = value; } pedig ennek feleltethető meg: public void set(double value) { átlag = value; }

Nyilván a get { return átlag; } és set { átlag = value; } részbe kerülhet több kód is. Nem fontos mindkét metódust implementálni, állhat csak így is (ilyenkor tudjuk olvasni már a privát mezőt, de változtatni továbbra sem tudunk rajta):

double átlag;

public double Átlag { get { return átlag; } }

Ha például létrehozunk egy mezőt privát láthatósággal, akkor használhatunk tulajdonságokat, hogy hozzáférhessünk az adott adatmezőkhöz. Nézzük meg, hogy lehet tulajdonságokat használni.

Szántó Balázs ÓE-NIK-IAR, 2011

17

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication3 { class Program { static void Main(string[] args) { Tanulo Jani = new Tanulo("Macska János", 20, 3, 5, 5); Tanulo Gergo = new Tanulo("Mózes Gergő", 21, 3, 4, 5); //Jani átlagánka kiiratása tulajdonság segítségével Console.WriteLine("Jani átlaga: {0}",Jani.Átlag); double janiÁtlag = Jani.Átlag; //Jani átlagának módosítása a tulajdonság segítségével Jani.Átlag = 5.0; Console.WriteLine("Jani átlaga: {0}", Jani.Átlag); Console.ReadLine(); } } class Tanulo { string név; private int életkor; int matekJegy; int angolJegy; int töriJegy; double átlag; public double Átlag { get { return átlag; } set { átlag = value; } } public Tanulo(string név, int életkor, int matekJegy, int angolJegy, int töriJegy) { this.név = név; this.életkor = életkor; this.matekJegy = matekJegy; this.angolJegy = angolJegy; this.töriJegy = töriJegy; átlag = (this.matekJegy + this.angolJegy + this.töriJegy) / 3.0; } } }

Ami fontos a tulajdonságokkal kapcsolatban

A tulajdonságoknak nincs paraméterlistájuk, a get, set és value kulcsszavak.

Szántó Balázs ÓE-NIK-IAR, 2011

18

A value típusa ugyan az, amit a tulajdonság fejlécében megadtunk.

Érdemes Nagy kezdőbetűs elnevezést adni neki, lehetőleg ugyan az legyen, amelyik mezővel kapcsolatban áll.

A tulajdonság tehát felfogható intelligens mezőként, melyhez ugyanúgy tartozik láthatóság, és hasonlóan lehet statikus is (amennyiben statikus adatmezőhöz kötjük).

static int darab; public static int Darab { get { return Tanulo.darab; } set { Tanulo.darab = value; } }

3.3 Öröklés

A már meglévő objektum típusok (osztálydefiníciók) alapján készíthetünk új típusokat, melyek rendelkeznek az ős tulajdonságaival (származtatás). A leszármazottak bővíthetik, esetenként akár szűkíthetik az őstípus állapotterét illetve műveleteit. Az alapelv következetes alkalmazásával elérhető, hogy a már megvalósított funkcionalitás később a megvalósítás részleteinek ismerete nélkül is felhasználható legyen. Jól átgondolt előzetes tervezést igényel.

5. ábra – Példa egy öröklési hierarchiára.

Létrehozunk egy Alakzat osztályt, melyre meghatározzuk a funkcionalitását (Mozgat(), Forgat(), Kirajzol()) és az adatmezőket. Az egyes alakzatoknál különböznek a funkciók megvalósításai. Azonban ezt tervezés során nem vesszük figyelembe. A leszármaztatás segítségével ugyanazt a funkciót lehet biztosítani, különböző rájuk jellemző megvalósításban.

Szántó Balázs ÓE-NIK-IAR, 2011

19

6. ábra - Példa egy öröklési hierarchiára.

Miért fontos az öröklés szerepe az objektum orientált szemléletben? Ha egyszer elkészítünk egy programot (osztály könyvtárat), akkor, ha van rá lehetőség a jövőben is újrafelhasználnánk. Egy hiba (bug) jelentkezésekor vagy új vevői igény beérkezésekor a lehető legkevesebbet szeretnénk a kódon változtatni.

Egy osztálynak akár több őse is lehet, ezt nevezzük többszörös öröklésnek. Ez egy sokat vitatott lehetőség néhány OOP megvalósításban, mivel sok csapdát rejt magában. Így kialakíthatunk több osztály tulajdonságait ötvöző és kiegészítő osztályokat. Jól hangzik elsőre. Sajnos igen nehéz megvalósítani, nagy rendszereknél lehetetlen következetesen megtervezni.

Szántó Balázs ÓE-NIK-IAR, 2011

20

7. ábra - A többszörös öröklés problémái.

3.3.1 Speciális metódusok öröklődése

Ahogy az előzőleg leírtuk, az öröklés során az öröklő osztály rendelkezni fog az ős osztály metódusaival. Azonban a speciális metódusok öröklésénél, mint a konstruktornál és destruktornál ez kicsit máshogy történik.

Van egy ős osztályunk számos adatmezővel, és ebből leszármaztatunk egy osztályt, melyet kiegészítünk még adatmezőkkel. Létrehozunk a leszármaztatott osztályból egy objektumot. Ilyenkor biztosítani kell azt is, hogy az ős adatmezői is inicializálva legyenek, ne csak a bővített adatmezők. Tehát elsőnek az ős osztály konstruktorát kell lefuttatunk, majd a leszármaztatott osztály konstruktorát. Destruktornál ez pontosan fordítva történik. Először az utódok esetleges belső objektumait kell felszámolni, hiszen az utódok destruktora még hivatkozhat az ősök belső objektumaira.

3.3.2 Konrét megvalósítás C# környezetben

A C# az egyszer öröklést támogatja csak. A System névtérben található Object osztály minden osztálynak az őse (System.Object), kód szinten ez nincs jelölve. Az öröklést a : operátorral használhatjuk. A példában létrehoztunk egy Alakzat nevű osztályt, mely két adatmezővel rendelkezik. A Háromszög osztály örököl az Alakzat osztálytól. Tehát a Háromszög osztály az egy adatmezőről három adatmezőre vált vagy kiegészíti az alakzat adatmezőit egy új adatmezővel (melyik oldalról nézzük). Fontos szerepet kapnak az örökléskor a láthatósági szintek. A public és protected láthatóságú mezők és metódusok a leszármazott osztályban hozzáférhetőek, azonban a private mezők nem.

Szántó Balázs ÓE-NIK-IAR, 2011

21

class Alakzat { public int terület; public int pozíció; } class Háromszög : Alakzat { public int a; public int b; public int c; }

8. ábra - A Obejct osztály által biztosított funkciók, ezt minden osztály megkapja.

Amikor példányosítunk egy származtatott osztályból, akkor a fordító először az ős típus adatait inicializálja, utána pedig a származtatott osztályét. Tehát a konstruktorok között hívási sorrendet állítunk fel. Vizsgáljuk meg, milyen esetek merülhetnek fel a konstruktor öröklésével kapcsolatban:

Ha az ősosztályban van paraméter nélküli konstruktor, akkor az a leszármazott osztály konstruktorában automatikusan meghívódik

Szántó Balázs ÓE-NIK-IAR, 2011

22

Ha az ősosztályban nincs paraméter nélküli konstruktor, az ősosztály konstruktorát meg kell hívni a leszármazott osztály konstruktorából

Létrehoztunk egy Ember és egy Hallgató osztályt, melyet az előző osztályból származtattunk (jelölése: class Hallgató:Ember{…). Az egyes mezőkhöz elkészítettük a megfelelő tulajdonságokat is. Az Ember osztály paraméteres konstruktorral rendelkezik, tehát ha származtatunk (Hallgató:Ember), akkor a származtatott osztályban (Hallgató) meg kell hívni az ősosztály (Ember) legalább egyik konstruktorát ().

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication5 { class Program { static void Main(string[] args) { Ember Gergely = new Ember("Köves Gergely", Nem.Férfi);

Szántó Balázs ÓE-NIK-IAR, 2011

23

Console.WriteLine(Gergely.Név); Console.WriteLine(Gergely._Nem); Console.WriteLine(); Hallgató hallgatóGergely = new Hallgató("Strider Gábor", Nem.Férfi, "ÓE-NIK", 6); //Az örökölt adatmezők (tulajdonságok, változók) Console.WriteLine(hallgatóGergely.Név); Console.WriteLine(hallgatóGergely._Nem); Console.ReadLine(); } } enum Nem { Férfi, Nő } class Ember { //A protected kulcsszó segítségével a leszármazottak látni fogják az adatmezőt. protected string név; public string Név { get { return név; } } protected Nem _nem; public Nem _Nem { get { return _nem; } set { _nem = value; } } public Ember(string név, Nem _nem) { this._nem = _nem; this.név = név; Console.WriteLine("Az ember konstruktora fut!"); } } class Hallgató:Ember { string iskolaNeve; public string IskolaNeve { get { return iskolaNeve; } set { iskolaNeve = value; } } int félévekSzáma; public int FélévekSzáma { get { return félévekSzáma; } set { félévekSzáma = value; } } //:base(név,_nem) - így kell meghívni az ősosztály konstruktorát

Szántó Balázs ÓE-NIK-IAR, 2011

24

public Hallgató(string név, Nem _nem, string iskolaNeve, int félévekSzáma) :base(név,_nem) { this.iskolaNeve = iskolaNeve; this.félévekSzáma = félévekSzáma; Console.WriteLine("A hallgató konstruktora fut!"); } } }

3.4 Többalakúság

Az örökléssel kapcsolatos fejtegetéseinket közel sem fejezzük még be. Nem mutattuk, hogy öröklődnek a metódusok. Itt számos különbség figyelhető meg, mint az adatmezőknél. Először ismerkedjünk meg a többalakúság fogalmával. A különböző, egymásból származó objektumtípusok hasonló műveletei a konkrét objektumtól függően más-más konkrét megvalósítással rendelkezhetnek. Ugyan az a művelet némileg eltérő lehet az őstípus és a leszármazott típus között. Viszont mi szeretnénk ugyanazzal a névvel hivatkozni egy metódusra (attól függetlenül, hogy máshogy működnek) a leszármazási hierarchia egyes osztályainál.

Az örökölt mezők a leszármazottakban is elérhetőek, azonban ugyan abban a formában (a mezők jellemzői nem változnak meg az öröklés során). Tehát az adatmezők nem támogatják a többalakúságot.

Az öröklés szempontjából a metódusok lehetnek virtuális, vagy nem virtuális metódusok. A következő két alfejezetben bemutatjuk a koncepciókat és a C# nyelv által fenntartott lehetőségeket.

3.4.1 Nem virtuális metódusok

C# alapértelmezés szerint minden metódus nem virtuális metódus. Az ilyen metódusok a leszármazottaknál is elérhetőek (az elérést az adott metódus láthatósági szintje határozza meg) és korlátozottan módosíthatóak. A saját osztályának megfelelő típusú változón keresztül hívhatóak meg. Az előbbi mondatot kicsit kifejtenénk, Nézzünk meg egy példát.

Jármű osztálytól örökölt az Autó osztály, és azt a funkcionalitást egészítetheti ki. Ahol Jármű objektum szerepelt eddig, ott szerepelhet az Autó is mivel rendelkezik azokkal az adatmezőkkel és funkcionalitással, mint az őse. Ha egy Jármű-vel lehet utazni, akkor egy Autó-val is. Tehát ha létrehozunk egy ősosztályból referenciát, azzal mutathatunk a leszármazott osztályokra is, mint a példában.

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication7 { class Program { static void Main(string[] args) {

Szántó Balázs ÓE-NIK-IAR, 2011

25

Jármű hajó = new Jármű(); hajó.Megy(); //Kimenet: //A Jármű megy! Jármű kocsi = new Autó(); kocsi.Megy(); //Kimenet: //A Jármű megy! Autó kocsi2 = new Autó(); kocsi2.Megy(); //Kimenet //Az Autó megy! Console.ReadLine(); } } class Jármű { public void Megy() { Console.WriteLine("A Jármű megy!"); } } class Autó:Jármű { public new void Megy() { //base.Megy(); Console.WriteLine("Az Autó megy!"); } } }

Ha egy nem virtuális metódust használunk, és a leszármazott osztályban változtatnánk, akkor a new kulcsszó segítségével felüldefiniálhatjuk az őstől örökölt metódust. A nem virtuális metódusokra statikus kötés jellemző, ami a következőt jelenti: adott típusú változón hívják meg a metódust, akkor az adott típusnál megadott metódus fog lefutni. Ezt nézzük meg közelebbről.

Jármű hajó = new Jármű(); hajó.Megy(); //Kimenet: //A Jármű megy! Jármű kocsi = new Autó(); kocsi.Megy(); //A Jármű típuson keresztül hívjuk meg a metódust, annak ellenére, hogy //Autó típusnak deklaráltuk->A Jármű osztály Megy() metódusa fog lefutni //Kimenet: //A Jármű megy!

A statikus kötés előnye, hogy fordítási időben már eldől, hogy melyik metódust hívjuk meg. Ha az ős osztály metódusát is fel szeretnénk használni, akkor meghívhatjuk a base.Megy();

metódus/kulcsszó segítségével.

Adott típusú változónak megfelelően…

Tényleges objektum típus

Szántó Balázs ÓE-NIK-IAR, 2011

26

9. ábra - Egy újabb példa a nem virtuális metódusokra.

3.4.2 Virtuális metódusok

Virtuális metódusok használatakor az adott objektum tényleges típusának megfelelő metódus fog lefutni. Az ilyen metódusokat dinamikus kötés jellemzi, tehát futási időben dől el az adott Típustól, hogy melyik metódust hívják meg.

A virtuális metódusokat az ősosztályban jelölni kell, mégpedig a virtual kulcsszóval. A leszármazott osztályban pedig az override kulcsszó segítségével lehet felülírni az őstől örökölt metódust.

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication7 { class Program { static void Main(string[] args) { Jármű hajó = new Jármű(); hajó.Megy(); //Kimenet: //A Jármű megy! Jármű kocsi = new Autó(); kocsi.Megy();

Szántó Balázs ÓE-NIK-IAR, 2011

27

//Kimenet: //Az Autó megy! Autó kocsi2 = new Autó(); kocsi2.Megy(); //Kimenet //Az Autó megy! Console.ReadLine(); } } class Jármű { public virtual void Megy() { Console.WriteLine("A Jármű megy!"); } } class Autó:Jármű { public override void Megy() { Console.WriteLine("Az Autó megy!"); } } }

Hasonlítsuk össze a kimeneteket (lásd kommentek)! Jól látható a különbség.

3.4.3 Absztrakt metódusok, absztrakt osztályok

Ha szabadon fogalmazunk, akkor absztrakt osztálynak tekinthetjük a félig elkészített osztályokat. Az absztrakt osztályok tartalmaznak egy vagy több megvalósítatlan metódust, ezek az absztrakt metódusok. Az absztrakt metódus csak a metódus szignatúrát tartalmazza, a megvalósítást a származtatott osztályokra bízza (ilyenkor kötelező megvalósítani az absztrakt metódusokat). A használata hasonló, mint a virtuális metódusoknál látott működés annyi különbséggel, hogy itt a leszármazottnak kötelező megvalósítania az absztrakt metódust.

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication7 { class Program { static void Main(string[] args) { //A következő sor hibát dobna, abstract osztályt nem lehet //példányosítani //Jármű j = new Jármű(); Jármű kocsi = new Autó(); kocsi.Go(); kocsi.Megy();

Szántó Balázs ÓE-NIK-IAR, 2011

28

//VAGY Autó kocsi2 = new Autó(); kocsi2.Go(); kocsi2.Megy(); Console.ReadLine(); } } abstract class Jármű { public abstract void Megy(); public void Go() { Console.WriteLine("Go"); } } class Autó:Jármű { public override void Megy() { Console.WriteLine("Az Autó megy!"); } } }

3.4.4 Lezárt metódusok, lezárt osztályok

Léteznek olyan osztályok amelyekből nem lehet leszármaztatni, ezeket nevezzük lezárt osztályoknak. Jelölése:

sealed class Autó { }

Csakugyan a lezárt metódusok nem bírálhatóak felül. Csak a már felülírt metódusok lezárásának van értelme.

class Jármű { public virtual void Go() { } } sealed class Autó:Jármű { public sealed override void Go() { base.Go(); Console.WriteLine("Azért tettem én hozzá :)"); } }

Lezárt osztály és metódusok lehetséges használati okai:

Szántó Balázs ÓE-NIK-IAR, 2011

29

Előre nem látható felhasználás elkerülése

Teljesítményoptimalizálás

Szerzői jogok védelme

3.4.5 Típusok közötti konverzió, casting, boxing, un-boxing

A típus kasztolás azt jelenti, hogy egy objektum viselkedését befolyásoljuk, hogy úgy működjön, mint egy másik típus. A leszármazási hierarchián belül lehet kasztolni, mind lefelé mind felfelé. A felfelé kasztolás mindig egyszerű és implicit. A példán is látható, hogy a fordító impliciten tud kasztolni, mivel az Autó osztály őse a Jármű.

Jármű kocsi = new Autó();

A lefelé kasztolás nem biztonságos és explicit. Nézzük meg a következő példát. Létrehoztunk egy Alakzat típusú tömböt, ezt feltöltöttük különböző Alakzat osztály leszármazottjaival. A kódunk fordítási hibával fog lefutni, mert nem tudja milyen kasztolást kell alkalmazni, ezért ezt nekünk kell végrehajtani. Mégpedig:

Háromszög háromszög = (Háromszög)alakzatok[0];

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication7 { class Program { static void Main(string[] args) { Alakzat[] alakzatok = new Alakzat[] { new Háromszög(), new Kör(), new Négyzet() }; //Hibás //Háromszög háromszög = alakzatok[0]; Háromszög háromszög = (Háromszög)alakzatok[0]; } } class Alakzat { } class Háromszög:Alakzat { } class Kör:Alakzat { } class Négyzet:Alakzat

Szántó Balázs ÓE-NIK-IAR, 2011

30

{ } }

Amennyiben nem egy Négyzet objektumot kasztolnánk Kör-re, akkor futási idejű hibát kapnánk. Ezt kiküszöbölhetjük az as operátor segítségével. Amennyiben nem lehetséges a kasztolás a kifejezés null értékkel tér vissza.

Háromszög háromszög = alakzatok[0] as Háromszög;

Egy lehetőség a futás időbeli objektum típus lekérdezésére az is operátor. A kifejezés bal oldalán egy változó áll, a jobb oldalán pedig egy típus. A kifejezés true vagy false értékkel térhet vissza.

if (alakzatok[0] is Háromszög) { Háromszög háromszög = alakzatok[0] as Háromszög; }

A boxing és unboxing segítségével egy érték típust referencia típusként kezelhetünk. Lényegében egy érték típus egy obejktum referenciába csomagolnak bele, ez a boxing. Így az érték típusunk a felügyelt memória területen fog elhelyezkedni, ahol a többi értéktípus is. Az unboxing ennek a műveletnek az inverze.

int i = 123; object o = (object)i; // boxing

o = 123; i = (int)o; // unboxing

A boxing és unboxing számítási teljesítményt nézve költséges, mivel ilyenkor egy teljesen új objektumot létre kell hozni, és ráadásul még kasztolni is kell sok esetben.

4 Felhasznált irodalom

Faraz Rasheed. C# School, www.programmersheaven.com

K. Watson, C. Nagel, J. H. Pedersen, J. D. Reid, M. Skinner, E. White. Begining Microsoft Visual C# 2008, Wiley, www.wrox.com

Dr. Kotsis Domokos, Miklós Árpád. Objektum orientált programozás – órai diák

Miklós Árpád. Adatstruktúrák, algoritmusok, objektumok – órai diák