W budowie
Materiał edukacyjny · Informatyka Kontrakty interfejsów, abstrakcja, elementy współdzielone i architektura wyjątków

CZĘŚĆ 24 · PROGRAMOWANIE OBIEKTOWE (OOP)

Polimorfizm, interfejsy, klasy abstrakcyjne i elementy statyczne

PPrezentacja dla nauczyciela

W rzeczywistych projektach webowych i frameworkach (np. Laravel, Symfony) rzadko pracujemy z pojedynczymi, odizolowanymi klasami. Prawdziwa siła obiektowości tkwi w projektowaniu wymiennych komponentów i definiowaniu kontraktów. Dzięki nim kod potrafi obsłużyć różne implementacje (np. bramkę PayU, Przelewy24 lub PayPal) za pomocą jednego, spójnego interfejsu — to zjawisko nazywamy polimorfizmem (wielopostaciowością).

W tej części zgłębisz zaawansowane mechanizmy OOP stosowane w profesjonalnych aplikacjach PHP oraz w pytaniach na egzaminach zawodowych i rozmowach rekrutacyjnych:

  • Klasy i metody abstrakcyjne (abstract): Szablony, których nie można instancjonować bezpośrednio, służące jako niepełna baza wymuszająca zdefiniowanie konkretnych metod w klasach pochodnych.
  • Interfejsy (interface oraz implements): Czyste kontrakty określające, jakie metody klasa musi udostępniać, bez narzucania sposobu ich wykonania.
  • Wielokrotna implementacja: W przeciwieństwie do dziedziczenia klas, jedna klasa w PHP może implementować wiele niezależnych interfejsów jednocześnie.
  • Polimorfizm w praktyce: Jak pisać funkcje operujące na typach interfejsowych zamiast konkretnych klasach, eliminując długie łańcuchy if/else czy switch.
  • Pola i metody statyczne (static): Składowe należące do samej klasy, a nie do jej pojedynczych instancji.
  • Operator zakresu :: (Paamayim Nekudotayim): Różnica między self:: a $this-> oraz dlaczego w metodzie statycznej nie istnieje zmienna $this.
  • Stałe klasowe (const): Niezmienne wartości powiązane z klasą i ich bezpieczny odczyt.
  • Obiektowa obsługa błędów (Wyjątki): Rzucanie (throw new Exception()) oraz przechwytywanie wyjątków w blokach try-catch-finally z analizą obiektów błędów.

Sprawdź swoją wiedzę, rozwiązując zestaw 16 przekrojowych pytań quizowych na dole strony.

Klasy abstrakcyjne

Klasy abstrakcyjnej nie można utworzyć przez new — służy jako wspólna baza. Może zawierać zwykłe metody oraz metody abstrakcyjne, czyli same nagłówki bez treści.

Klasa potomna musi zaimplementować wszystkie metody abstrakcyjne. To wymusza spójny interfejs: każda figura ma na przykład metodę pole().

Klasa abstrakcyjna różni się od interfejsu tym, że może mieć działający kod i właściwości. Interfejs opisuje wyłącznie zestaw metod.

abstract class Figura {
    abstract public function pole();

    public function opis() {
        return 'Pole figury: ' . round($this->pole(), 2);
    }
}

class Kwadrat extends Figura {
    public function __construct(private float $bok) {}

    public function pole() {
        return $this->bok ** 2;
    }
}

echo (new Kwadrat(3))->opis();
1. Czy można utworzyć klasę abstrakcyjną?
2. Co musi zrobić klasa potomna?
3. Czym różni się od interfejsu?

Interfejsy i polimorfizm

Interfejs deklaruje metody bez implementacji, a klasa przyjmuje go słowem implements. Jedna klasa może implementować kilka interfejsów, bo w PHP dziedziczyć można tylko po jednej klasie.

Polimorfizm polega na tym, że różne klasy mają metody o tej samej nazwie i można je wywoływać w ten sam sposób. Pętla po figury może liczyć pole niezależnie od konkretnej klasy.

Interfejs jest umową: kod, który go używa, nie musi znać szczegółów implementacji. Wystarczy, że obiekt ma wymagane metody.

interface Figura {
    public function pole(): float;
}

class Kolo implements Figura {
    public function __construct(private float $r) {}
    public function pole(): float { return pi() * $this->r ** 2; }
}

class Prostokat implements Figura {
    public function __construct(private float $a, private float $b) {}
    public function pole(): float { return $this->a * $this->b; }
}

foreach ([new Kolo(1), new Prostokat(2, 3)] as $f) {
    echo round($f->pole(), 2) . ' ';
}
4. Czym jest interfejs?
5. Ile interfejsów można użyć?
6. Na czym polega polimorfizm?

Składowe statyczne i stałe klasowe

Składowa statyczna należy do klasy, a nie do obiektu — istnieje w jednym egzemplarzu. Wywołujemy ją przez self:: wewnątrz klasy albo Klasa:: na zewnątrz.

static przydaje się do liczników i funkcji pomocniczych, które nie potrzebują danych obiektu. Do obiektu odwołujemy się wtedy przez $this, a do statycznej przez self.

const definiuje stałą klasową — wartość niezmienną, czytaną tą samą składnią. Dobrze opisuje np. stawkę VAT obowiązującą w całej klasie.

class Licznik {
    private static int $ile = 0;
    public const WERSJA = '1.0';

    public static function dodaj(): void {
        self::$ile++;
    }

    public static function wartosc(): int {
        return self::$ile;
    }
}

Licznik::dodaj();
Licznik::dodaj();
echo Licznik::wartosc() . ' / ' . Licznik::WERSJA;
7. Do czego należy składowa statyczna?
8. Jak czyta się stałą klasową?
9. Czym jest self?

Ćwiczenia — Quiz wiedzy o zaawansowanym OOP w PHP (16 pytań)

Przetestuj swoje zrozumienie klas abstrakcyjnych, interfejsów, polimorfizmu, elementów statycznych i obsługi wyjątków. Zaznacz prawidłowe odpowiedzi w poniższych 16 pytaniach, a następnie sprawdź swój wynik na dole strony.

1. Jaki będzie rezultat próby utworzenia obiektu klasy zadeklarowanej ze słowem kluczowym abstract class?
<?php
abstract class Pojazd {
    public int $kola = 4;
}

$p = new Pojazd();
?>
2. Które stwierdzenie dotyczące metod abstrakcyjnych (abstract function) w PHP jest prawdziwe?
3. Które słowo kluczowe służy w PHP do wdrożenia (zaimplementowania) interfejsu przez klasę?
4. Czy klasa w PHP może implementować więcej niż jeden interfejs?
5. Jaki mechanizm programowania obiektowego demonstruje poniższy kod?
<?php
interface PowitanieInterface {
    public function pobierzTekst(): string;
}

class Dziennik implements PowitanieInterface {
    public function pobierzTekst(): string { return "Dzień dobry"; }
}

class Noc implements PowitanieInterface {
    public function pobierzTekst(): string { return "Dobry wieczór"; }
}

function wyswietl(PowitanieInterface $p): void {
    echo $p->pobierzTekst();
}
?>
6. Co odróżnia klasę abstrakcyjną od interfejsu w języku PHP?
7. Jaka wartość zostanie wyświetlona po wykonaniu poniższego kodu?
<?php
class Licznik {
    public static int $ile = 0;

    public function zwieksz(): void {
        self::$ile++;
    }
}

$a = new Licznik();
$a->zwieksz();

$b = new Licznik();
$b->zwieksz();

echo Licznik::$ile;
?>
8. Jak poprawnie wywołać publiczną metodę statyczną pomnoz zdefiniowaną w klasie Kalkulator bez tworzenia instancji?
9. Co stanie się podczas próby użycia pseudozmiennej $this wewnątrz metody zadeklarowanej jako public static function?
<?php
class Narzedzia {
    public string $wersja = "1.0";

    public static function sprawdz(): void {
        echo $this->wersja;
    }
}

Narzedzia::sprawdz();
?>
10. Jak poprawnie zadeklarować i odczytać stałą klasową w PHP?
<?php
class Konfiguracja {
    public const WERSJA = '2.4.0';
}
?>
11. Jaki wynik pojawi się na ekranie po wykonaniu poniższego fragmentu kodu?
<?php
class Sesja {
    public static int $licznik = 0;

    public function __construct() {
        self::$licznik++;
    }
}

$s1 = new Sesja();
$s2 = new Sesja();
new Sesja();

echo Sesja::$licznik;
?>
12. Która instrukcja służy w PHP do zgłoszenia nowego wyjątku obiektowego w przypadku napotkania błędu biznesowego?
13. Jaki ciąg znaków zostanie wyświetlony po wykonaniu poniższej konstrukcji try-catch?
<?php
try {
    throw new Exception("Brak połączenia z bazą!");
    echo "Sukces";
} catch (Exception $e) {
    echo "Złapano: " . $e->getMessage();
}
?>
14. Jaki interfejs jest wspólnym nadrzędnym typem w hierarchii wszystkich wyjątków i błędów (Exception oraz Error) w nowoczesnym PHP?
15. Kiedy w strukturze try-catch-finally wykonywany jest kod zawarty w bloku finally?
16. Co pojawi się na ekranie po uruchomieniu poniższego programu?
<?php
interface Dokument {
    public function pobierzTresc(): string;
}

class Drukarka {
    public static string $status = "OK";

    public static function drukuj(Dokument $d): void {
        echo $d->pobierzTresc() . " [" . self::$status . "]";
    }
}

class Notatka implements Dokument {
    public function __construct(private string $tekst) {}
    public function pobierzTresc(): string {
        return "Notatka: " . $this->tekst;
    }
}

$n = new Notatka("Zakupy");
Drukarka::drukuj($n);
?>