Wielu z Was zapewne zastanawia się dlaczego programowanie obiektowe jest lepsze. Otóż dzięki OOP(object oriented programming) jesteśmy w stanie w bardziej ludzki sposób modelować świat programistyczny. W poniższym przykładzie postaram się przedstawić mechanizm polimorfizmu i korzyści, które daje nam programowanie obiektowe.
Dla celów wpisu utworzę sobie interfejs IVehicle, który będzie opisywał pojazd, klasę abstrakcyjną samochód, która opisuje samochód oraz klasy Maluch, Audi, Plane(uproszczenie bo to powinna być klasa abstrakcyjna), które opisują konkretne samochody, samoloty. Dla przypomnienia:
Klasa – jest to opis obiektu zawierający jego właściwości i metody, które może używać.
Klasa abstrakcyjna – jest opisem obiektu jednakże niektóre z jej metod mogą być abstrakcyjne, tj bez ich definicji. Czyli mówiąc prostszym językiem piszemy sobie, że taka metoda powinna być w tej klasie, ale nie definiujemy dokładnie jak ma wyglądać pozwalając programiście dowolnie ją zaimplementować w klasach pochodnych.
interface IVehicle { public function run(); public function stop(); public function getName(); public function setName($name); }
Interfejs deklarujemy słowem kluczowym interface przed nazwą. Dobrą praktyką jest zaczynanie nazwy interfejsu dużą literą I aby łatwiej było je rozpoznać w kodzie. Interfejs zawiera deklaracje 4 metod. Pierwsza run() będzie odpowiadała za poruszanie sie pojazdu, stop() za zatrzymanie, getName() za pobranie imienia, setName() za ustawienie imienia.
abstract class Car implements IVehicle { protected $name; protected $fuelCapacity; public function run() { //some mechanics to start a car echo "$this->name car is started\n"; } public function stop() { //some mechanics to stop a car echo "$this->name car is stopped"; } public function tank($fuel) { $this->fuelCapacity = $fuel; } public function getName() { return $this->name; } public function setName($name) { if(!empty($name) && isset($name)) { $this->name = $name; echo "ustawiam nazwe na $name\n"; } } }
Abstakcyjna klasa samochód implementuje interfejs oraz zawiera definicje metod interfejsu. W tym miejscu warto wspomnieć, że klasa może implementować więcej niż jeden interfejs.
class Maluch extends Car { public function run() { parent::run(); echo "kaszleee...\n"; } public function stop() { echo "maluch sie nie zatrzymuje..\n"; } } class Audi extends Car { public function nitro() { echo "adding some speed\n"; } }
Powyżej mamy deklaracje klas konkretnych samochodów, maluch napisuje metode run() i stop() z abstrakcyjnej klasy Car. W pierwszej metodzie wywołuje za pomocą słowa kluczowego parent:: metodę z klasy nadrzędnej i dodaje coś od siebie, w drugiej całkowicie nadpisuje metodę stop().
Audi natomiast w całości implementuje metody z klasy samochód i dodaje metodę nitro() specyficzną dla pojazdów audi
Ostatnią klasą bedzie samolot, który w inny sposób podchodzi do metod pojazdu.
class Plane implements IVehicle { public function fly() { echo "This is $this->name and I am flying\n"; } public function run() { $this->fly(); } public function stop() { echo "I'm stopping"; } public function getName() { return $this->name; } public function setName($name) { if(!empty($name) && isset($name)) { $this->name = $name; echo "ustawiam nazwe na $name\n"; } } }
W poprawnej formie getName() i setName() powinny zostać umieszczone w innej klasie by nie powtarzać kodu, ale w tym prostym przykładzie by nie komplikować zdecydowałem się na powtórzenie kodu. Samolot wywołuje metodę fly() w metodzie run i implementuje interfejs pojazd.
I teraz jak to działa:
$car = new Maluch(); // stworzenie instancji klasy Maluch $audi = new Audi(); // stworzenie instancji klasy Audi $plane = new Plane(); //stworzenie instacji klasy Plane $car->setName('126p'); //ustawienie nazwy malcha na 126p $car->run(); //wywołanie metody run na maluchu $car->stop(); //wywołanie metody stop na maluchu $audi->setName('a3'); //ustawienie nazwy audi na a3 $audi->run(); //wywołanie metody run $audi->nitro(); //wywołanie metody nitro na audi $audi->stop(); // wywołanie metody stop $plane->setName('airbus'); //ustawienie nazwy samolotu $plane->run(); //wywołanie metody run()
W tym miejscu możecie zauważyć jak działa dziedziczenie. Dzięki niemu nie powtarzamy kodu i jesteśmy w stanie bardzo szybko zmieniająć jedną metodę w klasie nadrzędnej, zmienić jej działanie we wszystkich klasach podrzędnych.
Powyższy kod wyświetli coś w stylu:
ustawiam nazwe na 126p 126p car is started kaszleee... maluch sie nie zatrzymuje.. ustawiam nazwe na a3 a3 car is started adding some speed a3 car is stoppedustawiam nazwe na airbus This is airbus and I am flying
A teraz przejdzmy do bardziej zaawansowanego przykładu polimorfizmu. Załóżmy, że w aplikacji chcemy mieć funkcję, która „poruszy” dowolnym pojazdem(IVehicle) i nie interesuje nas w jaki sposób to zrobi. Wiedząc, że pojazd jest typu IVehicle czyli musi mieć zaimplementowaną metodę run() możemy nim poruszyć.
Przykład:
function runVehicle(IVehicle $vehicle) { $vehicle->run(); } runVehicle($audi); runVehicle($plane); runVehicle($car);
Funkcja runVehicle() pobiera referencje do pojazdu typu IVehicle następne wywołuje na nim metodę run().
Powyższy kod wyświetli:
a3 car is started This is airbus and I am flying 126p car is started kaszleee...
Mechanizm ten jest bardzo czesto wykorzystywany we wzorcach programistycznych i bibliotekach. Daje pole do popisu programistom i jest niezwykle użyteczny. Można go wykorzystać np do rysowania obiektów, do szyfrowania itd itp.
Pełny działający kod znajdziecie tutaj
Po co przekazujesz jawnie obiekt przez referencje, skoro PHP robi to za Ciebie ?
Oczywiście jest to błąd i niepotrzebne tworzenie referencji do referencji. Jak wiadomo w PHP obiekty domyślnie są przekazywane przez referencje. Dzięki poprawię.