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:
class Car
{
function honk()
{
echo 'Beep beep!';
}
}
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:
class Person
{
private $age;
function __construct($age)
{
$this->age = $age;
}
function howOldAreYou()
{
return $this->age;
}
}
$person = new Person(25);
echo $person->howOldAreYou(); // Outputs: 25
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:
class Person
{
private $age;
function __construct($age = 20)
{
$this->age = $age;
}
function howOldAreYou()
{
return $this->age;
}
}
$person = new Person; // if no argument is passed, parentheses can be omitted
echo $person->howOldAreYou(); // Outputs: 20
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:
class Person
{
function __construct(
private $age = 20,
) {
}
}
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é:
namespace Transport;
class Car
{
function honk()
{
echo 'Beep beep!';
}
}
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:
$car = new Transport\Car;
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:
use Transport\Car;
$car = new Car;
Ö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:
class Person
{
private $age;
function __construct($age)
{
$this->age = $age;
}
function printInformation()
{
echo "Age: {$this->age} years\n";
}
}
class Student extends Person
{
private $fieldOfStudy;
function __construct($age, $fieldOfStudy)
{
parent::__construct($age);
$this->fieldOfStudy = $fieldOfStudy;
}
function printInformation()
{
parent::printInformation();
echo "Field of study: {$this->fieldOfStudy} \n";
}
}
$student = new Student(20, 'Computer Science');
$student->printInformation();
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:
$people = [
new Person(30),
new Student(20, 'Computer Science'),
new Employee(45, 'Director'),
];
foreach ($people as $person) {
$person->printInformation();
}
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.
class Engine
{
function start()
{
echo 'Engine is running.';
}
}
class Car
{
private $engine;
function __construct()
{
$this->engine = new Engine;
}
function start()
{
$this->engine->start();
echo 'The car is ready to drive!';
}
}
$car = new Car;
$car->start();
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:
class VisibilityExample
{
public $publicProperty = 'Public';
protected $protectedProperty = 'Protected';
private $privateProperty = 'Private';
public function printProperties()
{
echo $this->publicProperty; // Works
echo $this->protectedProperty; // Works
echo $this->privateProperty; // Works
}
}
$object = new VisibilityExample;
$object->printProperties();
echo $object->publicProperty; // Works
// echo $object->protectedProperty; // Throws an error
// echo $object->privateProperty; // Throws an error
Folytatva az osztályöröklést:
class ChildClass extends VisibilityExample
{
public function printProperties()
{
echo $this->publicProperty; // Works
echo $this->protectedProperty; // Works
// echo $this->privateProperty; // Throws an error
}
}
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.
final class FinalClass
{
}
// The following code will throw an error because we cannot inherit from a final class.
class ChildOfFinalClass extends FinalClass
{
}
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:
class Calculator
{
public static function add($a, $b)
{
return $a + $b;
}
public static function subtract($a, $b)
{
return $a - $b;
}
}
// Using the static method without creating an instance of the class
echo Calculator::add(5, 3); // Output: 8
echo Calculator::subtract(5, 3); // Output: 2
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.
class Car
{
public const NumberOfWheels = 4;
public function displayNumberOfWheels(): int
{
echo self::NumberOfWheels;
}
}
echo Car::NumberOfWheels; // Output: 4
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.
interface Animal
{
function makeSound();
}
class Cat implements Animal
{
public function makeSound()
{
echo 'Meow';
}
}
$cat = new Cat;
$cat->makeSound();
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:
interface Guardian
{
function guardHouse();
}
class Dog implements Animal, Guardian
{
public function makeSound()
{
echo 'Bark';
}
public function guardHouse()
{
echo 'Dog diligently guards the house';
}
}
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.
abstract class AbstractClass
{
public function regularMethod()
{
echo 'This is a regular method';
}
abstract public function abstractMethod();
}
class Child extends AbstractClass
{
public function abstractMethod()
{
echo 'This is the implementation of the abstract method';
}
}
$instance = new Child;
$instance->regularMethod();
$instance->abstractMethod();
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:
class Person
{
private int $age;
public function __construct(int $age)
{
$this->age = $age;
}
public function printAge(): void
{
echo "This person is {$this->age} years old.";
}
}
/**
* A function that accepts a Person object and prints the person's age.
*/
function printPersonAge(Person $person): void
{
$person->printAge();
}
Í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.
class Registry
{
/** @var array<Person> indicates that it's an array of Person objects */
private array $persons = [];
public function addPerson(Person $person): void
{
$this->persons[] = $person;
}
}
Ö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ó.
class Car
{
public string $brand;
public function __construct(string $brand)
{
$this->brand = $brand;
}
}
$car1 = new Car('Skoda');
$car2 = new Car('Skoda');
$car3 = $car1;
var_dump($car1 == $car2); // true, because they have the same value
var_dump($car1 === $car2); // false, because they are not the same instance
var_dump($car1 === $car3); // true, because $car3 is the same instance as $car1
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:
class Person
{
private int $age;
public function __construct(int $age)
{
$this->age = $age;
}
}
class Student extends Person
{
private string $major;
public function __construct(int $age, string $major)
{
parent::__construct($age);
$this->major = $major;
}
}
$student = new Student(20, 'Computer Science');
// Check if $student is an instance of the Student class
var_dump($student instanceof Student); // Output: bool(true)
// Check if $student is an instance of the Person class (because Student is a descendant of Person)
var_dump($student instanceof Person); // Output: bool(true)
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:
public function sendMessage()
{
$email = new Email;
$email->setFrom('sender@example.com')
->setRecipient('admin@example.com')
->setMessage('Hello, this is a message.')
->send();
}
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.
class Sheep
{
public string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function __clone()
{
$this->name = 'Clone of ' . $this->name;
}
}
$original = new Sheep('Dolly');
echo $original->name . "\n"; // Outputs: Dolly
$clone = clone $original;
echo $clone->name . "\n"; // Outputs: Clone of Dolly
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:
trait Honking
{
public function honk()
{
echo 'Beep beep!';
}
}
class Car
{
use Honking;
}
class Truck
{
use Honking;
}
$car = new Car;
$car->honk(); // Outputs 'Beep beep!'
$truck = new Truck;
$truck->honk(); // Also outputs 'Beep beep!'
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.
function division(float $a, float $b): float
{
if ($b === 0) {
throw new Exception('Division by zero!');
}
return $a / $b;
}
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:
try {
echo division(10, 0);
} catch (Exception $e) {
echo 'Exception caught: '. $e->getMessage();
}
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):
try {
echo division(10, 0);
} catch (Exception $e) {
echo 'Exception caught: '. $e->getMessage();
} finally {
// Code that is always executed whether the exception has been thrown or not
}
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:
class BankingException extends Exception {}
class InsufficientFundsException extends BankingException {}
class ExceededLimitException extends BankingException {}
class BankAccount
{
private int $balance = 0;
private int $dailyLimit = 1000;
public function deposit(int $amount): int
{
$this->balance += $amount;
return $this->balance;
}
public function withdraw(int $amount): int
{
if ($amount > $this->balance) {
throw new InsufficientFundsException('Not enough funds in the account.');
}
if ($amount > $this->dailyLimit) {
throw new ExceededLimitException('Daily withdrawal limit exceeded.');
}
$this->balance -= $amount;
return $this->balance;
}
}
Egyetlen try
blokkhoz több catch
blokk is megadható, ha különböző típusú kivételekre
számít.
$account = new BankAccount;
$account->deposit(500);
try {
$account->withdraw(1500);
} catch (ExceededLimitException $e) {
echo $e->getMessage();
} catch (InsufficientFundsException $e) {
echo $e->getMessage();
} catch (BankingException $e) {
echo 'An error occurred during the operation.';
}
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:
class Person
{
public function __construct(
public int $age,
) {
}
}
class Registry implements IteratorAggregate
{
private array $people = [];
public function addPerson(Person $person): void
{
$this->people[] = $person;
}
public function getIterator(): Generator
{
foreach ($this->people as $person) {
yield $person;
}
}
}
$list = new Registry;
$list->addPerson(new Person(30));
$list->addPerson(new Person(25));
foreach ($list as $person) {
echo "Age: {$person->age} years\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.