Lekcja 28 (OOP 8): Polimorfizm, Interfejsy (implements)
Witaj w ósmej lekcji naszego modułu o Programowaniu Obiektowym w PHP! W poprzednich lekcjach zgłębiliśmy dziedziczenie oraz klasy i metody abstrakcyjne, które pozwalają na tworzenie hierarchii klas i definiowanie wspólnych szkieletów funkcjonalności. Dzisiaj skupimy się na dwóch niezwykle ważnych koncepcjach OOP: polimorfizmie oraz interfejsach. Polimorfizm, czyli "wielopostaciowość", pozwala obiektom różnych klas reagować na to samo wywołanie metody w sposób specyficzny dla swojej klasy. Interfejsy natomiast definiują kontrakty, które klasy mogą implementować, gwarantując dostępność określonego zestawu metod, niezależnie od hierarchii dziedziczenia.
Zrozumienie polimorfizmu i interfejsów jest kluczowe dla pisania elastycznego, rozszerzalnego i łatwego w utrzymaniu kodu obiektowego. Pozwalają one na tworzenie bardziej abstrakcyjnych i luźno powiązanych komponentów systemu. Dowiemy się, jak polimorfizm manifestuje się w PHP w kontekście dziedziczenia i metod abstrakcyjnych, jak definiować i implementować interfejsy za pomocą słowa kluczowego interface
i implements
, oraz jakie są główne różnice i zastosowania interfejsów w porównaniu do klas abstrakcyjnych.
Czym Jest Polimorfizm?
Polimorfizm (z greckiego "poly" - wiele, "morphē" - postać, kształt) to zdolność obiektów należących do różnych klas do odpowiadania na to samo wywołanie metody (tę samą wiadomość) w sposób specyficzny dla swojej własnej klasy. Innymi słowy, ta sama operacja może zachowywać się inaczej w zależności od typu obiektu, na którym jest wykonywana.
Polimorfizm w PHP najczęściej realizowany jest poprzez:
- Dziedziczenie i nadpisywanie metod: Klasa potomna może nadpisać metodę odziedziczoną z klasy bazowej, dostarczając własną implementację. Gdy metoda ta jest wywoływana na obiekcie klasy potomnej, wykonuje się jej nadpisana wersja.
- Implementację interfejsów: Różne klasy mogą implementować ten sam interfejs, co oznacza, że każda z nich dostarczy własną implementację metod zdefiniowanych w tym interfejsie.
- Użycie klas abstrakcyjnych i metod abstrakcyjnych: Klasy potomne muszą zaimplementować metody abstrakcyjne zdefiniowane w klasie abstrakcyjnej, każda na swój sposób.
Główne korzyści z polimorfizmu:
- Elastyczność i rozszerzalność: Można łatwo dodawać nowe klasy, które implementują to samo zachowanie (np. metodę z interfejsu lub nadpisują metodę z klasy bazowej) bez modyfikowania kodu, który z tych obiektów korzysta. Kod klienta może operować na obiektach poprzez wspólny typ bazowy (klasę abstrakcyjną lub interfejs), nie martwiąc się o konkretny typ obiektu.
- Uproszczenie kodu: Zamiast pisać wiele instrukcji warunkowych (
if/else
lubswitch
) sprawdzających typ obiektu i wywołujących odpowiednią funkcję, można po prostu wywołać metodę na obiekcie, a polimorfizm zadba o to, aby wykonała się właściwa implementacja. - Lepsza organizacja i czytelność: Kod staje się bardziej modularny i łatwiejszy do zrozumienia, gdy operacje są definiowane na poziomie abstrakcji (interfejsu lub klasy bazowej).
Przykład Polimorfizmu z Dziedziczeniem
Kontynuując przykład z figurami geometrycznymi z poprzedniej lekcji:
<?php
abstract class FiguraGeometrycznaPolimorfizm
{
protected string $nazwaFigury;
public function __construct(string $nazwa) { $this->nazwaFigury = $nazwa; }
public function getNazwa(): string { return $this->nazwaFigury; }
abstract public function obliczPole(): float;
}
class KoloPolimorfizm extends FiguraGeometrycznaPolimorfizm
{
private float $promien;
public function __construct(float $promien)
{
parent::__construct("Koło");
$this->promien = $promien;
}
public function obliczPole(): float
{
return M_PI * pow($this->promien, 2);
}
}
class ProstokatPolimorfizm extends FiguraGeometrycznaPolimorfizm
{
private float $bokA, $bokB;
public function __construct(float $a, float $b)
{
parent::__construct("Prostokąt");
$this->bokA = $a;
$this->bokB = $b;
}
public function obliczPole(): float
{
return $this->bokA * $this->bokB;
}
}
class KwadratPolimorfizm extends ProstokatPolimorfizm // Kwadrat jest szczególnym przypadkiem prostokąta
{
public function __construct(float $bok)
{
parent::__construct($bok, $bok); // Wywołujemy konstruktor Prostokąta z tymi samymi bokami
$this->nazwaFigury = "Kwadrat"; // Możemy nadpisać nazwę ustawioną przez rodzica
}
// Metoda obliczPole() jest dziedziczona z ProstokatPolimorfizm i działa poprawnie dla kwadratu
}
// Funkcja, która operuje na dowolnej figurze geometrycznej (polimorfizm)
function wyswietlPoleFigury(FiguraGeometrycznaPolimorfizm $figura): void
{
echo "Figura: " . $figura->getNazwa() . "<br>";
// Wywołanie metody obliczPole() - dzięki polimorfizmowi wykona się
// odpowiednia implementacja dla konkretnego typu obiektu ($figura)
echo "Pole: " . round($figura->obliczPole(), 2) . " jedn.<sup>2</sup><br>";
}
$figury = [
new KoloPolimorfizm(5.0),
new ProstokatPolimorfizm(4.0, 6.0),
new KwadratPolimorfizm(3.0)
];
foreach ($figury as $figura) {
wyswietlPoleFigury($figura);
echo "---<br>";
}
/*
Przykładowy wynik:
Figura: Koło
Pole: 78.54 jedn.2
---
Figura: Prostokąt
Pole: 24 jedn.2
---
Figura: Kwadrat
Pole: 9 jedn.2
---
*/
?>
W tym przykładzie funkcja wyswietlPoleFigury()
przyjmuje jako argument obiekt typu FiguraGeometrycznaPolimorfizm
(klasa abstrakcyjna). Możemy jej przekazać obiekty klas KoloPolimorfizm
, ProstokatPolimorfizm
czy KwadratPolimorfizm
, ponieważ wszystkie one dziedziczą po FiguraGeometrycznaPolimorfizm
(spełniają relację "is-a"). Gdy wewnątrz funkcji wywołujemy $figura->obliczPole()
, PHP dynamicznie określa, która wersja tej metody ma być wykonana, bazując na faktycznym typie obiektu przekazanego jako $figura
. To jest właśnie polimorfizm w działaniu.
Czym Są Interfejsy?
Interfejs (interface) w PHP to konstrukcja, która definiuje zestaw publicznych metod, jakie klasa musi zaimplementować, jeśli deklaruje, że implementuje dany interfejs. Interfejs określa "co" klasa potrafi zrobić (jej kontrakt, jej API), ale nie mówi "jak" ma to zrobić (nie dostarcza implementacji metod).
Kluczowe cechy interfejsów:
- Deklarowane są za pomocą słowa kluczowego
interface
(np.interface NazwaInterfejsu
). - Mogą zawierać tylko deklaracje publicznych metod (bez ciała, zakończone średnikiem). Nie używa się słowa kluczowego
abstract
anipublic
przy deklaracji metod w interfejsie, ponieważ są one domyślnie publiczne i abstrakcyjne. - Mogą zawierać publiczne stałe (
const
). - Nie mogą zawierać właściwości instancyjnych ani metod zaimplementowanych (z pewnymi wyjątkami od PHP 8.0, gdzie interfejsy mogą mieć domyślne implementacje metod, ale jest to rzadziej stosowane i służy innym celom niż w klasach abstrakcyjnych).
- Nie można utworzyć instancji interfejsu (
new NazwaInterfejsu()
spowoduje błąd). - Klasa implementuje interfejs za pomocą słowa kluczowego
implements
(np.class MojaKlasa implements NazwaInterfejsu
). - Klasa implementująca interfejs musi zaimplementować wszystkie metody zadeklarowane w tym interfejsie, z zachowaniem kompatybilnej sygnatury i modyfikatora dostępu (który musi być
public
). - Jedna klasa może implementować wiele interfejsów (oddzielonych przecinkami, np.
class MojaKlasa implements InterfejsA, InterfejsB
). To jest sposób na osiągnięcie pewnego rodzaju "wielokrotnego dziedziczenia" zachowań. - Interfejsy mogą dziedziczyć po innych interfejsach za pomocą słowa kluczowego
extends
(np.interface InterfejsPotomny extends InterfejsBazowy
).
Definiowanie i Implementacja Interfejsu
<?php
// Definicja interfejsu
interface Zapisywalny
{
public const FORMAT_JSON = 'json';
public const FORMAT_XML = 'xml';
public function zapisz(string $sciezkaDoPliku): bool;
public function wczytaj(string $sciezkaDoPliku): mixed; // Może zwracać różne typy
public function getDaneDoZapisu(): array;
}
interface Logowalny
{
public function logujWiadomosc(string $wiadomosc, string $poziom = 'info'): void;
}
// Klasa implementująca jeden interfejs
class UstawieniaAplikacji implements Zapisywalny
{
private array $opcje = [];
public function __construct(array $domyslneOpcje = [])
{
$this->opcje = $domyslneOpcje;
}
public function setOpcja(string $klucz, mixed $wartosc): void
{
$this->opcje[$klucz] = $wartosc;
}
public function getOpcja(string $klucz): mixed
{
return $this->opcje[$klucz] ?? null;
}
// Implementacja metod z interfejsu Zapisywalny
public function getDaneDoZapisu(): array
{
return $this->opcje;
}
public function zapisz(string $sciezkaDoPliku): bool
{
$daneJson = json_encode($this->getDaneDoZapisu(), JSON_PRETTY_PRINT);
if (file_put_contents($sciezkaDoPliku, $daneJson) !== false) {
echo "Ustawienia zapisane do: {$sciezkaDoPliku} w formacie " . self::FORMAT_JSON . "<br>";
return true;
}
echo "Błąd zapisu ustawień do: {$sciezkaDoPliku}<br>";
return false;
}
public function wczytaj(string $sciezkaDoPliku): mixed
{
if (file_exists($sciezkaDoPliku)) {
$daneJson = file_get_contents($sciezkaDoPliku);
$this->opcje = json_decode($daneJson, true) ?? [];
echo "Ustawienia wczytane z: {$sciezkaDoPliku}<br>";
return $this->opcje;
}
echo "Plik {$sciezkaDoPliku} nie istnieje.<br>";
return null;
}
}
// Klasa implementująca wiele interfejsów
class Uzytkownik implements Zapisywalny, Logowalny
{
public string $nazwaUzytkownika;
private array $daneProfilu = [];
public function __construct(string $nazwa)
{
$this->nazwaUzytkownika = $nazwa;
}
public function setProfil(string $klucz, mixed $wartosc): void
{
$this->daneProfilu[$klucz] = $wartosc;
}
// Implementacja metod z Zapisywalny
public function getDaneDoZapisu(): array
{
return ['nazwa' => $this->nazwaUzytkownika, 'profil' => $this->daneProfilu];
}
public function zapisz(string $sciezkaDoPliku): bool
{
// Uproszczony zapis, np. jako serializowany obiekt lub JSON
if (file_put_contents($sciezkaDoPliku, serialize($this->getDaneDoZapisu())) !== false) {
$this->logujWiadomosc("Profil użytkownika '{$this->nazwaUzytkownika}' zapisany.", "debug");
return true;
}
return false;
}
public function wczytaj(string $sciezkaDoPliku): mixed
{
if (file_exists($sciezkaDoPliku)) {
$dane = unserialize(file_get_contents($sciezkaDoPliku));
if (is_array($dane)) {
$this->nazwaUzytkownika = $dane['nazwa'] ?? 'nieznany';
$this->daneProfilu = $dane['profil'] ?? [];
$this->logujWiadomosc("Profil użytkownika '{$this->nazwaUzytkownika}' wczytany.");
return $dane;
}
}
return null;
}
// Implementacja metody z Logowalny
public function logujWiadomosc(string $wiadomosc, string $poziom = 'info'): void
{
echo "[LOG - {$poziom}] ({$this->nazwaUzytkownika}): {$wiadomosc}<br>";
}
}
// Użycie
$ustawienia = new UstawieniaAplikacji(['jezyk' => 'pl', 'motyw' => 'ciemny']);
$ustawienia->setOpcja('liczbaWynikow', 20);
$ustawienia->zapisz('ustawienia.json');
$ustawienia->wczytaj('ustawienia.json');
var_dump($ustawienia->getOpcja('motyw'));
echo "<hr>";
$user = new Uzytkownik('admin');
$user->setProfil('email', 'admin@example.com');
$user->zapisz('profil_admin.dat');
$user->logujWiadomosc("Użytkownik się zalogował.");
// Polimorfizm z interfejsami
function wykonajZapis(Zapisywalny $obiektDoZapisu, string $plik): void
{
echo "Próba zapisu obiektu implementującego Zapisywalny...<br>";
if ($obiektDoZapisu->zapisz($plik)) {
echo "Zapis powiódł się dla pliku: {$plik}<br>";
} else {
echo "Zapis nie powiódł się dla pliku: {$plik}<br>";
}
}
wykonajZapis($ustawienia, 'backup_ustawien.json');
wykonajZapis($user, 'backup_user_admin.dat');
?>
W tym przykładzie:
Zapisywalny
iLogowalny
to interfejsy.Zapisywalny
definiuje metody związane z zapisem i odczytem danych oraz stałe dla formatów.- Klasa
UstawieniaAplikacji
implementuje interfejsZapisywalny
. - Klasa
Uzytkownik
implementuje zarówno interfejsZapisywalny
, jak iLogowalny
. - Funkcja
wykonajZapis()
przyjmuje jako argument obiekt dowolnej klasy, która implementuje interfejsZapisywalny
. Dzięki temu możemy jej przekazać zarówno obiekt$ustawienia
, jak i$user
, a funkcja będzie działać poprawnie, wywołując odpowiednią metodęzapisz()
dla każdego z nich (polimorfizm).
Interfejsy vs Klasy Abstrakcyjne – Kiedy Co Stosować?
Wybór między interfejsem a klasą abstrakcyjną zależy od konkretnej sytuacji i tego, co chcemy osiągnąć. Poniżej podsumowanie z poprzedniej lekcji, rozszerzone o kontekst polimorfizmu:
- Użyj klasy abstrakcyjnej, gdy:
- Chcesz współdzielić kod (zaimplementowane metody, właściwości) między blisko powiązanymi klasami (silna relacja "is-a", np.
Kolo
jestFiguraGeometryczna
). - Chcesz dostarczyć domyślną implementację dla niektórych metod, ale inne pozostawić do zaimplementowania przez klasy potomne.
- Chcesz, aby klasy dziedziczące miały wspólne właściwości lub metody z modyfikatorami
protected
. - Przewidujesz, że wspólna funkcjonalność może się zmieniać i chcesz, aby te zmiany były automatycznie odzwierciedlone we wszystkich klasach potomnych.
- Chcesz współdzielić kod (zaimplementowane metody, właściwości) między blisko powiązanymi klasami (silna relacja "is-a", np.
- Użyj interfejsu, gdy:
- Chcesz zdefiniować kontrakt (API), który mogą implementować różne, niekoniecznie powiązane hierarchią dziedziczenia klasy (relacja "can-do" lub "behaves-as", np.
Samochod
iRower
mogą implementowaćPojazdMechaniczny
, aPtak
iSamolot
mogą implementowaćLatajacy
). - Chcesz, aby klasa mogła implementować wiele "kontraktów" (ponieważ klasa może implementować wiele interfejsów, ale dziedziczyć tylko po jednej klasie).
- Chcesz osiągnąć pełne oddzielenie definicji od implementacji. Interfejs nie narzuca żadnej implementacji.
- Chcesz umożliwić polimorfizm dla zupełnie różnych typów obiektów, które łączy tylko wspólny zestaw zachowań.
- Chcesz zdefiniować kontrakt (API), który mogą implementować różne, niekoniecznie powiązane hierarchią dziedziczenia klasy (relacja "can-do" lub "behaves-as", np.
Często zdarza się, że klasy abstrakcyjne same implementują interfejsy, dostarczając częściową implementację metod wymaganych przez interfejs i pozostawiając resztę jako abstrakcyjne dla swoich potomków. Można też mieć hierarchię interfejsów (interfejs dziedziczący po innym interfejsie).
Polimorfizm z Interfejsami
Polimorfizm z interfejsami działa analogicznie do polimorfizmu z klasami abstrakcyjnymi. Jeśli funkcja lub metoda oczekuje obiektu implementującego dany interfejs, możemy jej przekazać obiekt dowolnej klasy, która ten interfejs implementuje. Wywołanie metody zdefiniowanej w interfejsie na takim obiekcie spowoduje wykonanie implementacji tej metody z konkretnej klasy obiektu.
Przykład z funkcją wykonajZapis(Zapisywalny $obiektDoZapisu, ...)
doskonale to ilustruje. Funkcja ta nie wie, czy dostaje obiekt UstawieniaAplikacji
czy Uzytkownik
. Wie tylko, że obiekt ten implementuje interfejs Zapisywalny
, a więc na pewno posiada metodę zapisz()
.
Podsumowanie Lekcji
W tej lekcji zgłębiliśmy dwie fundamentalne koncepcje OOP: polimorfizm i interfejsy. Zrozumieliśmy, że polimorfizm pozwala obiektom różnych klas reagować na to samo wywołanie metody w sposób specyficzny dla siebie, co prowadzi do bardziej elastycznego i rozszerzalnego kodu. Zobaczyliśmy, jak polimorfizm manifestuje się w PHP poprzez dziedziczenie i implementację interfejsów.
Nauczyliśmy się, jak definiować interfejsy za pomocą słowa kluczowego interface
, które określają kontrakt (zestaw publicznych metod) dla klas. Klasy implementują interfejsy za pomocą implements
i muszą dostarczyć implementacje wszystkich metod z interfejsu. Podkreśliliśmy, że klasa może implementować wiele interfejsów, co jest kluczowe dla modelowania złożonych zachowań.
Porównaliśmy interfejsy z klasami abstrakcyjnymi, wskazując na ich różne cele i scenariusze użycia. Interfejsy są idealne do definiowania kontraktów "can-do", podczas gdy klasy abstrakcyjne lepiej sprawdzają się do współdzielenia kodu i definiowania szkieletów dla blisko powiązanych klas w relacji "is-a".
W następnej lekcji przyjrzymy się Cechom (Traits), które oferują kolejny mechanizm reużywania kodu i horyzontalnego współdzielenia funkcjonalności w PHP.
Zadanie praktyczne
Stwórz system do odtwarzania różnych typów mediów (audio, wideo).
- Stwórz interfejs
Odtwarzalny
z następującymi metodami publicznymi:odtwarzaj(): void
pauzuj(): void
stop(): void
getTytul(): string
- Stwórz klasę
PlikAudio
implementującą interfejsOdtwarzalny
:- Właściwości:
private string $tytulAudio
,private string $artysta
. - Konstruktor przyjmujący tytuł i artystę.
- Zaimplementuj wszystkie metody z interfejsu
Odtwarzalny
. Metodyodtwarzaj
,pauzuj
,stop
powinny wyświetlać odpowiednie komunikaty (np. "Odtwarzanie audio: [tytuł] - [artysta]"). MetodagetTytul
powinna zwracać$tytulAudio
.
- Właściwości:
- Stwórz klasę
PlikWideo
implementującą interfejsOdtwarzalny
:- Właściwości:
private string $tytulWideo
,private int $dlugoscSekundy
. - Konstruktor przyjmujący tytuł i długość w sekundach.
- Zaimplementuj wszystkie metody z interfejsu
Odtwarzalny
. Metodyodtwarzaj
,pauzuj
,stop
powinny wyświetlać odpowiednie komunikaty (np. "Odtwarzanie wideo: [tytuł] (długość: [dlugoscSekundy]s)"). MetodagetTytul
powinna zwracać$tytulWideo
.
- Właściwości:
- Stwórz funkcję
uruchomOdtwarzacz(Odtwarzalny $medium): void
, która przyjmuje obiekt implementującyOdtwarzalny
, wyświetla jego tytuł, a następnie wywołuje na nim metodyodtwarzaj()
,pauzuj()
istop()
. - Przetestuj system: utwórz obiekty
PlikAudio
iPlikWideo
, a następnie przekaż je do funkcjiuruchomOdtwarzacz()
.
Kliknij, aby zobaczyć przykładowe rozwiązanie
<?php
interface Odtwarzalny
{
public function odtwarzaj(): void;
public function pauzuj(): void;
public function stop(): void;
public function getTytul(): string;
}
class PlikAudio implements Odtwarzalny
{
private string $tytulAudio;
private string $artysta;
public function __construct(string $tytul, string $artysta)
{
$this->tytulAudio = $tytul;
$this->artysta = $artysta;
}
public function odtwarzaj(): void
{
echo "Odtwarzanie audio: " . $this->tytulAudio . " - " . $this->artysta . "<br>";
}
public function pauzuj(): void
{
echo "Pauza audio: " . $this->tytulAudio . "<br>";
}
public function stop(): void
{
echo "Stop audio: " . $this->tytulAudio . "<br>";
}
public function getTytul(): string
{
return $this->tytulAudio;
}
}
class PlikWideo implements Odtwarzalny
{
private string $tytulWideo;
private int $dlugoscSekundy;
public function __construct(string $tytul, int $dlugosc)
{
$this->tytulWideo = $tytul;
$this->dlugoscSekundy = $dlugosc;
}
public function odtwarzaj(): void
{
echo "Odtwarzanie wideo: " . $this->tytulWideo . " (długość: " . $this->dlugoscSekundy . "s)<br>";
}
public function pauzuj(): void
{
echo "Pauza wideo: " . $this->tytulWideo . "<br>";
}
public function stop(): void
{
echo "Stop wideo: " . $this->tytulWideo . "<br>";
}
public function getTytul(): string
{
return $this->tytulWideo;
}
}
function uruchomOdtwarzacz(Odtwarzalny $medium): void
{
echo "<h3>Odtwarzacz dla: " . $medium->getTytul() . "</h3>";
$medium->odtwarzaj();
$medium->pauzuj();
$medium->stop();
}
// Testowanie
$piosenka = new PlikAudio("Bohemian Rhapsody", "Queen");
$film = new PlikWideo("Incepcja", 9000); // 2.5h * 60m * 60s
uruchomOdtwarzacz($piosenka);
echo "<hr>";
uruchomOdtwarzacz($film);
/* Przykładowy wynik:
Odtwarzacz dla: Bohemian Rhapsody
Odtwarzanie audio: Bohemian Rhapsody - Queen
Pauza audio: Bohemian Rhapsody
Stop audio: Bohemian Rhapsody
---
Odtwarzacz dla: Incepcja
Odtwarzanie wideo: Incepcja (długość: 9000s)
Pauza wideo: Incepcja
Stop wideo: Incepcja
*/
?>
Zadanie do samodzielnego wykonania
Zaprojektuj system obsługi różnych metod płatności.
- Stwórz interfejs
MetodaPlatnosci
z metodami:procesujPlatnosc(float $kwota): bool
(zwraca true jeśli płatność się powiodła)getNazwaMetody(): string
- Stwórz klasę
PlatnoscKarta
implementującąMetodaPlatnosci
. Dodaj właściwości np.$numerKarty
,$dataWaznosci
. MetodaprocesujPlatnosc
powinna symulować przetwarzanie (np. wyświetlić komunikat i zwrócić true). - Stwórz klasę
PlatnoscPayPal
implementującąMetodaPlatnosci
. Dodaj właściwość np.$emailPayPal
. MetodaprocesujPlatnosc
powinna symulować przetwarzanie. - Stwórz klasę
PlatnoscPrzelew
implementującąMetodaPlatnosci
. Dodaj właściwość np.$numerKontaBankowego
. MetodaprocesujPlatnosc
powinna symulować przetwarzanie. - Stwórz funkcję
dokonajZakupu(float $kwotaDoZaplaty, MetodaPlatnosci $metoda)
, która wyświetla nazwę metody płatności i próbuje przetworzyć płatność. - Przetestuj, tworząc różne obiekty płatności i przekazując je do funkcji
dokonajZakupu
.
FAQ - Polimorfizm i Interfejsy
Czy klasa może implementować interfejs i jednocześnie dziedziczyć po klasie (abstrakcyjnej lub konkretnej)?
Tak, klasa w PHP może jednocześnie dziedziczyć po jednej klasie (extends NazwaKlasyBazowej
) i implementować wiele interfejsów (implements InterfejsA, InterfejsB
). Słowo kluczowe extends
musi wystąpić przed implements
. Na przykład: class MojaSuperKlasa extends KlasaBazowa implements Interfejs1, Interfejs2 {}
.
Jaka jest główna korzyść z używania typowania argumentów funkcji za pomocą interfejsów (np. function foo(NazwaInterfejsu $obj)
)?
Główną korzyścią jest elastyczność i luźne powiązania (loose coupling). Funkcja taka może przyjmować obiekty dowolnych klas, które implementują dany interfejs, niezależnie od ich miejsca w hierarchii dziedziczenia. Gwarantuje to, że przekazany obiekt będzie posiadał metody zdefiniowane w interfejsie, co czyni kod bardziej generycznym i łatwiejszym do rozszerzenia o nowe typy obiektów.
Czy interfejs może dziedziczyć po innym interfejsie?
Tak, interfejsy mogą dziedziczyć po jednym lub wielu innych interfejsach za pomocą słowa kluczowego extends
. Interfejs potomny dziedziczy wtedy wszystkie deklaracje metod i stałych z interfejsów nadrzędnych. Na przykład: interface ZaawansowanyInterfejs extends PodstawowyInterfejs, InnyInterfejs {}
.
Czy metody w interfejsie mogą mieć modyfikatory dostępu inne niż public
?
Nie, wszystkie metody deklarowane w interfejsie są domyślnie publiczne. Nie można używać modyfikatorów protected
ani private
dla metod w interfejsie. Klasa implementująca interfejs musi zaimplementować te metody jako publiczne.
Czy interfejs może zawierać właściwości?
Nie, interfejsy nie mogą deklarować właściwości instancyjnych (zmiennych członkowskich). Mogą jedynie deklarować publiczne stałe klasowe (public const NAZWA_STALEJ = wartosc;
).
Czy od PHP 8.0 interfejsy mogą mieć domyślne implementacje metod? Jak to działa?
Tak, od PHP 8.0 interfejsy mogą zawierać metody z domyślną implementacją (czyli z ciałem). Jeśli klasa implementująca taki interfejs nie dostarczy własnej implementacji dla metody z domyślnym ciałem, zostanie użyta implementacja z interfejsu. Jest to jednak mechanizm, który należy stosować ostrożnie, aby nie zacierać granicy między interfejsami a klasami abstrakcyjnymi. Głównym celem interfejsów nadal jest definiowanie kontraktu.
Kiedy polimorfizm może być trudny w debugowaniu?
Ponieważ rzeczywiste zachowanie metody zależy od dynamicznego typu obiektu, śledzenie przepływu wykonania w złożonych hierarchiach polimorficznych może być czasem trudniejsze. Dobre narzędzia deweloperskie (debuggery) i jasne nazewnictwo oraz dokumentacja mogą tu bardzo pomóc. Jednak korzyści z elastyczności zazwyczaj przewyższają te trudności.