Dziedziczenie i klasy abstrakcyjne
🎯 Po tej lekcji rozszerzysz klasę przez extends, wywołasz parent::__construct i zdefiniujesz klasę abstrakcyjną z metodami abstrakcyjnymi.
Wyobraź sobie sklep z pojazdami: wszystkie pojazdy mają markę i rok produkcji, ale samochody mają dodatkowo liczbę drzwi, a motocykle — typ silnika. Kopiowanie właściwości do każdej klasy osobno to strata czasu i źródło błędów. Dziedziczenie pozwala zdefiniować wspólne cechy raz — w klasie bazowej — i rozszerzyć je w klasach pochodnych.
extends — rozszerzanie klasy
Słowo kluczowe extends sprawia, że podklasa dziedziczy wszystkie właściwości i metody klasy nadrzędnej:
<?php
declare(strict_types=1);
class Pojazd
{
public function __construct(
protected string $marka,
protected int $rokProdukcji,
) {}
public function podstawoweInfo(): string
{
return $this->marka . ' (' . $this->rokProdukcji . ')';
}
}
class Samochod extends Pojazd
{
public function __construct(
string $marka,
int $rokProdukcji,
private int $liczbaDrzwi,
) {
parent::__construct($marka, $rokProdukcji);
}
public function info(): string
{
return $this->podstawoweInfo() . ', drzwi: ' . $this->liczbaDrzwi;
}
}
class Motocykl extends Pojazd
{
public function __construct(
string $marka,
int $rokProdukcji,
private string $typSilnika,
) {
parent::__construct($marka, $rokProdukcji);
}
public function info(): string
{
return $this->podstawoweInfo() . ', silnik: ' . $this->typSilnika;
}
}
$auto = new Samochod('Toyota', 2020, 4);
$moto = new Motocykl('Honda', 2019, 'V2');
echo $auto->info(); // wynik: Toyota (2020), drzwi: 4
echo $moto->info(); // wynik: Honda (2019), silnik: V2
parent:: — wywołanie kodu klasy nadrzędnej
Gdy podklasa definiuje własny konstruktor, musi zadbać o inicjalizację właściwości z klasy nadrzędnej. Robi to przez parent::__construct(...):
parent::__construct($marka, $rokProdukcji);
Operator parent:: możesz też użyć w zwykłych metodach, żeby wywołać metodę nadrzędną i rozszerzyć jej działanie:
<?php
declare(strict_types=1);
class Pojazd
{
public function __construct(protected string $marka) {}
public function opisz(): string
{
return 'Pojazd: ' . $this->marka;
}
}
class ElektrycznySamochod extends Pojazd
{
public function __construct(
string $marka,
private int $zasiegKm,
) {
parent::__construct($marka);
}
public function opisz(): string
{
return parent::opisz() . ', zasięg: ' . $this->zasiegKm . ' km';
}
}
$tesla = new ElektrycznySamochod('Tesla', 500);
echo $tesla->opisz(); // wynik: Pojazd: Tesla, zasięg: 500 km
Nadpisywanie metod (override)
Podklasa może nadpisać metodę z klasy nadrzędnej, deklarując ją pod tą samą nazwą. PHP wywoła wersję z podklasy:
<?php
declare(strict_types=1);
class Powiadomienie
{
public function wyslij(string $wiadomosc): void
{
echo 'Powiadomienie: ' . $wiadomosc;
}
}
class PowiadomienieEmail extends Powiadomienie
{
public function __construct(private string $adresEmail) {}
public function wyslij(string $wiadomosc): void
{
echo 'E-mail do ' . $this->adresEmail . ': ' . $wiadomosc;
}
}
class PowiadomienieSMS extends Powiadomienie
{
public function __construct(private string $numerTelefonu) {}
public function wyslij(string $wiadomosc): void
{
echo 'SMS na ' . $this->numerTelefonu . ': ' . $wiadomosc;
}
}
$email = new PowiadomienieEmail('[email protected]');
$sms = new PowiadomienieSMS('+48500123456');
$email->wyslij('Twoje zamówienie jest gotowe.'); // wynik: E-mail do [email protected]: Twoje zamówienie jest gotowe.
$sms->wyslij('Twoje zamówienie jest gotowe.'); // wynik: SMS na +48500123456: Twoje zamówienie jest gotowe.
Klasy abstrakcyjne
Klasa abstrakcyjna to klasa, której nie można instancjonować bezpośrednio (nie możesz zrobić new NazwaKlasy()). Służy wyłącznie jako baza dla podklas. Deklarujesz ją słowem abstract:
<?php
declare(strict_types=1);
abstract class Ksztalt
{
// metoda abstrakcyjna — brak ciała, musi być zaimplementowana w podklasie
abstract public function pole(): float;
// metoda konkretna — dziedziczy się bez zmian (lub można nadpisać)
public function opisz(): string
{
return get_class($this) . ': pole = ' . $this->pole();
}
}
class Prostokat extends Ksztalt
{
public function __construct(
private float $szerokosc,
private float $wysokosc,
) {}
public function pole(): float
{
return $this->szerokosc * $this->wysokosc;
}
}
class Trojkat extends Ksztalt
{
public function __construct(
private float $podstawa,
private float $wysokosc,
) {}
public function pole(): float
{
return ($this->podstawa * $this->wysokosc) / 2;
}
}
$p = new Prostokat(4.0, 3.0);
$t = new Trojkat(6.0, 4.0);
echo $p->opisz(); // wynik: Prostokat: pole = 12
echo $t->opisz(); // wynik: Trojkat: pole = 12
// new Ksztalt(); // Fatal error: Cannot instantiate abstract class Ksztalt
new Ksztalt() kończy się błędem wykonania (Fatal Error), nie ostrzeżeniem. Klasa abstrakcyjna to projekt, nie gotowy produkt.Kiedy używać dziedziczenia?
Dziedziczenie sprawdza się, gdy klasy łączy relacja „jest rodzajem" (ang. is-a):
Samochodjest rodzajemPojazd✓Prostokatjest rodzajemKsztalt✓PowiadomienieEmailjest rodzajemPowiadomienie✓
Unikaj dziedziczenia dla relacji „ma" (ang. has-a) — tu lepszy jest inny wzorzec (kompozycja), ale to już temat na osobny moduł.
W następnej lekcji poznasz interfejsy — lżejszą formę kontraktu, gdzie jedna klasa może zobowiązać się do wielu niezależnych umów.
Sprawdź się
Co się stanie, gdy spróbujesz wywołać new na klasie zadeklarowanej jako abstract?
Klasa abstract istnieje wyłącznie jako szablon dla podklas. Próba new AbstraktowanaKlasa() kończy się Fatal Error: Cannot instantiate abstract class. Możesz używać new tylko na klasach konkretnych (bez abstract).
Do czego służy parent::__construct() w konstruktorze podklasy?
Gdy podklasa ma własny konstruktor i musi też zainicjalizować właściwości z klasy nadrzędnej, wywołuje parent::__construct() z odpowiednimi argumentami. Bez tego rodzicielskie właściwości nie zostaną ustawione.
Czym jest metoda abstrakcyjna?
abstract public function pole(): float; — to sama deklaracja bez ciała {}. PHP wymaga, żeby każda nieabstrakcyjna podklasa dostarczyła implementację tej metody. To sposób na narzucenie kontraktu wszystkim podklasom.
Ćwiczenie
Stwórz abstrakcyjną klasę Ksztalt z metodą abstrakcyjną pole(): float i metodą opisz(): string (zwraca 'Pole: X'). Następnie utwórz klasy Prostokat (szerokosc, wysokosc) i Kolo (promien, użyj M_PI). Utwórz obiekty obu klas i wywołaj na nich opisz().
Pokaż rozwiązanie
<?php
declare(strict_types=1);
abstract class Ksztalt
{
abstract public function pole(): float;
public function opisz(): string
{
return 'Pole: ' . round($this->pole(), 2);
}
}
class Prostokat extends Ksztalt
{
public function __construct(
private float $szerokosc,
private float $wysokosc,
) {}
public function pole(): float
{
return $this->szerokosc * $this->wysokosc;
}
}
class Kolo extends Ksztalt
{
public function __construct(
private float $promien,
) {}
public function pole(): float
{
return M_PI * $this->promien ** 2;
}
}
$p = new Prostokat(4.0, 3.0);
$k = new Kolo(5.0);
echo $p->opisz(); // wynik: Pole: 12
echo $k->opisz(); // wynik: Pole: 78.54