AZ OOP ALAPJAI
OOP vs struktúrálatlan programozás Az objektum-orientált programozás a klasszikus struktúrált programozásnál jóval hatékonyabb megoldást képes nyújtani a legtöbb problémára. Az osztályok újrafelhasználásának támogatásával nagymértékben tudja csökkenteni a szoftverek fejlesztéséhez szükséges időt. Az objektumorientált programozás abban különbözik leginkább a struktúrálatlan programozási módszerektől, hogy a programrészeket, hasonló feladatokat, de főleg hasonló feladatokon dolgozó adatokat és az azokat felhasználó metódusokat csokorba foglaljuk. Az objektum-orientált programozás jobban igyekszik közelíteni a világban lejátszódó valódi folyamatokhoz.
Az OOP alapfogalmai Osztály Objektum, példány, egyed Osztálydefiníció példányváltozók osztályváltozók metódusok konstruktor destruktor inicializáló blokk Hozzáférési jogok Egységbezárás Adatelrejtés
Osztály létrehozása Egy egyed egy osztály megvalósítása, más szóval példányosítása. Javaban ezt így érjük el: Object objektum = new Object() Ilyenkor lefut az osztály konstruktora, ahol meghatározhatjuk a kezdőértékeket, inicializálhatjuk az objektumot. Egy osztálynak lehetnek példányváltozói, illetve osztályváltozói. Előbbiből minden egyes példányosított egyedhez kerül egy-egy, míg osztályváltozóból az összes azonos osztályból példányosított egyedhez csak egy. Ugyanez érvényes az eljárásokra is. Java esetén egy osztály definíciója és deklarációja, azaz az eljárások feje és megvalósítása nem szétválasztható, mint például C++-ban.
Példa - Autó Valósítsunk meg egy Autó osztályt Javaban! package pelda002; // az osztályt tartalmazó csomag public class Auto { //az osztály kezdete // az osztály megvalósítása } //az osztály vége
- benzinár: int - tipus: String - km_ora: double - benzin: double Autó - benzinár: int - tipus: String - km_ora: double - benzin: double - ferohely: int +Auto(tipus: String, fogyasztas: double, ferohely: double) +Auto(tipus: String, fogyasztas: double, ferohely: double, km_ora: double) +költség(km: double): double +fuvar (km: double) +tankol (l: double) +toString(): String
Példa – Autó - változók Ahogy C++-ban, úgy a Javában is felsorolhatunk változókat, de a Java megengedi, hogy iniciáljuk is azokat. Változó deklarációja: [módosítók] típus változó [inicializáció] public class Auto { static private int benzinar=250; //statikus típus private String tipus; private double benzin; private double fogyasztas; private double km_ora=0; //példa kezdőértékre private int ferohely;
Példa – Autó - Konstruktor Minden osztály példányosításakor lefut egy konstruktor. Ennek neve – ahogy C++-ban is – megegyezik az osztály nevével, és nincs visszatérési értéke. Minden osztálynak létezik konstruktora, legfeljebb az üres, mégha nem is hozzuk létre, lefut. Konstruktorból lehet több is, túlterhelésükre ugyanaz vonatkozik, mint a függvények túlterhelésére.
Példa – Autó - Konstruktor public Auto(String tipus, double fogyasztas, int ferohely){ this.tipus=tipus; this.fogyasztas=fogyasztas; this.km_ora=0; this.benzin=0; this.ferohely=ferohely; } public Auto(String tipus, double fogyasztas, int ferohely, double km_ora){ this.km_ora=km_ora;
Példa – Autó - Metódusok Érdekesség, hogy a metódus neve megegyezhet egy változóéval is. Az osztály metódusait, ahogy láttuk a konstruktornál, szintén túl lehet terhelni. Metódus definíciója: [módosítók] típus Név([paraméterek]) {törzs} Példa metódusokra:
Példa – Autó - Metódusok Az alábbi függvények dokumentációja és magyarázata a külön dokumentumban található public void tankol(double l){ benzin+=l; } private double benzinigeny(double km){ return ((double)km/100)*fogyasztas; public double koltseg(double km){ return benzinigeny(km)*benzinar;
Példa – Autó - Metódusok public double halad(double km) { if(benzinigeny(km)<=benzin){ benzin-=benzinigeny(km); km_ora+=km; System.out.println("Megtettunk " + km + "km utat!"); return km; } else { System.out.println("Nincs benzin " +km+ "km uthoz!"); return 0;
Példa – Autó – Getter & Setter Objektumok leírásánál legtöbbször elrejtjük a belső változókat a külvilág elől, mert pl nem akarjuk, hogy változtassák, módosítottan akarjuk megjeleníteni vagy átadni, vagy egyszerűen nem akarjuk, hogy lássák. Ezért is lettek private változók. Ilyenkor használatosak a be- és kiviteli függvények, a getterek és setterek. Most az előbbire látunk példát: public double benzin_ora(){ return benzin; } public double km_ora() { return km_ora;
Példa – Autó – String toString() A Javában minden egyednek van egy függvénye, ami szöveges információt hordoz a példányról, ez a public String toString() függvény, amit ha nem hozunk létre, az objektumunk örököl az ősosztálytól (erről bővebben később). public String toString(){ return tipus + ", benzin: " + benzin_ora() + ", km: " + km_ora(); }
További osztály-elemek (említés szintjén, a példába ne foglaljuk őket): inicializációs blokk (vagy blokkok), ami még a konstruktor előtt lefut, és azokon a változókon dolgozhat, amiket már előtte definiáltunk (mindez a az osztály törzsében, metóduson kívül). String s0; { s0 = new String("es-null"); // inic. blokkon kívül ezt nem szabadna! } Javában a Garbage-collector elvégzi helyettünk a memória tisztítását, de néha szükség lehet valamiféle destruktor jellegű metódusra. Erre szolgál a Finalize, illetve a classFinalize metódus: protected void Finalize() throws Throwable {} static void classFinalize() throws Throwable {}
Módosítók összessége Osztály: public: Az osztály bárki számára látható final: Végleges, nem lehet tovább bővíteni. Az öröklődésnél lesz róla szó bővebben. abstract: Az öröklődésnél lesz róla szó, csak bővíteni lehet, példányosítani nem. (Üres): Üres gyakorlatilag úgy használható, mint a public, a csomagra vonatkozóan
Módosítók összessége Változó, illetve objektum: public: Az objektumot használó bármely kód számára közvetlenül hozzáférhető. protected: Private, de az alosztályok látják private: Csak azon objektum számára elérhetők, melyben meghatározták őket. final: Végleges, azaz konstans érték static: Osztályváltozó, egy osztályhoz csak egy tartozik
Módosítók összessége Metódus esetén: public: Az objektumot használó bármely kód számára közvetlenül hozzáférhető. protected: Közvetlenül nem, csak egy öröklés általi alosztályon keresztül érhető el. private: Csak azon objektum számára elérhetők, melyben meghatározták őket. static: Példányosítás nélkül használható, (pl println fv), csak statikus változókat használhat. final: Végleges, a leszármazott nem írhatja felül. (Öröklődésnél bővebben) abstract: Absztrakt osztálynál használható, kötelező felülírnia, azaz megvalósítania a leszármazott osztálynak.
Öröklődés Tegyük fel, hogy létre akarunk hozni: Autót (már megvan) Taxit Buszt Ez így egy kicsit sok, főleg hogy az adatok és metódusok nagy része ismétlődne. Megoldás: bővítsük ki az Autót a Taxi jellemzőivel! = specializáljunk!
Öröklődés a Javában A leszármazott osztály örökli a szülő minden tulajdonságát. A szülő private tagjaiból is van példánya, de nem férhet hozzájuk közvetlenül (erre van a protected). Valójában Jávában minden objektum származtatott, az ősosztály java.lang.Object kiterjesztettje. Jávában a kiterjesztést az extend szóval jelölhetjük. A leszármazott a szülőobjektum egyes tagjaira a super kulcsszóval hivatkozhat. Object metódusai: equals, getClass, toString, stb
Az Auto osztály bővítése Bővítsük ki az Autó osztályát, és rögtön írjuk bele az új változóinkat! public class Taxi extends Auto{ private double penztarca; private double kmdij; // függvénytest }
Példa – Taxi - konstruktorok A leszármazott nem örökli a szülő konstruktorát. Van lehetőségünk a leszármazott konstruktorának első sorában meghívni a szülő valamelyik konstruktorát a super kulcsszóval. Ha ezt nem tesszük meg, vagy ha nem is definiálunk konstruktort, akkor is végrehajtódik a szülő üres kostruktora (ha van ilyen), mégpedig minden gyermekbeli inicializációs blokk előtt.
Példa – Taxi - konstruktor public Taxi(String tipus, double fogyasztas, int ferohely, double kmdij) { super(tipus, fogyasztas, ferohely); this.kmdij=kmdij; this.penztarca = 0; }
Elfedés, felülírás Nyilván néhány változónak és metódusnak más szerepet szánunk az új, származtatott osztályban. Ilyenkor felülírhatjuk, elfedhetjük a szülő azonos szignatúrájú metódusait (egyébként egyszerű túlterhelés lenne). Taxi esetén másképp számoljuk a költséget, hiszen hozzájön még a fuvarozó kilométerdíja. Egyszerűen írjunk új eljárásokat azonos névvel, de az új funkcióval! Itt is hivatkozhatunk a szülő metódusára (ha az már egy új fv miatt el lenne fedve) a super kulcsszóval. A toString() függvényünket is úgy módosítsuk, hogy az már kiírja a fuvarozó pénztárcájának tartalmát is!
Példa – Taxi Írjuk meg a Taxi osztályt! A költség számításánál vegyük figyelembe a kilométerdíjat! A halad függvényt hagyjuk meg, mellé hozzunk létre egy fuvaroz függvényt, ahol adjuk hozzá a taxis pénztárcájához a kilométerdíjból beszedett összeget is. A toString() metódus írja ki egyrészt az Autó adatait, de tegye hozzá a taxis pénztárcájának tartalmát is.
public double koltseg(double km){ return super.koltseg(km)+kmdij*km; } public void tankol(int l){ benzin+=l; penztarca-=l*benzinar; public String toString(){ return super.toString()+", a fuvarozo penze: "+penztarca;
A halad(int km) függvényt ne írjuk felül, mert két fuvar közt egyszerű autóként halad a taxis: public double fuvar(double km){ if(halad(km)==km){ //a halad(int km)-t nem irtuk felul. penztarca+=koltseg(km); return km; } else return 0;
Költség/fő (ha többen taxizunk, olcsóbb) public double koltseg_per_fo(double km, int fo){ if(fo>ferohely+1){ System.out.println("Tobb utas, mint ferohely, ha rendor jon, nagyon draga lesz!"); return 0; } else return koltseg(km)/fo;
Kész a Taxi osztály De még nem működhet teljesen: Az Autó tagváltozóinak mindig private a módosítójuk, változtassuk meg protectedre, hogy tudjuk őket használni! Hozzunk létre egy futtató osztályt main()-nel! public class runAuto { public static void main(String[] args){ Auto lada = new Auto("Lada", 10, 5); Taxi daewoo = new Taxi("Daewoo", 7, 5, 200); lada.tankol(40); lada.halad(15); // Megtettunk 15.0km utat! System.out.println(lada.toString()); // Lada, benzin: 38.5, km: 15.0 daewoo.tankol(30); daewoo.halad(40); // Megtettunk 40.0km utat! System.out.println(daewoo.koltseg_per_fo(15, 4)); // 815.625 daewoo.fuvar(200); System.out.println(daewoo.toString()); // Daewoo, benzin: 13.2, km: 240.0, a fuvarozo penze: 36000.0
Ízelítő a következő óra anyagából Polimorfizmus: Auto tata = new Taxi("Tata (indiai automarka)", 9, 4, 250); // létrehoztunk egy autót, ami egyébként egy taxi. tata.tankol(18); // Taxival vettunk 18.0l benzint! // tata.fuvar(100); // helytelen! tata.halad(100); // Megtettunk 100.0km utat! System.out.println(tata.toString()); // Tata (indiai automarka), benzin: 9.0, km: 100.0, a fuvarozo penze: -4500.0
Véglegesítés Hozzuk létre a Busz osztályt a Taxi osztálybol! A buszon egy vonaljeggyel lehet utazni, szintén van kilométerdíj, amit a sofőr kap. A költség marad ugyanúgy, ahogy a taxinál volt, ellenben a fuvar esetében az üzemeltető cég pénztárcáját tekintsük, azaz a jegyárból vontjuk ki a buszvezető díját is! Az osztály legyen végleges, azaz már ne lehessen szülő (final) Az alábbi osztályokat valósítsuk meg: public Busz(String tipus, double fogyasztas, int ferohely, double kmdij) public double fuvar(double km) public double fuvar(double km, int fo) public double Haszon(double km, int fo) public double koltseg_per_fo(double km, int fo)
Busz megvalósítás public final class Busz extends Taxi { private int jegyar = 230; /** * Busz konstruktora * @param tipus A busz márkája * @param fogyasztas //A busz fogyasztása * @param ferohely // Férőhelyek száma * @param kmdij //A buszvezető bére kilométerenként */ public Busz(String tipus, double fogyasztas, int ferohely, double kmdij){ super(tipus,fogyasztas, ferohely, kmdij); }
Busz megvalósítás /** * A fuvar ára egy vonaljegy. Most csak egy embert viszunk * csak hogy felul tudjuk irni a Taxi fuggvenyet */ public double fuvar(double km){ if(halad(km)==km){ //a halad(int km)-t nem irtuk felul. penztarca+=jegyar – km_dij*km; return km; } else return 0;
Busz megvalósítás /** * Fuvar több főre. * @param km Megtett út * @param fo Utasok száma * @return Megtett kilométerek száma */ public double fuvar(double km, int fo){ if(halad(km)==km){ penztarca+=fo*jegyar - km*kmdij; return km; } else return 0;
Busz megvalósítása /** * A fuvarozó cég összköltsége. Tegyük fel, hogy * az utasok végig utazták az utat. * @param km A megtett kilométerek száma * @param fo Utasok száma * @return Költség forintban */ public double Haszon(double km, int fo){ return fo*jegyar - koltseg(km); }
/** * Akárhányan is utazunk akármennyit, csak egy vonaljegyet veszünk. */ public double koltseg_per_fo(double km, int fo){ return jegyar; }
Általánosítás A specializálással ellentétes fejlesztés az általánosítás. Tegyük fel, hogy létre akarok hozni egy kerékpár típust. Szintén lenne néhány közös jellemzője az autóval. Hozzunk létre egy jármű osztályt, de úgy, hogy azt ne lehessen példányosítani, azaz legyen abstract! Ilyenkor amelyik függvényt abstract jelzővel illetjük, azt az utódnak muszáj felülírnia. Ez esetben a halad(), illetve a költség() metódus lehet ilyen. Az autóból természetesen át kell tenni néhány változót, azokat, amik a kerékpárral, illetve általánosságban egy járművel egyeznek! Valósítsuk meg a járművet!
public abstract class Jarmu { protected String tipus; protected double km_ora; protected int ferohely; // Haladást megvalósító metódus, absztrakt public abstract double halad(double km); // Költséget kiszámoló metódus, absztrakt public abstract double koltseg(double km); // Egy megvalósított metódus, kiírja a nevét és a kmórát. public String toString(){ return this.tipus+ ", " + km_ora + " km"; }
Kerékpár megvalósítása Írjuk meg a kerékpár osztályát! A költség legyen 0! (ezért is szeretjük a kerékpárt) A haladást csak a kilométeróra növekedése kövesse! A toString() a típust és a kilométerórát írja ki!
public final class Kerekpar extends Jarmu { public Kerekpar( String tipus){ // Egy kerékpár this.tipus=tipus; this.km_ora=0; this.ferohely=1; } public double halad(double km) { // A halad fv. this.km_ora+=km; return 0; public double koltseg(double km) { // Adott utra adott koltseg.
Vége! (majdnem) A feladatok megoldásai megtekinthetők a digitus../stuat/progny/ mappában. Házi feladat: Hozzunk létre egy osztályhierarchiát, melyben ingatlanokat reprezentálunk. Legyen egy absztrakt Ingatlan osztály, ahol az ingatlan területe, címe és bérleti díja van, továbbá egy metódust, ami az éves költségeket számolja ki. Írjunk egy Lakás osztályt, ahol a lakók számát is tároljuk, ők a lakásért fizetnek adót, illetve rezsit. Írjunk egy Kollégium osztályt, mely a Lakásból öröklődik, számoljunk az állami támogatással és számoljuk ki az egy főre eső költségeket is. Végül valósítsunk meg egy Iroda osztályt is, ahol tároljuk a cég nevét, a költségek számításánál pedig figyelembe vesszük az adót is. Összes osztálynál legyen aktuális String toString osztály!