Bevezetés az objektumorientált programozásba
Az „OOP“ kifejezés az objektumorientált programozás rövidítése, amely a kód szervezésének és strukturálásának egy módja. Az OOP lehetővé teszi, hogy a programot egymással kommunikáló objektumok gyűjteményének tekintsük, nem pedig parancsok és függvények sorozatának.
Az OOP-ban az „objektum“ olyan egység, amely adatokat és az adatokkal operáló függvényeket tartalmaz. Az objektumok létrehozása „osztályok“ alapján történik, amelyek az objektumok tervrajzaként vagy sablonjaként értelmezhetők. Ha már van egy osztályunk, létrehozhatjuk annak „példányát“, amely egy konkrét, az osztályból készült objektum.
Nézzük meg, hogyan hozhatunk létre egy egyszerű osztályt PHP-ben. Egy osztály definiálásakor az „class“ kulcsszót használjuk, amelyet az osztály neve követ, majd a szögletes zárójelek az osztály függvényeit (az úgynevezett „metódusokat“) és az osztály változóit (az úgynevezett „tulajdonságokat“ vagy „attribútumokat“) zárják körül:
Ebben a példában egy Car
nevű osztályt hoztunk létre egy honk
nevű függvénnyel (vagy
„módszerrel“).
Minden osztály csak egy fő feladatot oldhat meg. Ha egy osztály túl sok mindent csinál, akkor célszerű lehet kisebb, speciális osztályokra osztani.
Az osztályokat általában külön fájlokban tároljuk, hogy a kódot rendszerezve tartsuk és könnyen áttekinthetővé
tegyük. A fájlnévnek meg kell egyeznie az osztály nevével, így a Car
osztály esetében a fájl neve
Car.php
lenne.
Az osztályok elnevezésénél célszerű a „PascalCase“ konvenciót követni, ami azt jelenti, hogy a névben minden szó nagybetűvel kezdődik, és nincsenek aláhúzások vagy egyéb elválasztójelek. A metódusok és tulajdonságok a „camelCase“ konvenciót követik, azaz kisbetűvel kezdődnek.
A PHP egyes metódusai speciális szerepkörrel rendelkeznek, és a __
(két aláhúzás) előtaggal vannak
ellátva. Az egyik legfontosabb speciális metódus a „konstruktor“, amelyet a __construct
jelöléssel látunk
el. A konstruktor egy olyan metódus, amely automatikusan meghívásra kerül, amikor egy osztály új példányát
létrehozzuk.
A konstruktort gyakran használjuk egy objektum kezdeti állapotának beállítására. Például egy személyt reprezentáló objektum létrehozásakor a konstruktort használhatjuk a kor, a név vagy más attribútumok beállítására.
Lássuk, hogyan használhatunk konstruktort a PHP-ban:
Ebben a példában a Person
osztály rendelkezik egy $age
tulajdonsággal (változóval) és egy
konstruktorral, amely beállítja ezt a tulajdonságot. A howOldAreYou()
metódus ezután hozzáférést biztosít a
személy életkorához.
A $this
álváltozót az osztályon belül az objektum tulajdonságainak és metódusainak elérésére
használjuk.
A new
kulcsszó egy osztály új példányának létrehozására szolgál. A fenti példában egy új, 25 éves
személyt hoztunk létre.
A konstruktor paramétereihez alapértelmezett értékeket is megadhatunk, ha azok nincsenek megadva egy objektum létrehozásakor. Például:
Ebben a példában, ha a Person
objektum létrehozásakor nem ad meg életkort, az alapértelmezett 20-as
értéket fogja használni.
A szép dolog az, hogy a tulajdonság definíciója a konstruktoron keresztül történő inicializálással lerövidíthető és leegyszerűsíthető így:
A teljesség kedvéért a konstruktorok mellett az objektumok rendelkezhetnek destruktorokkal ( __destruct
),
amelyeket az objektum memóriából való kikerülése előtt hívunk meg.
Névterek
A névterek lehetővé teszik, hogy az egymással összefüggő osztályokat, függvényeket és konstansokat rendszerezzük és csoportosítsuk, miközben elkerüljük a névkonfliktusokat. Úgy gondolhat rájuk, mint a számítógép mappáira, ahol minden mappa egy adott projekthez vagy témához kapcsolódó fájlokat tartalmaz.
A névterek különösen hasznosak nagyobb projekteknél vagy harmadik féltől származó könyvtárak használatakor, ahol osztályok elnevezési konfliktusok merülhetnek fel.
Képzelje el, hogy van egy Car
nevű osztálya a projektjében, és a Transport
nevű névtérben
szeretné elhelyezni. Ezt a következőképpen tenné:
Ha a Car
osztályt egy másik fájlban szeretné használni, akkor meg kell adnia, hogy az osztály melyik
névtérből származik:
Az egyszerűsítés érdekében a fájl elején megadhatja, hogy melyik osztályt szeretné használni egy adott névtérből, így a teljes elérési út megadása nélkül hozhat létre példányokat:
Öröklés
Az öröklés az objektumorientált programozás egyik eszköze, amely lehetővé teszi új osztályok létrehozását a már létező osztályok alapján, örökölve azok tulajdonságait és metódusait, és szükség szerint kibővítve vagy átdefiniálva azokat. Az öröklés biztosítja a kód újrafelhasználhatóságát és az osztályhierarchiát.
Egyszerűen fogalmazva, ha van egy osztályunk, és szeretnénk létrehozni egy másik, abból származtatott, de némi módosítással rendelkező osztályt, akkor az új osztályt „örökölhetjük“ az eredetiből.
A PHP-ben az öröklés a extends
kulcsszóval valósul meg.
A mi Person
osztályunk az életkori információkat tárolja. Létrehozhatunk egy másik osztályt, a
Student
, amely a Person
kiterjesztése, és hozzáadhatja a tanulmányi területre vonatkozó
információkat.
Nézzünk egy példát:
Hogyan működik ez a kód?
- A
extends
kulcsszóval bővítettük aPerson
osztályt, ami azt jelenti, hogy aStudent
osztály minden metódust és tulajdonságot aPerson
osztályból örököl. - A
parent::
kulcsszó lehetővé teszi számunkra, hogy a szülő osztály metódusait hívjuk. Ebben az esetben aPerson
osztály konstruktorát hívtuk meg, mielőtt aStudent
osztályhoz hozzáadtuk volna a saját funkcionalitásunkat. És hasonlóképpen aprintInformation()
felmenő osztály metódusát, mielőtt a tanulói adatokat listáznánk.
Az öröklés olyan helyzetekre való, amikor az osztályok között „is a“ kapcsolat van. Például egy
Student
egy Person
. A macska egy állat. Lehetővé teszi számunkra, hogy olyan esetekben, amikor egy
objektumot (pl. „Person“) várunk a kódban, egy származtatott objektumot használjunk helyette (pl. „Student“).
Lényeges felismerni, hogy az öröklés elsődleges célja nem a kódduplikáció megakadályozása. Éppen ellenkezőleg, az öröklés helytelen használata összetett és nehezen karbantartható kódhoz vezethet. Ha nincs „is a“ kapcsolat az osztályok között, akkor az öröklés helyett a kompozíciót kell fontolóra vennünk.
Vegyük észre, hogy a Person
és a Student
osztályok printInformation()
metódusai
kissé eltérő információkat adnak ki. És hozzáadhatunk más osztályokat is (például Employee
), amelyek ennek
a metódusnak más megvalósításait biztosítják. A különböző osztályok objektumainak azt a képességét, hogy ugyanarra
a metódusra különböző módon reagáljanak, polimorfizmusnak nevezzük:
Kompozíció
A kompozíció egy olyan technika, ahol ahelyett, hogy egy másik osztály tulajdonságait és metódusait örökölnénk, egyszerűen csak használjuk annak példányát a saját osztályunkban. Ez lehetővé teszi, hogy több osztály funkcionalitását és tulajdonságait kombináljuk anélkül, hogy bonyolult öröklési struktúrákat hoznánk létre.
Például van egy Engine
és egy Car
osztályunk. Ahelyett, hogy azt mondanánk, hogy „Egy autó
egy motor“, azt mondjuk, hogy „Egy autónak van egy motorja“, ami egy tipikus kompozíciós kapcsolat.
Itt a Car
nem rendelkezik a Engine
osztály összes tulajdonságával és metódusával, de a
$engine
tulajdonságon keresztül hozzáférhet azokhoz.
A kompozíció előnye a nagyobb tervezési rugalmasság és a jobb alkalmazkodóképesség a jövőbeli változásokhoz.
Láthatóság
A PHP-ben az osztályok tulajdonságai, metódusai és konstansai számára meghatározható a „láthatóság“. A láthatóság határozza meg, hogy hol lehet hozzáférni ezekhez az elemekhez.
- Public: Ha egy elemet
public
-ként jelölünk, az azt jelenti, hogy bárhonnan, akár az osztályon kívülről is elérhetjük. - Védett: Egy
protected
jelű elem csak az osztályon belül és annak összes leszármazottján (az osztályból öröklődő osztályok) belül érhető el. - Privát: Ha egy elem
private
, akkor csak azon az osztályon belül érhető el, ahol definiálták.
Ha nem adja meg a láthatóságot, a PHP automatikusan public
értékre állítja.
Nézzünk meg egy példakódot:
Folytatva az osztályöröklést:
Ebben az esetben a printProperties()
metódus a ChildClass
osztályban hozzáférhet a nyilvános és
védett tulajdonságokhoz, de nem férhet hozzá a szülőosztály privát tulajdonságaihoz.
Az adatoknak és a metódusoknak a lehető legrejtettebbnek kell lenniük, és csak egy meghatározott interfészen keresztül lehet őket elérni. Ez lehetővé teszi, hogy az osztály belső implementációját megváltoztassa anélkül, hogy a kód többi részét befolyásolná.
Végleges kulcsszó
A PHP-ben a final
kulcsszót használhatjuk, ha meg akarjuk akadályozni, hogy egy osztály, metódus vagy
konstans öröklődjön vagy felülíródjon. Ha egy osztályt final
jelöli, akkor nem bővíthető. Ha egy metódus
final
-ként van megjelölve, akkor nem lehet felülírni egy alosztályban.
Ha tudatában vagyunk annak, hogy egy bizonyos osztály vagy metódus nem lesz többé módosítható, könnyebben tudunk változtatásokat végrehajtani anélkül, hogy aggódnánk a lehetséges konfliktusok miatt. Például hozzáadhatunk egy új metódust anélkül, hogy attól kellene tartanunk, hogy egy leszármazottnak már van egy ugyanilyen nevű metódusa, ami ütközéshez vezethet. Vagy megváltoztathatjuk egy metódus paramétereit, szintén anélkül, hogy azt kockáztatnánk, hogy következetlenséget okozunk egy leszármazottban lévő felülhajtott metódussal.
Ebben a példában a FinalClass
végleges osztályból való öröklés megkísérlése hibát eredményez.
Statikus tulajdonságok és metódusok
Amikor egy osztály „statikus“ elemeiről beszélünk a PHP-ben, akkor olyan metódusokra és tulajdonságokra gondolunk, amelyek magához az osztályhoz tartoznak, nem pedig az osztály egy adott példányához. Ez azt jelenti, hogy nem kell létrehozni az osztály egy példányát ahhoz, hogy hozzáférjünk hozzájuk. Ehelyett közvetlenül az osztály nevén keresztül hívja meg vagy éri el őket.
Ne feledje, hogy mivel a statikus elemek az osztályhoz tartoznak, nem pedig annak példányaihoz, nem használhatja a
$this
pszeudováltozót statikus metódusokon belül.
A statikus tulajdonságok használata buktatókkal teli homályos kódhoz vezet, ezért soha ne használd őket, és itt nem is mutatunk példát. A statikus metódusok viszont hasznosak. Íme egy példa:
Ebben a példában létrehoztunk egy Calculator
osztályt két statikus metódussal. Ezeket a metódusokat
közvetlenül meg tudjuk hívni anélkül, hogy az osztály példányát létrehoznánk a ::
operátor
segítségével. A statikus metódusok különösen hasznosak olyan műveleteknél, amelyek nem függnek egy adott
osztálypéldány állapotától.
Osztálykonstansok
Az osztályokon belül lehetőségünk van konstansok definiálására. A konstansok olyan értékek, amelyek a program végrehajtása során soha nem változnak. A változókkal ellentétben a konstansok értéke változatlan marad.
Ebben a példában van egy Car
osztályunk a NumberOfWheels
konstanssal. Az osztályon belüli
konstans elérésekor az osztály neve helyett a self
kulcsszót használhatjuk.
Objektum interfészek
Az objektum interfészek az osztályok „szerződéseiként“ működnek. Ha egy osztálynak egy objektum interfészt kell implementálnia, akkor tartalmaznia kell az összes metódust, amelyet az interfész definiál. Ez egy nagyszerű módja annak, hogy bizonyos osztályok azonos „szerződést“ vagy struktúrát tartsanak be.
A PHP-ben az interfészeket a interface
kulcsszóval definiáljuk. Az interfészben definiált összes metódus
nyilvános (public
). Amikor egy osztály megvalósít egy interfészt, akkor a implements
kulcsszót
használja.
Ha egy osztály megvalósít egy interfészt, de nem minden elvárt metódus van definiálva, a PHP hibát fog dobni.
Egy osztály egyszerre több interfészt is megvalósíthat, ami eltér az örökléstől, ahol egy osztály csak egy osztálytól örökölhet:
Absztrakt osztályok
Az absztrakt osztályok más osztályok alapsablonjaiként szolgálnak, de közvetlenül nem hozhatók létre példányaik. Teljes metódusok és olyan absztrakt metódusok keverékét tartalmazzák, amelyeknek nincs meghatározott tartalma. Az absztrakt osztályokból öröklődő osztályoknak meg kell adniuk a szülőktől származó összes absztrakt metódus definícióját.
Az absztrakt osztályok definiálására a abstract
kulcsszót használjuk.
Ebben a példában egy absztrakt osztályunk van egy reguláris és egy absztrakt metódussal. Ezután van egy
Child
osztályunk, amely a AbstractClass
osztályból örököl, és implementációt biztosít az
absztrakt metódushoz.
Miben különböznek az interfészek és az absztrakt osztályok? Az absztrakt osztályok absztrakt és konkrét metódusokat is tartalmazhatnak, míg az interfészek csak azt határozzák meg, hogy az osztálynak milyen metódusokat kell implementálnia, de implementációt nem biztosítanak. Egy osztály csak egy absztrakt osztálytól örökölhet, de tetszőleges számú interfészt implementálhat.
Típusellenőrzés
A programozásban alapvető fontosságú annak biztosítása, hogy az adatok, amelyekkel dolgozunk, megfelelő típusúak legyenek. A PHP-ben vannak olyan eszközeink, amelyek ezt a biztosítékot nyújtják. Az adatok megfelelő típusának ellenőrzését „típusellenőrzésnek“ nevezzük.
Típusok, amelyekkel a PHP-ben találkozhatunk:
- Bázis típusok: Ezek közé tartozik a
int
(egész számok),float
(lebegőpontos számok),bool
(boolék),string
(karakterláncok),array
(tömbök) ésnull
. - osztályok: Amikor azt akarjuk, hogy egy érték egy adott osztály példánya legyen.
- Interfészek: Meghatározza azon metódusok halmazát, amelyeket egy osztálynak implementálnia kell. Egy interfésznek megfelelő értéknek rendelkeznie kell ezekkel a metódusokkal.
- Keverék típusok: Megadhatjuk, hogy egy változónak több megengedett típusa is lehet.
- Void: Ez a speciális típus azt jelzi, hogy egy függvény vagy metódus nem ad vissza értéket.
Lássuk, hogyan módosíthatjuk a kódot a típusok felvételére:
Így biztosítjuk, hogy a kódunk a megfelelő típusú adatokat várja el és a megfelelő típusú adatokkal dolgozik, ami segít megelőzni a lehetséges hibákat.
Néhány típus nem írható közvetlenül PHP-ben. Ebben az esetben a phpDoc kommentben szerepelnek, ami a PHP kód
dokumentálásának szabványos formátuma, a /**
kezdődik és a */
végződik. Lehetővé teszi, hogy
osztályok, metódusok stb. leírását adjuk hozzá. Valamint az összetett típusok felsorolását az úgynevezett annotációk
segítségével @var
, @param
és @return
. Ezeket a típusokat aztán a statikus kódelemző
eszközök használják, de maga a PHP nem ellenőrzi őket.
Összehasonlítás és azonosság
A PHP-ben kétféleképpen lehet objektumokat összehasonlítani:
- Érték-összehasonlítás
==
: Ellenőrzi, hogy az objektumok ugyanabba az osztályba tartoznak-e, és tulajdonságaikban ugyanazok az értékek szerepelnek-e. - Azonosság
===
: Ellenőrzi, hogy az objektum azonos példányáról van-e szó.
A instanceof
Üzemeltető
A instanceof
operátor lehetővé teszi annak meghatározását, hogy egy adott objektum egy adott osztály
példánya, leszármazottja, vagy egy bizonyos interfész megvalósítója-e.
Képzeljük el, hogy van egy Person
osztályunk és egy másik osztályunk, a Student
, amely a
Person
leszármazottja:
A kimenetekből látható, hogy a $student
objektumot a Student
és a Person
osztályok
példányának tekintjük.
Folyékony interfészek
A „folyékony interfész“ egy olyan technika az OOP-ban, amely lehetővé teszi a metódusok egyetlen hívással történő láncolását. Ez gyakran egyszerűsíti és egyértelművé teszi a kódot.
A folyékony interfész kulcseleme, hogy a lánc minden egyes metódusa az aktuális objektumra való hivatkozást adja
vissza. Ezt a return $this;
használatával érjük el a metódus végén. Ez a programozási stílus gyakran társul
a „setters“ nevű metódusokkal, amelyek az objektum tulajdonságainak értékeit állítják be.
Nézzük meg, hogyan nézhet ki egy folyékony interfész az e-mailek küldéséhez:
Ebben a példában a setFrom()
, setRecipient()
és setMessage()
metódusok a megfelelő
értékek (feladó, címzett, üzenet tartalma) beállítására szolgálnak. Az egyes értékek beállítása után a metódusok
visszaadják az aktuális objektumot ($email
), ami lehetővé teszi számunkra, hogy egy másik metódust láncoljunk
utána. Végül meghívjuk a send()
metódust, amely ténylegesen elküldi az e-mailt.
A folyékony interfészeknek köszönhetően olyan kódot írhatunk, amely intuitív és könnyen olvasható.
Másolás a clone
címen
A PHP-ben a clone
operátor segítségével létrehozhatjuk egy objektum másolatát. Így egy új, azonos
tartalmú példányt kapunk.
Ha egy objektum másolásakor szükségünk van néhány tulajdonságának módosítására, akkor az osztályban
definiálhatunk egy speciális __clone()
metódust. Ez a metódus automatikusan meghívódik, amikor az objektumot
klónozzuk.
Ebben a példában van egy Sheep
osztályunk, amelynek egy tulajdonsága a $name
. Amikor klónozzuk
ennek az osztálynak egy példányát, a __clone()
metódus biztosítja, hogy a klónozott bárány neve a „Clone
of“ előtagot kapja.
A tulajdonságok.
A PHP-ban a Traits egy olyan eszköz, amely lehetővé teszi a metódusok, tulajdonságok és konstansok megosztását az osztályok között, és megakadályozza a kód duplikálását. Úgy gondolhatsz rájuk, mint egy „másolás és beillesztés“ mechanizmusra (Ctrl-C és Ctrl-V), ahol egy tulajdonság tartalma „beillesztésre“ kerül az osztályokba. Ez lehetővé teszi a kód újrafelhasználását anélkül, hogy bonyolult osztályhierarchiákat kellene létrehozni.
Nézzünk meg egy egyszerű példát a vonások PHP-ban való használatára:
Ebben a példában van egy Honking
nevű tulajdonságunk, amely egy metódust tartalmaz: honk()
.
Ezután van két osztályunk: Car
és Truck
, amelyek mindkettő a Honking
tulajdonságot
használja. Ennek eredményeképpen mindkét osztály „rendelkezik“ a honk()
metódussal, és mindkét osztály
objektumain meg tudjuk hívni azt.
A tulajdonságok lehetővé teszik az osztályok közötti egyszerű és hatékony kódmegosztást. Nem lépnek be az
öröklési hierarchiába, azaz a $car instanceof Honking
a false
-t fogja visszaadni.
Kivételek
A kivételek az OOP-ban lehetővé teszik számunkra a hibák és váratlan helyzetek méltóságteljes kezelését a kódunkban. Ezek olyan objektumok, amelyek információt hordoznak egy hibáról vagy szokatlan helyzetről.
A PHP-ben van egy beépített osztályunk, a Exception
, amely az összes kivétel alapjául szolgál. Ez több
olyan metódussal rendelkezik, amelyek lehetővé teszik számunkra, hogy több információt kapjunk a kivételről, például a
hibaüzenetet, a fájlt és a sort, ahol a hiba történt, stb.
Ha hiba lép fel a kódban, a throw
kulcsszóval „dobhatjuk“ a kivételt.
Ha a division()
függvény második argumentumként null értéket kap, akkor a 'Division by zero!'
hibaüzenetű kivételt dob. Annak érdekében, hogy a program ne essen össze a kivétel dobásakor, a try/catch
blokkban csapdába ejtjük azt:
Az olyan kódot, amely kivételt dobhat, egy blokkba csomagoljuk try
. Ha a kivételt dobjuk, a kód végrehajtása
átkerül a catch
blokkba, ahol a kivételt kezelhetjük (pl. hibaüzenetet írhatunk).
A try
és a catch
blokkok után hozzáadhatunk egy opcionális finally
blokkot, amely
mindig végrehajtásra kerül, függetlenül attól, hogy a kivételt dobta-e vagy sem (még akkor is, ha a return
,
break
vagy continue
blokkot használjuk a try
vagy catch
blokkban):
Saját kivételosztályokat (hierarchiát) is létrehozhatunk, amelyek az Exception osztályból öröklődnek. Példaként tekintsünk egy egyszerű banki alkalmazást, amely lehetővé teszi a befizetéseket és a kifizetéseket:
Egyetlen try
blokkhoz több catch
blokk is megadható, ha különböző típusú kivételekre
számít.
Ebben a példában fontos megjegyezni a catch
blokkok sorrendjét. Mivel minden kivétel a
BankingException
-tól öröklődik, ha ez a blokk lenne az első, akkor minden kivételt elkapnánk benne anélkül,
hogy a kód elérné a következő catch
blokkokat. Ezért fontos, hogy a specifikusabb (azaz másoktól
öröklődő) kivételek a catch
blokkok sorrendjében előrébb legyenek, mint a szülő kivételek.
Iterációk
A PHP-ben a foreach
ciklus segítségével végighaladhat az objektumokon, hasonlóan a tömbökön való
végighaladáshoz. Ahhoz, hogy ez működjön, az objektumnak egy speciális interfészt kell megvalósítania.
Az első lehetőség a Iterator
interfész implementálása, amely a current()
metódusokkal
rendelkezik, amelyek visszaadják az aktuális értéket, a key()
metódus visszaadja a kulcsot, a
next()
metódus a következő értékre lép, a rewind()
metódus az elejére lép, és a
valid()
metódus ellenőrzi, hogy a végén vagyunk-e már.
A másik lehetőség a IteratorAggregate
interfész implementálása, amelynek csak egy metódusa van:
getIterator()
. Ez vagy egy helyőrző objektumot ad vissza, amely a traverzálást biztosítja, vagy lehet egy
generátor, ami egy speciális függvény, amely a yield
segítségével adja vissza a kulcsokat és az értékeket
egymás után:
Legjobb gyakorlatok
Ha már az objektumorientált programozás alapelveivel megismerkedtél, elengedhetetlen, hogy az OOP legjobb gyakorlataira koncentrálj. Ezek segítenek olyan kódot írni, amely nemcsak funkcionális, hanem olvasható, érthető és könnyen karbantartható is.
- Separation of Concerns: Minden osztálynak egyértelműen meghatározott felelősségi körrel kell rendelkeznie, és csak egy elsődleges feladattal kell foglalkoznia. Ha egy osztály túl sok mindent csinál, célszerű lehet kisebb, specializált osztályokra bontani.
- Kapszulázás: Az adatoknak és a metódusoknak a lehető legrejtettebbnek kell lenniük, és csak egy meghatározott interfészen keresztül lehet hozzájuk hozzáférni. Ez lehetővé teszi, hogy egy osztály belső megvalósítását megváltoztassuk anélkül, hogy a kód többi részét befolyásolná.
- Függőségi injektálás: Ahelyett, hogy közvetlenül egy osztályon belül hoznánk létre függőségeket, inkább kívülről „injektáljuk“ azokat. Ennek az elvnek a mélyebb megértéséhez ajánljuk a Dependency Injection fejezeteket.