9.18. Objektumok, osztályok - Objektum Orientált Programozás

Objektum Orientált Programozás - OOP

Először az igényekről. Amikor elkezd valaki programozást tanulni, használja az egyszerű utasításokat és egyszerű vezérlési szerkezeteket, majd egyre bonyolultabb alkalmazásokat kezd el írni. Van, aki megmarad az egyszerű szinten. Neki soha, vagy csak nagyon ritkán lesz szüksége az OOP használatára. Abban az esetben, amikor adatbázis is kerül az alkalmazásba, sok feltételnek megfelelő összetett programot írunk, előbb-utóbb nem elkerülhető az OOP használata.

Egyszerű adatszerkezetek

Az alábbiakban egy kis programozáselmélet következik. Az egyszerű adatok, mint például az integer, real, karakter logikai vagy hasonló adattípusok felhasználása úgy történik, hogy egy változó létrehozásakor megmondjuk a változó nevét és típusát, esetleg a kezdőértékét. Az így generált változók egymástól függetlenül jönnek létre, és kezelésük is némileg független egymástól. A tipikus programszerkezet az ilyen változók feldolgozására a szekvencia, azaz utasítások egymásutánja.

Az ilyen adatszerkezetet egyszerű adatszerkezetnek hívjuk.

Összetett adatszerkezetek

A programozás során szükséges, hogy több adatot összekössünk egymással és azokat együtt kezeljük. A hagyományos programozásban két lehetőségünk van erre.

Tömb

Ha azonos típusú és méretű adatokat akarunk együtt kezelni, akkor tömböket hozunk létre. A tömb tehát olyan adatszerkezet, amelyben az adatok egyforma méretűek. A tömböt alkotó adatok a tömb elemei. A tömb elemeinek az eléréséhez pedig indexeket használunk. Az index a programozási nyelvtől függően pozitív egész szám, amely általában nullától (C, C++, C++, PHP, Javascript, JAVA) kezdőindextől kezdődik.

Asszociatív tömbnek hívjuk azt a tömböt, amelyben a tömb indexeket nem egész számmal, hanem akár stringekkel, de mindenképpen megszámlálható számosságú (enumeratív) azonosítjuk. A tömbök feldolgozásához általában ciklus vezérlési szerkezetet használunk, ugyanis a tömb elemeit általában egymás után dolgozzuk fel sorban, és a tömb elemeken általában ugyanazt vagy hasonló, esetleg feltételektől függő tevékenységet végzünk el.

Megjegyzés:

Az asszociatív tömb a PHP-ban megegyezik a más nyelvekben (pl. C#) meglévő listákkal. ebben az esetben a tömb elemei nem feltétlenül vannak egymás után a memóriában, hanem egy mutató mutat a következő listaelemre.

Két dimenziós tömbnek hívjuk, ha két index-szel hivatkozunk a tömb elemeire. Ha n db indexxel hivatkozhatunk egy elemre, akkor n dimenziós tömbről beszélünk.

Az n dimenziós tömbök feldolgozása általában n db egymásba ágyazott ciklussal zajlik. Tipikusan a for vagy while ciklust használhatjuk ilyenkor.

Assziociatív tömbre sok nyelvnek van jó megoldása. PHP-ban a foreach() ciklus, amivel végig lehet menni egy asszociatív tömb elemein.

Rekordok

Ha nem azonos típusú adatokból akarunk összetett adatszerkezetet létrehozni, akkor az ilyen adatszerkezetet rekordnak (Pascalban), struktúrának (C-ben) hívjuk, illetve PHP-ban minden tömb elem lehet különböző típusú, tehát ebben a nyelvben a tömb és a rekord szinte azonos. Közös névként a rekord szót használjuk. A rekord adattípus feldolgozása szelekciót igényel, azaz a különböző típusú adatokat különböző kódok, programrészek dolgozzák fel. Erre a feltételes elágazás ( if…else…) vagy a többirányú elágazás (switch) alkalmas.

Sturktúrált programozás

A nem túlságosan összetett feladatok megoldása során használható az a módszer, hogy a programban az adatszerkezeteket definiáljuk és az adatszerkezetek alapján hozzuk létre a programszerkezetet. Ez egy eljáráshívási rendszert hoz létre, amelyben az egyes eljárásoknak, függvényeknek átadjuk a paramétereket, majd a visszakapott eredményeket más eljárásokkal, függvényekkel tovább feldolgozzuk.

A strukturált programozás akkor kezd nehézkessé válni, amikor a programokkal különböző adattípusokkal kell ugyanazt a fajta műveletet elvégezni, mint például a grafikus felhasználói interface létrehozása. A grafikus elemek különböznek egymástól, ugyanakkor a feladatok hasonlóak. Ilyen esetben célszerű lenne olyan programot írni, amely „tudja”, hogy milyen paramétereket adunk át neki, és ennek alapján ki tudja választani a megfelelő kezelő alkalmazást. Az is célszerű, hogy amennyire lehet újrafelhasználható kódot írjunk. Célszerű az is, hogy az egyes kódrészek csak szabványos paraméterlistán és függvényeken keresztül tartsanak egymással kapcsolatot (ezt már a strukturált programozás is tudja).

Új gondolat az, hogy legyünk képesek nagy bonyolultságú adatstruktúrák egyben történő kezelésére, mint például egy tömb vagy egy rekord egyszerre történő kezelése, ugyanakkor tetszőleges bonyolultságú adatokat tudjunk ily módon kezelni.

A fenti problémákra a megoldás a következő.

Létesítsünk olyan adatszerkezetet, amelyben az adatszerkezetben tárolt adatokon kívül az őt kezelő kódot is tároljuk. Így az adat és a kód elválaszthatatlanul összetartozik. (Strukturált programozásnál lehetséges az, hogy egy függvénynek olyan paramétert adunk át, amelyet nem tud feldolgozni, ekkor fatális hiba vagy fordítási hiba keletkezik). Ha az összetartozást leírjuk, akkor a kód saját maga „tudja”, hogy melyik adatokon kell dolgoznia. Az így definiált adattípust osztálynak hívjuk (Class).

Class (Osztály), Objektum (object)

Ez tehát egy adattípus lesz, ami a korábban leírt adattípusokhoz, adatszerkezetekhez hasonlóan akkor használható, ha létrehozunk egy változót, amelynek a típusa ez az adattípus lesz. Ezt hívják példányosításnak. Ebben az esetben a létrejött adatszerkezetet objektumnak hívjuk.

Néha az osztályokat nem kell példányosítani. A PHP-ban a beépített osztályok ilyenek gyakran.

Az osztályban vagy objektumban található adatokat az osztály tulajdonságainak (property) hívjuk. Az osztályban vagy objektumban található függvényeket és eljárásokat az osztály metódusainak hívjuk.

Megjegyzés

  • Amikor egy programot írunk és lefordítjuk, akkor nyilvánvalóan az osztályban található eljárásoknak memóriát kell foglalni. Ezt a fordító vagy a futtató rendszer elvégzi helyettünk. Amikor a kódunk létrehoz egy objektumot, akkor létre kell hozni a memóriában az osztály tulajdonságainak megfelelő memóriaterületet, továbbá az így lefoglalt memóriaterületet össze kell kötni az osztály kódjával is. Egyszerű megoldás lenne, ha minden objektum egy az egyben lemásolódna az eredeti osztályról, mint egy sablonról, de ebben az esetben nagyon sok memóriára lenne szükségünk, ezért a programok ilyenkor a kódot csak egy példányban tárolják és az objektum csak hivatkozásokat tárol az eredeti osztályban definiált eljárásokra.

  • A memóriafoglalás ugyanakkor meglehetősen bonyolulttá is válhat, ugyanis szükség lehet a példányosítás során kezdőértékek adására is, ezt pedig nem lehet mindenre általánosan megfogalmazni. Az osztály példányosítása tehát összetett folyamat.

Konstruktor

Az osztályok mindig tartalmaznak egy (vagy több) olyan metódust, amely az osztály példányosítása vagy másképpen az objektum létrehozásakor lefut. Ezt hívják konstruktornak. A konstruktor feladata lefoglalni a megfelelő memóriaterületet az objektum részére, illetve minden olyan beállítást megvalósítani, amely az objektum létrehozása és futása során szükséges lehet. Ha egy osztályban nem definiálunk konstruktort, akkor a rendszer létrehoz egy default konstruktort.

Destruktor

Ha egy objektumot megszüntetünk, akkor illik felszabadítani a memóriaterületét, és lezárni, illetve elengedni a menet közben lefoglalt erőforrásokat. Azt a metódust, amely ezt megteszi destruktornak hívják. Minden objektumnak van default destruktora. Ezt __destruct() néven érjük el. A destruktor nem hívja meg automatikusan a szülő osztály (erről később írok) destruktorát, ezért ha annak a memóriáját is fel akarjuk szabadítani azt külön meg kell hívni: parent::__destruct() néven.

A destruktor feladata tehát memóriafelszabadítás és különböző erőforrások lezárása, mint például filekezelő memóriaterület.

A destruktoroknak nincsen nagy jelentősége a webes alkalmazásokban, hiszen miután lefut egy PHP alkalmazás utána a rendszer automatikusan felszabadítja a használt memóriaterületet. Csak akkor érdemes ezzel foglalkozni, ha hosszan tartó folyamatokat futtatunk PHP-ban és sok objektumot hozunk létre.

A PHP-ban az osztályok definíciója az alábbiakban történik.

Class osztalynev {
   public  $tul1;
   private $tul2;

   Function osztalynev(parameterlista){          //Saját konstruktor
     ..... 
   }
   Function Method1(){

   }
   Function Method2(){
      $a = $this->tul2;
      Print($a); 
   }
}
Class osztalynev {
   public  $tul1;
   private $tul2;

   Function __construct( paraméterlista){          //Saját konstruktor
     ..... 
   }
   Function Method1(){

   }
   Function Method2(){
      $a = $this->tul2;
      Print($a); 
   }
}

Az osztály példányosítása így zajlik.

$a = new osztalynev();

Tulajdonságok és metódusok láthatósága

Az OOP szabályai szerint vannak publikus (public) tulajdonságok és metódusok. Ezeket a metódusokat külső kódból meg lehet hívni, el lehet érni, és vannak privát (private) metódusok és tulajdonságok, amelyeket csak az osztályból magából lehet elérni. A PHP-ban minden metódus és property alapértelmezetten publikus, ha másképpen nem rendelkezünk. A harmadik típus a védett (protected), amely csak abban az osztályban hívható meg, amelyben deklarálták, a leszármazott osztályokban sem!

A tulajdonságok létrehozhatók a PHP 7.1.0 óta olyan módon is, hogy megadjuk a változó típusát és a láthatóságát:

class valami {
    public int $a;
    function metodus(){
    
    }
}

Az osztályon kívülről az alábbi módon tudjunk értéket adni egy osztály egy tulajdonságának:

$a->tul2 = ”helló”;

Ha az objektumon belülről akarunk egy metódust vagy tulajdonságot elérni, akkor a $this előtagot kell használni, mivel az objektum nem tudja, hogy saját magának mi az éppen aktuális neve. :

$this->tul1 =  $this->tul2 . " tetszik?";

Minden osztály ugyanis saját magáról azért tud, csak a saját nevét nem tudja.

PHP 8.0-tól

A konstruktor paraméterei lehetnek egyúttal tulajdonságok is, amikor felsoroljuk a konstruktor paraméterlistájában. Ebben a példában létrejön két protected property a konstruktor interface-eben.

class valami {
    public function __construct( protected int $x = 0, protected int $y =0){
    }

    function metodus(){
    
    }
}

Az öröklődés / leszármazás, szülőosztály, gyerekosztály

Ha definiáltunk egy olyan osztályt, amely csak bizonyos tulajdonságaiban bővebb egy korábban definiáltnál, akkor a korábbi definíciót kiterjeszthetjük és a régi osztályra alapozva egy új osztályt hozhatunk létre. Ezt egy osztály kiterjesztésének hívjuk. Ilyenkor a kiterjesztett osztály örökli az ős-osztály tulajdonságait és metódusait. Az ős-osztályt szülő osztálynak hívjuk, a kiterjesztettet gyerek osztálynak.

Class Osztalykit extends Osztalynev{
   public $tul3;
   Function Osztalykit(){
       $Parent::osztalynev();
   }

   Function MehodKit(){
       $this->Method2()
       $this->tul3 = $this->tul2;
       Print($this->tul3);
   }
}

$b = new Osztalykit();

Ha az osztályban hivatkozunk a szülő osztály valamilyen tulajdonságára vagy metódusára, akkor a $parent:: kulcsszót kell használnunk.

A fenti példában a korábban definiált osztalynev() class-t kiterjesztettük. A konstruktorban meghívtuk a szülő osztály konstruktorát $parent:: osztalynev().

A gyerek osztály eléri a szülő osztály metódusait és tulajdonságait is. A szülő osztály azonban nem éri el a gyerek osztályét.

A PHP-ban egy gyerek osztálynak csak egy szülője lehet. Ez más nyelveken nem feltétlenül van így.

Az egyes osztályokban lehetnek ugyanolyan nevű metódusok, sőt a szülő-gyermek kapcsolatban lévő osztálystruktúrákban is lehet azonos nevű metódus vagy változó. Az OOP esetén ha egy osztályban meghívunk egy metódust vagy egy tulajdonságra hivatkozunk, a rendszer tudni fogja, hogy az esetlegesen azon

Absztrakt osztályok

Az absztrakt osztályok nem példányosíthatók. Absztrakt osztálynak kell fdefiniálni egy osztályt, ha van legalább egy absztrakt metódusa. A definíció nem fejti ki a metódust, hanem csak megnevezi és megadja a paramétereit.

abstract class AbsztraktOsztaly{

	abstract protected function getValue();
	public function printOut(){
		print $this->getValue();
	}
}

class KonkretOsztaly1 extends AbsztraktOsztaly{
	protected function getValue(){
		return "KonkretOsztaly";
	}
}

class KonkretOsztaly2 extends AbsztraktOsztaly{
	protected function getValue(){
		return "KonkretOsztaly2";
	}
}

$osztaly1 = new KonkretOsztaly1;
$osztaly1->printOut();

$osztaly2 = new KonkretOsztaly2;
$osztaly2->printOut();

Interface-ek

Az interface-ek olyan kódok, amelyek megmondják, hogy milyen metódusokat kell implementálni egy osztályban, ha az interface szerint kell működniük. Az interface nem mondja meg, hogy a metódusoknak hogyan kell működnie és a tulajdonságokat sem írja le. Az alábbi példában két ISablonA és ISablonB ként interface, amelyek alapján létrehozzuk az Valodi nevű osztályt. A két sablon metódusainak csak az interface-i vannak definiálva, amelyet a Valodi osztályban ki is fejtettünk. A mintából az is látható, hogy egy osztály több interface-t is meg tud valósítani.

interface ISablonA{
	public function getHtml($template);
}

interface ISablonB{
	public function setVariable($name, $var);
}

class Valodi implements ISablonA, ISablonB{
	private $vars = array();

	public function setVariable($name, $var){
		$this->vars[$name] = $var;
	}

	public function getHtml($template){
		foreach($this->vars as $name => $value){
			$template = str_replace('{'.$name.'}', $value, $template);
		}
		return $template;
	}
}

A PHP 5-től vannak beépített interface-ek a nyelvben, amelyeket itt nem tárgyalunk.

AutoLoading

Az objektumokat alapesetben úgy használjuk, hogy az objektumot definiáló osztályt külön fájlban írunk meg. A fájl neve leggyakrabban azonos az osztály nevével (kis- és nagybetűt tekintve is).

include  "path-to-file/pOsztaly.php";
include  "path-to-file/qOsztaly.php";

$p = new pOsztaly();
$q = new qOsztaly();

Ha túl sok osztályt használunk, illetve túl sok helyen használjuk az osztályokat, akkor a sok include utasítás kavarodásokat okozhat. És ugyanazt az osztályt többször include-oljuk, illetve ki is maradhatnak...

Erre a problémára ad megoldást az autoloading lehetőség.

function __autoload($class_name) {
	include $class_name . '.php';
}

$p  = new pOsztaly();
$q = new qOsztaly();

Az __autoload() mágikus függvény, amellyel automatizálhatjuk az osztálydefiníciók betöltődését.