Lekcja 27 (OOP 7): Klasy Abstrakcyjne i Metody Abstrakcyjne
Witaj w siódmej lekcji naszego modułu o Programowaniu Obiektowym w PHP! W poprzedniej lekcji szczegółowo omówiliśmy mechanizm dziedziczenia, który pozwala klasom potomnym przejmować i rozszerzać funkcjonalności klas bazowych. Dzisiaj wprowadzimy pojęcie klas abstrakcyjnych i metod abstrakcyjnych. Są to potężne narzędzia, które pozwalają na definiowanie pewnego rodzaju "szablonów" lub "kontraktów" dla klas potomnych, wymuszając na nich implementację określonych metod, jednocześnie pozwalając na dostarczenie pewnej wspólnej funkcjonalności już w klasie abstrakcyjnej.
Zrozumienie klas abstrakcyjnych jest kluczowe dla projektowania elastycznych i dobrze zorganizowanych hierarchii klas. Pozwalają one na osiągnięcie wyższego poziomu abstrakcji, definiując wspólny interfejs dla grupy powiązanych klas, bez konieczności dostarczania pełnej implementacji wszystkich metod w klasie bazowej. Dowiemy się, jak deklarować klasy i metody abstrakcyjne za pomocą słowa kluczowego abstract
, jakie są ich właściwości i kiedy warto je stosować.
Czym Jest Klasa Abstrakcyjna?
Klasa abstrakcyjna to klasa, która nie może być bezpośrednio instancjonowana (nie można utworzyć jej obiektu za pomocą operatora new
). Służy ona jako klasa bazowa (szablon) dla innych klas. Klasa abstrakcyjna może zawierać zarówno metody zaimplementowane (konkretne), jak i metody abstrakcyjne.
Kluczowe cechy klasy abstrakcyjnej:
- Deklarowana jest za pomocą słowa kluczowego
abstract
przed słowemclass
(np.abstract class NazwaKlasy
). - Nie można utworzyć jej instancji: Próba zrobienia
$obj = new AbstrakcyjnaKlasa();
spowoduje błąd krytyczny (Fatal error). - Może zawierać metody abstrakcyjne (bez implementacji) oraz metody konkretne (z implementacją).
- Może zawierać właściwości (zarówno zwykłe, jak i statyczne) oraz stałe klasowe.
- Jeśli klasa zawiera przynajmniej jedną metodę abstrakcyjną, sama musi być zadeklarowana jako abstrakcyjna.
- Klasa dziedzicząca po klasie abstrakcyjnej musi zaimplementować wszystkie odziedziczone metody abstrakcyjne, chyba że sama również jest zadeklarowana jako abstrakcyjna.
Klasy abstrakcyjne są użyteczne, gdy chcemy zdefiniować wspólny szkielet dla grupy klas, ale pewne szczegóły implementacyjne muszą być dostarczone przez konkretne klasy potomne. Stanowią one kompromis między w pełni zaimplementowaną klasą bazową a interfejsem (o którym będziemy mówić w następnej lekcji), który definiuje tylko sygnatury metod bez żadnej implementacji.
Czym Jest Metoda Abstrakcyjna?
Metoda abstrakcyjna to metoda zadeklarowana w klasie abstrakcyjnej, która nie posiada ciała (implementacji). Definiuje ona jedynie sygnaturę metody (nazwę, parametry, typ zwracany) i musi być zaimplementowana (nadpisana) przez każdą konkretną (nieabstrakcyjną) klasę potomną, która dziedziczy po tej klasie abstrakcyjnej.
Kluczowe cechy metody abstrakcyjnej:
- Deklarowana jest za pomocą słowa kluczowego
abstract
przed modyfikatorem dostępu i słowemfunction
(np.abstract public function nazwaMetody(typ $param): typZwracany;
). - Nie posiada ciała – deklaracja kończy się średnikiem zamiast bloku kodu
{}
. - Może być zadeklarowana tylko w klasie abstrakcyjnej.
- Musi być zaimplementowana w każdej klasie potomnej, która nie jest sama klasą abstrakcyjną.
- Podczas implementacji w klasie potomnej, modyfikator dostępu musi być taki sam lub mniej restrykcyjny, a sygnatura (liczba i typy parametrów, typ zwracany) musi być kompatybilna (zgodnie z zasadami kowariancji i kontrawariancji).
- Metody abstrakcyjne nie mogą być deklarowane jako
private
(ponieważ musiałyby być zaimplementowane w tej samej klasie, co jest sprzeczne z ideą braku ciała) ani jakofinal
(ponieważ muszą być nadpisywane).
Metody abstrakcyjne służą do zdefiniowania "kontraktu", który muszą spełnić klasy potomne. Określają, jakie metody muszą być dostępne w obiektach tych klas, ale pozostawiają szczegóły ich działania do zaimplementowania przez te klasy.
Przykład Klasy Abstrakcyjnej i Metod Abstrakcyjnych
Rozważmy przykład z poprzedniej lekcji dotyczący figur geometrycznych. Możemy uczynić klasę FiguraGeometryczna
abstrakcyjną, ponieważ sama "figura geometryczna" jest pojęciem abstrakcyjnym i nie ma sensu tworzyć jej obiektu bez określenia konkretnego kształtu. Metody obliczPole()
i obliczObwod()
są idealnymi kandydatami na metody abstrakcyjne, ponieważ każda konkretna figura będzie je obliczać inaczej.
<?php
// Abstrakcyjna klasa bazowa
abstract class FiguraGeometryczna
{
protected string $nazwaFigury;
public string $kolor;
public function __construct(string $nazwa, string $kolor = "nieokreślony")
{
$this->nazwaFigury = $nazwa;
$this->kolor = $kolor;
echo "Tworzenie szablonu dla: " . $this->nazwaFigury . " o kolorze: " . $this->kolor . "<br>";
}
public function getNazwa(): string
{
return $this->nazwaFigury;
}
// Metoda konkretna, wspólna dla wszystkich figur
public function wyswietlInfo(): void
{
echo "To jest " . $this->getNazwa() . ".<br>";
echo "Kolor: " . $this->kolor . ".<br>";
echo "Pole: " . round($this->obliczPole(), 2) . " jedn.<sup>2</sup><br>";
echo "Obwód: " . round($this->obliczObwod(), 2) . " jedn.<br>";
}
// Metody abstrakcyjne - muszą być zaimplementowane przez klasy potomne
abstract public function obliczPole(): float;
abstract protected function obliczObwod(): float; // Może być protected, jeśli nie chcemy publicznego dostępu bezpośredniego
}
// Konkretna klasa potomna implementująca metody abstrakcyjne
class Kolo extends FiguraGeometryczna
{
private float $promien;
public function __construct(float $promien, string $kolor = "czerwony")
{
parent::__construct("Koło", $kolor); // Wywołanie konstruktora klasy bazowej
$this->promien = $promien;
echo "Utworzono obiekt Kolo o promieniu: " . $this->promien . "<br>";
}
// Implementacja metody abstrakcyjnej obliczPole
public function obliczPole(): float
{
return M_PI * pow($this->promien, 2);
}
// Implementacja metody abstrakcyjnej obliczObwod
// Modyfikator dostępu musi być taki sam lub mniej restrykcyjny (tutaj public >= protected)
public function obliczObwod(): float
{
return 2 * M_PI * $this->promien;
}
// Dodatkowa metoda specyficzna dla koła
public function getPromien(): float
{
return $this->promien;
}
}
class Prostokat extends FiguraGeometryczna
{
private float $bokA;
private float $bokB;
public function __construct(float $a, float $b, string $kolor = "niebieski")
{
parent::__construct("Prostokąt", $kolor);
$this->bokA = $a;
$this->bokB = $b;
echo "Utworzono obiekt Prostokat o bokach: " . $this->bokA . "x" . $this->bokB . "<br>";
}
public function obliczPole(): float
{
return $this->bokA * $this->bokB;
}
public function obliczObwod(): float
{
return 2 * ($this->bokA + $this->bokB);
}
}
// Próba utworzenia obiektu klasy abstrakcyjnej - spowoduje błąd
// $figura = new FiguraGeometryczna("Jakaś figura"); // Fatal error: Cannot instantiate abstract class FiguraGeometryczna
$mojeKolo = new Kolo(7.0, "żółty");
$mojeKolo->wyswietlInfo();
echo "<hr>";
$mojProstokat = new Prostokat(5.0, 10.0);
$mojProstokat->wyswietlInfo();
// Możemy teraz łatwo dodać inną figurę, np. Trójkąt, która musi zaimplementować obliczPole i obliczObwod
/*
abstract class Wielokat extends FiguraGeometryczna { // Może być pośrednia klasa abstrakcyjna
abstract public function liczbaBokow(): int;
}
class Trojkat extends FiguraGeometryczna { // lub Wielokat
private float $podstawa, $wysokosc, $bok1, $bok2, $bok3;
public function __construct(float $p, float $h, float $b1, float $b2, float $b3, string $kolor = "zielony") {
parent::__construct("Trójkąt", $kolor);
$this->podstawa = $p; $this->wysokosc = $h; $this->bok1 = $b1; $this->bok2 = $b2; $this->bok3 = $b3;
}
public function obliczPole(): float { return 0.5 * $this->podstawa * $this->wysokosc; }
public function obliczObwod(): float { return $this->bok1 + $this->bok2 + $this->bok3; }
}
$trojkat = new Trojkat(10, 5, 7, 8, 6);
$trojkat->wyswietlInfo();
*/
?>
W tym przykładzie:
FiguraGeometryczna
jest klasą abstrakcyjną. Zawiera konkretną właściwość$nazwaFigury
,$kolor
, konkretny konstruktor oraz konkretną metodęwyswietlInfo()
, która jest wspólna dla wszystkich figur.- Metody
obliczPole()
iobliczObwod()
są zadeklarowane jako abstrakcyjne. Oznacza to, żeFiguraGeometryczna
nie dostarcza ich implementacji, ale każda konkretna klasa dziedzicząca (jakKolo
czyProstokat
) musi je zaimplementować. - Klasy
Kolo
iProstokat
dziedziczą poFiguraGeometryczna
i dostarczają własne implementacje metodobliczPole()
iobliczObwod()
. - Próba utworzenia obiektu
new FiguraGeometryczna()
zakończyłaby się błędem. - Zauważ, że metoda abstrakcyjna
obliczObwod()
w klasie bazowej byłaprotected
, ale w klasieKolo
zaimplementowaliśmy ją jakopublic
. Jest to dozwolone (modyfikator mniej restrykcyjny). Gdybyśmy spróbowali zaimplementować ją jakoprivate
, byłby błąd.
Kiedy Używać Klas Abstrakcyjnych?
Klasy abstrakcyjne są przydatne w następujących sytuacjach:
- Chcesz współdzielić kod między kilkoma blisko powiązanymi klasami: Jeśli wiele klas ma wspólną funkcjonalność (metody zaimplementowane, właściwości), ale także pewne aspekty, które muszą być specyficznie zaimplementowane przez każdą z nich, klasa abstrakcyjna jest dobrym wyborem. Pozwala umieścić wspólny kod w klasie bazowej i zdefiniować "szablony" dla metod specyficznych.
- Chcesz zdefiniować wspólny interfejs dla grupy klas, ale z częściową implementacją: Klasa abstrakcyjna może definiować, jakie publiczne metody będą miały jej klasy potomne (poprzez metody abstrakcyjne i konkretne), zapewniając pewien poziom spójności, jednocześnie dostarczając już część implementacji.
- Chcesz stworzyć klasę bazową, która sama w sobie nie ma sensu jako konkretny obiekt: Jak w przykładzie
FiguraGeometryczna
– ogólna figura nie istnieje, istnieją tylko konkretne kształty. Podobnie klasaZwierze
mogłaby być abstrakcyjna, jeśli nie chcemy tworzyć ogólnych "zwierząt", a jedynie konkretne gatunki. - Chcesz kontrolować hierarchię dziedziczenia: Klasy abstrakcyjne mogą mieć konstruktory (nawet
protected
), co pozwala kontrolować, jak tworzone są obiekty klas potomnych i wymuszać przekazywanie określonych parametrów do konstruktora bazowego.
Klasy Abstrakcyjne vs Interfejsy
Klasy abstrakcyjne i interfejsy (które szczegółowo omówimy w następnej lekcji) służą do osiągania abstrakcji, ale robią to w różny sposób i mają różne zastosowania.
Główne różnice:
Cecha | Klasa Abstrakcyjna | Interfejs |
---|---|---|
Instancjonowanie | Nie można utworzyć instancji | Nie można utworzyć instancji |
Metody z implementacją | Może zawierać metody z implementacją (konkretne) | Generalnie nie (od PHP 8.0 interfejsy mogą mieć domyślne implementacje metod, ale jest to rzadziej używane i ma inne cele) |
Metody abstrakcyjne | Może zawierać metody abstrakcyjne (bez implementacji) | Wszystkie metody są domyślnie abstrakcyjne i publiczne (nie używa się słowa abstract ) |
Właściwości | Może zawierać właściwości (zwykłe i statyczne) | Może deklarować tylko publiczne stałe (const ), nie może mieć właściwości instancyjnych. |
Konstruktory | Może mieć konstruktor | Nie może mieć konstruktora |
Dziedziczenie / Implementacja | Klasa może dziedziczyć (extends ) tylko po jednej klasie abstrakcyjnej (lub konkretnej) |
Klasa może implementować (implements ) wiele interfejsów |
Cel główny | Definiowanie wspólnego szkieletu i współdzielenie kodu dla blisko powiązanych klas (relacja "jest rodzajem") | Definiowanie kontraktu (zbioru metod), który muszą spełnić klasy, niezależnie od ich hierarchii dziedziczenia (relacja "potrafi zrobić") |
W skrócie:
- Użyj klasy abstrakcyjnej, gdy chcesz stworzyć klasę bazową, która dostarcza pewną wspólną funkcjonalność (zaimplementowane metody, właściwości) dla grupy powiązanych klas, ale jednocześnie chcesz wymusić na klasach potomnych implementację pewnych specyficznych metod. Jest to dobre dla relacji "is-a" (np.
Kolo
jestFiguraGeometryczna
). - Użyj interfejsu, gdy chcesz zdefiniować kontrakt (zestaw publicznych metod), który mogą implementować różne, niekoniecznie powiązane hierarchią dziedziczenia klasy. Jest to dobre dla relacji "can-do" (np. klasa
Ptak
i klasaSamolot
mogą obie implementować interfejsLatajacy
).
Często klasy abstrakcyjne same mogą implementować interfejsy, dostarczając częściową implementację metod wymaganych przez interfejs i pozostawiając resztę jako abstrakcyjne dla swoich potomków.
Ważne Zasady Dotyczące Metod Abstrakcyjnych
- Modyfikatory dostępu: Kiedy implementujesz metodę abstrakcyjną w klasie potomnej, jej modyfikator dostępu musi być taki sam lub mniej restrykcyjny niż w deklaracji abstrakcyjnej. Na przykład, jeśli metoda abstrakcyjna jest
protected
, jej implementacja może byćprotected
lubpublic
, ale nieprivate
. Jeśli jestpublic
, implementacja musi byćpublic
. - Sygnatura metody: Nazwa metody musi być identyczna. Liczba wymaganych parametrów musi być taka sama. Typy parametrów i typ zwracany muszą być kompatybilne (zasady kowariancji/kontrawariancji). Oznacza to, że typ zwracany w metodzie implementującej może być taki sam lub bardziej szczegółowy (kowariancja), a typy parametrów mogą być takie same lub bardziej ogólne (kontrawariancja), choć najczęściej utrzymuje się identyczne typy.
- Parametry opcjonalne: Jeśli metoda abstrakcyjna definiuje parametr opcjonalny (z wartością domyślną), to metoda implementująca również może go mieć (lub uczynić go wymaganym, usuwając wartość domyślną, ale nie odwrotnie – nie można dodać wymaganego parametru, jeśli nie było go w sygnaturze abstrakcyjnej, ani zmienić wartości domyślnej w sposób niekompatybilny).
<?php
abstract class Baza
{
abstract protected function przetworz(string $dane, bool $opcja = true): array;
}
class Potomek extends Baza
{
// Poprawna implementacja
public function przetworz(string $dane, bool $opcja = true): array
{
echo "Przetwarzam dane: {$dane} z opcją: " . ($opcja ? 'tak' : 'nie') . "<br>";
return ['status' => 'ok', 'dane_przetworzone' => strtoupper($dane)];
}
}
/*
class PotomekZly1 extends Baza {
// BŁĄD: Modyfikator private jest bardziej restrykcyjny niż protected
private function przetworz(string $dane, bool $opcja = true): array { return []; }
}
class PotomekZly2 extends Baza {
// BŁĄD: Zmieniono typ zwracany na niekompatybilny (string zamiast array)
public function przetworz(string $dane, bool $opcja = true): string { return ""; }
}
class PotomekZly3 extends Baza {
// BŁĄD: Zmieniono typ parametru $dane na niekompatybilny (int zamiast string)
public function przetworz(int $dane, bool $opcja = true): array { return []; }
}
class PotomekZly4 extends Baza {
// BŁĄD: Dodano nowy wymagany parametr $dodatkowy
public function przetworz(string $dane, bool $opcja = true, string $dodatkowy): array { return []; }
}
*/
$p = new Potomek();
$wynik = $p->przetworz("testowe dane");
print_r($wynik);
?>
Podsumowanie Lekcji
W tej lekcji nauczyliśmy się, czym są klasy abstrakcyjne i metody abstrakcyjne w PHP. Zrozumieliśmy, że klasy abstrakcyjne, deklarowane za pomocą słowa kluczowego abstract
, nie mogą być instancjonowane i służą jako szablony dla klas potomnych. Mogą one zawierać zarówno metody zaimplementowane, jak i metody abstrakcyjne (bez ciała), które muszą być zaimplementowane przez konkretne klasy potomne.
Omówiliśmy kluczowe cechy i zasady dotyczące klas i metod abstrakcyjnych, w tym modyfikatory dostępu i zgodność sygnatur przy implementacji. Porównaliśmy również klasy abstrakcyjne z interfejsami, wskazując na ich główne różnice i typowe scenariusze użycia. Klasy abstrakcyjne są idealne do współdzielenia kodu i definiowania wspólnego szkieletu dla blisko powiązanych klas w relacji "is-a".
W następnej lekcji rozwiniemy temat abstrakcji, skupiając się na interfejsach i koncepcji polimorfizmu, które są kolejnymi fundamentalnymi elementami programowania obiektowego.
Zadanie praktyczne
Stwórz system do obsługi różnych typów powiadomień (email, SMS).
- Stwórz abstrakcyjną klasę
Powiadomienie
z:- Chronioną właściwością
$odbiorca
. - Chronioną właściwością
$tresc
. - Konstruktorem przyjmującym odbiorcę i treść.
- Publiczną, konkretną metodą
getTresc(): string
zwracającą treść. - Publiczną, konkretną metodą
getOdbiorca(): string
zwracającą odbiorcę. - Abstrakcyjną publiczną metodą
wyslij(): bool
, która będzie odpowiedzialna za wysłanie powiadomienia.
- Chronioną właściwością
- Stwórz konkretną klasę
PowiadomienieEmail
dziedziczącą poPowiadomienie
:- Dodaj prywatną właściwość
$tematEmail
. - Konstruktor powinien przyjmować odbiorcę (adres email), temat i treść, i wywoływać konstruktor rodzica.
- Zaimplementuj metodę
wyslij()
. W tej implementacji metoda powinna po prostu wyświetlać komunikat typu: "Wysyłanie emaila do [odbiorca] o temacie '[tematEmail]' z treścią: [tresc]" i zwracaćtrue
.
- Dodaj prywatną właściwość
- Stwórz konkretną klasę
PowiadomienieSMS
dziedziczącą poPowiadomienie
:- Konstruktor powinien przyjmować odbiorcę (numer telefonu) i treść, i wywoływać konstruktor rodzica.
- Zaimplementuj metodę
wyslij()
. W tej implementacji metoda powinna po prostu wyświetlać komunikat typu: "Wysyłanie SMSa do [odbiorca] z treścią: [tresc]" i zwracaćtrue
.
- Przetestuj system: utwórz obiekty
PowiadomienieEmail
iPowiadomienieSMS
, a następnie wywołaj na nich metodęwyslij()
.
Kliknij, aby zobaczyć przykładowe rozwiązanie
<?php
abstract class Powiadomienie
{
protected string $odbiorca;
protected string $tresc;
public function __construct(string $odbiorca, string $tresc)
{
$this->odbiorca = $odbiorca;
$this->tresc = $tresc;
}
public function getTresc(): string
{
return $this->tresc;
}
public function getOdbiorca(): string
{
return $this->odbiorca;
}
abstract public function wyslij(): bool;
}
class PowiadomienieEmail extends Powiadomienie
{
private string $tematEmail;
public function __construct(string $odbiorca, string $temat, string $tresc)
{
parent::__construct($odbiorca, $tresc);
$this->tematEmail = $temat;
}
public function wyslij(): bool
{
echo sprintf(
"Wysyłanie emaila do %s o temacie '%s' z treścią: %s<br>",
$this->getOdbiorca(),
$this->tematEmail,
$this->getTresc()
);
return true;
}
}
class PowiadomienieSMS extends Powiadomienie
{
public function __construct(string $odbiorca, string $tresc)
{
parent::__construct($odbiorca, $tresc);
}
public function wyslij(): bool
{
echo sprintf(
"Wysyłanie SMSa do %s z treścią: %s<br>",
$this->getOdbiorca(),
$this->getTresc()
);
return true;
}
}
// Testowanie
$email = new PowiadomienieEmail("jan.kowalski@example.com", "Ważna informacja", "Treść ważnej informacji email.");
if ($email->wyslij()) {
echo "Email został (symulacyjnie) wysłany.<br>";
}
echo "<hr>";
$sms = new PowiadomienieSMS("+48123456789", "Krótka wiadomość SMS.");
if ($sms->wyslij()) {
echo "SMS został (symulacyjnie) wysłany.<br>";
}
/* Przykładowy wynik:
Wysyłanie emaila do jan.kowalski@example.com o temacie 'Ważna informacja' z treścią: Treść ważnej informacji email.
Email został (symulacyjnie) wysłany.
---
Wysyłanie SMSa do +48123456789 z treścią: Krótka wiadomość SMS.
SMS został (symulacyjnie) wysłany.
*/
?>
Zadanie do samodzielnego wykonania
Stwórz abstrakcyjną klasę Pracownik
i konkretne klasy dziedziczące.
- Klasa
Pracownik
powinna być abstrakcyjna i zawierać:- Właściwości:
protected string $imie
,protected string $nazwisko
,protected float $stawkaGodzinowa
. - Konstruktor ustawiający te właściwości.
- Metodę konkretną
getDane(): string
zwracającą imię i nazwisko. - Metodę abstrakcyjną
obliczWynagrodzenieMiesieczne(int $liczbaGodzin): float
.
- Właściwości:
- Stwórz klasę
PracownikEtatowy
dziedziczącą poPracownik
. W implementacjiobliczWynagrodzenieMiesieczne
załóż, że pracownik etatowy ma stałe wynagrodzenie miesięczne (np. 160 * stawkaGodzinowa), niezależnie od podanej liczby godzin (ale liczba godzin może być użyta do walidacji, np. czy nie jest za mała). - Stwórz klasę
PracownikGodzinowy
dziedziczącą poPracownik
. W implementacjiobliczWynagrodzenieMiesieczne
wynagrodzenie jest liczone jakoliczbaGodzin * stawkaGodzinowa
. - Stwórz klasę
PracownikZleceniowy
dziedziczącą poPracownik
. Dodaj mu właściwośćprivate float $prowizjaOdSprzedazy
(ustawianą w konstruktorze). W metodzieobliczWynagrodzenieMiesieczne
wynagrodzenie toliczbaGodzin * stawkaGodzinowa + prowizjaOdSprzedazy
(załóż, że$liczbaGodzin
to przepracowane godziny, a prowizja jest dodatkiem). - Przetestuj, tworząc obiekty różnych typów pracowników i obliczając ich wynagrodzenia.
FAQ - Klasy Abstrakcyjne i Metody Abstrakcyjne
Czy klasa abstrakcyjna może nie mieć żadnych metod abstrakcyjnych?
Tak, klasa może być zadeklarowana jako abstract
nawet jeśli nie zawiera żadnych metod abstrakcyjnych. W takim przypadku głównym celem uczynienia jej abstrakcyjną jest uniemożliwienie tworzenia jej bezpośrednich instancji. Może ona nadal służyć jako klasa bazowa dostarczająca wspólną funkcjonalność.
Czy klasa abstrakcyjna może dziedziczyć po innej klasie abstrakcyjnej?
Tak, klasa abstrakcyjna może dziedziczyć po innej klasie abstrakcyjnej. W takim przypadku nie musi implementować metod abstrakcyjnych odziedziczonych z klasy nadrzędnej – obowiązek ten przechodzi na pierwszą konkretną (nieabstrakcyjną) klasę w hierarchii dziedziczenia.
Czy klasa abstrakcyjna może implementować interfejs?
Tak, klasa abstrakcyjna może implementować interfejs. Może ona dostarczyć implementacje niektórych (lub wszystkich) metod wymaganych przez interfejs, a te, których nie zaimplementuje, może zadeklarować jako abstrakcyjne, przerzucając obowiązek ich implementacji na swoje konkretne klasy potomne.
Jaka jest różnica między metodą abstrakcyjną a metodą w interfejsie?
Obie nie mają implementacji i definiują kontrakt. Jednak metody w interfejsach są domyślnie publiczne i abstrakcyjne (nie używa się słów kluczowych public
ani abstract
w ich deklaracji w interfejsie). Metody abstrakcyjne w klasie abstrakcyjnej muszą mieć jawnie podany modyfikator dostępu (public
lub protected
) i słowo kluczowe abstract
. Ponadto, klasa abstrakcyjna może zawierać zaimplementowane metody i właściwości, a interfejs (generalnie) nie.
Czy metody abstrakcyjne mogą być statyczne?
Nie, metody abstrakcyjne nie mogą być deklarowane jako static
. Metody statyczne są związane z klasą, a nie z obiektem, i ich idea często kłóci się z polimorficznym charakterem metod abstrakcyjnych, które są implementowane w różny sposób przez różne obiekty klas potomnych.
Co się stanie, jeśli klasa potomna nie zaimplementuje wszystkich metod abstrakcyjnych odziedziczonych po klasie abstrakcyjnej?
Jeśli klasa potomna nie zaimplementuje wszystkich odziedziczonych metod abstrakcyjnych, a sama nie jest zadeklarowana jako abstrakcyjna, PHP zgłosi błąd krytyczny (Fatal error) podczas próby utworzenia obiektu tej klasy potomnej lub nawet podczas parsowania skryptu, informując, że klasa zawiera niezdefiniowane metody abstrakcyjne.
Czy konstruktor w klasie abstrakcyjnej może być abstrakcyjny?
Nie, konstruktory nie mogą być deklarowane jako abstrakcyjne. Klasa abstrakcyjna może mieć normalny, zaimplementowany konstruktor (publiczny, chroniony lub nawet prywatny, choć prywatny miałby ograniczone zastosowanie). Jeśli klasa potomna ma własny konstruktor, powinna wywołać parent::__construct()
, jeśli jest to potrzebne.