Vererbung und Polymorphismus sind zwei zentrale Konzepte der objektorientierten Programmierung, die in PHP vollständig unterstützt werden. Diese Konzepte helfen dabei, Code effizienter zu organisieren und wiederzuverwenden, was zu saubereren und wartbareren Anwendungen führt.
Vererbung ermöglicht es einer Klasse (Kindklasse), Eigenschaften und Methoden einer anderen Klasse (Elternklasse) zu übernehmen. Dies fördert die Code-Wiederverwendung und etabliert eine hierarchische Beziehung zwischen Klassen.
In PHP wird Vererbung mit dem Schlüsselwort extends
implementiert:
class Fahrzeug {
protected string $marke;
protected string $modell;
protected int $baujahr;
public function __construct(string $marke, string $modell, int $baujahr) {
$this->marke = $marke;
$this->modell = $modell;
$this->baujahr = $baujahr;
}
public function getBeschreibung(): string {
return "{$this->marke} {$this->modell} ({$this->baujahr})";
}
public function fahren(): string {
return "Das Fahrzeug fährt.";
}
}
class Auto extends Fahrzeug {
private int $türen;
public function __construct(string $marke, string $modell, int $baujahr, int $türen) {
parent::__construct($marke, $modell, $baujahr);
$this->türen = $türen;
}
public function getBeschreibung(): string {
return parent::getBeschreibung() . " mit {$this->türen} Türen";
}
}
// Verwendung
$meinAuto = new Auto("VW", "Golf", 2022, 5);
echo $meinAuto->getBeschreibung(); // Ausgabe: VW Golf (2022) mit 5 Türen
echo $meinAuto->fahren(); // Ausgabe: Das Fahrzeug fährt.In diesem Beispiel: - Auto erbt alle öffentlichen und
geschützten Eigenschaften und Methoden von Fahrzeug -
Auto erweitert die geerbte Funktionalität um eine
zusätzliche Eigenschaft $türen - Auto
überschreibt die Methode getBeschreibung(), nutzt aber die
Elternimplementierung mit parent:: - Auto erbt
die Methode fahren() unverändert
Der parent::-Operator ermöglicht den Zugriff auf
überschriebene Eigenschaften und Methoden der Elternklasse:
class Motorrad extends Fahrzeug {
private bool $hatBeiwagen;
public function __construct(string $marke, string $modell, int $baujahr, bool $hatBeiwagen) {
parent::__construct($marke, $modell, $baujahr);
$this->hatBeiwagen = $hatBeiwagen;
}
public function fahren(): string {
return parent::fahren() . " Aber vorsichtig, es ist ein Motorrad!";
}
}Bei der Vererbung gelten folgende Regeln für Zugriffsmodifikatoren:
public: Von überall zugänglich, auch in abgeleiteten
Klassenprotected: Zugänglich innerhalb der Klasse und in allen
abgeleiteten Klassenprivate: Nur innerhalb der definierenden Klasse
zugänglich, nicht in abgeleiteten Klassenclass Basis {
public $öffentlich = "Zugriff von überall";
protected $geschützt = "Zugriff in Basis und abgeleiteten Klassen";
private $privat = "Nur Zugriff in Basis";
public function testZugriff() {
echo $this->öffentlich; // Erlaubt
echo $this->geschützt; // Erlaubt
echo $this->privat; // Erlaubt
}
}
class Abgeleitet extends Basis {
public function testZugriff() {
echo $this->öffentlich; // Erlaubt
echo $this->geschützt; // Erlaubt
echo $this->privat; // Fehler! Kein Zugriff auf private Elemente der Elternklasse
}
}Seit PHP 5.5 kann die Vererbung mit dem Schlüsselwort
final verhindert werden:
// Diese Klasse kann nicht erweitert werden
final class EndgültigeKlasse {
// Implementierung...
}
// Fehler: Class KannNichtErweitern may not inherit from final class (EndgültigeKlasse)
class KannNichtErweitern extends EndgültigeKlasse {
// ...
}Das final-Schlüsselwort kann auch auf Methoden
angewendet werden, um zu verhindern, dass sie in abgeleiteten Klassen
überschrieben werden:
class Basis {
final public function unüberschreibbareMethode() {
// Implementierung...
}
}
class Abgeleitet extends Basis {
// Fehler: Cannot override final method Basis::unüberschreibbareMethode()
public function unüberschreibbareMethode() {
// ...
}
}Polymorphismus erlaubt es, Objekte verschiedener Klassen durch eine gemeinsame Schnittstelle zu behandeln. In PHP wird dies hauptsächlich durch Methodenüberschreibung und Typdeklarationen erreicht.
Eine abgeleitete Klasse kann eine Methode der Elternklasse überschreiben, um ihr eigenes Verhalten zu implementieren:
class Form {
public function berechneFlaeche(): float {
return 0.0;
}
public function beschreibung(): string {
return "Ich bin eine Form mit einer Fläche von " . $this->berechneFlaeche() . " Quadrateinheiten.";
}
}
class Kreis extends Form {
private float $radius;
public function __construct(float $radius) {
$this->radius = $radius;
}
public function berechneFlaeche(): float {
return pi() * $this->radius * $this->radius;
}
}
class Rechteck extends Form {
private float $breite;
private float $höhe;
public function __construct(float $breite, float $höhe) {
$this->breite = $breite;
$this->höhe = $höhe;
}
public function berechneFlaeche(): float {
return $this->breite * $this->höhe;
}
}Der wahre Nutzen des Polymorphismus wird deutlich, wenn wir Objekte verschiedener Klassen einheitlich behandeln:
// Array von verschiedenen Formen
$formen = [
new Kreis(5),
new Rechteck(4, 6),
new Kreis(3)
];
// Einheitlicher Umgang mit allen Formen
foreach ($formen as $form) {
echo $form->beschreibung() . PHP_EOL;
}
// Berechnung der Gesamtfläche
$gesamtFlaeche = array_sum(array_map(function($form) {
return $form->berechneFlaeche();
}, $formen));
echo "Gesamtfläche: " . $gesamtFlaeche;Seit PHP 7 können wir Typ-Deklarationen verwenden, um sicherzustellen, dass eine Funktion oder Methode ein Objekt eines bestimmten Typs (oder einer abgeleiteten Klasse) erwartet:
function druckeFormInfo(Form $form): void {
echo "Form-Info: " . $form->beschreibung();
}
// Funktioniert mit jeder Klasse, die von Form erbt
druckeFormInfo(new Kreis(7)); // Gültig
druckeFormInfo(new Rechteck(2, 3)); // Gültig
druckeFormInfo(new stdClass()); // Fehler! stdClass ist keine FormUm die Konzepte der Vererbung und des Polymorphismus in einem realitätsnahen Kontext zu demonstrieren, betrachten wir ein einfaches Zahlungssystem:
abstract class Zahlungsmethode {
protected float $betrag;
public function __construct(float $betrag) {
$this->betrag = $betrag;
}
// Abstrakte Methode, die von allen konkreten Zahlungsmethoden implementiert werden muss
abstract public function prozessieren(): bool;
public function getBetrag(): float {
return $this->betrag;
}
}
class Kreditkarte extends Zahlungsmethode {
private string $kartenNummer;
private string $ablaufDatum;
private string $ccv;
public function __construct(float $betrag, string $kartenNummer, string $ablaufDatum, string $ccv) {
parent::__construct($betrag);
$this->kartenNummer = $kartenNummer;
$this->ablaufDatum = $ablaufDatum;
$this->ccv = $ccv;
}
public function prozessieren(): bool {
// In einer realen Anwendung würde hier die Kreditkartenzahlung verarbeitet
echo "Verarbeite Kreditkartenzahlung über {$this->betrag}€...";
return true;
}
}
class PayPal extends Zahlungsmethode {
private string $email;
public function __construct(float $betrag, string $email) {
parent::__construct($betrag);
$this->email = $email;
}
public function prozessieren(): bool {
// In einer realen Anwendung würde hier die PayPal-Zahlung verarbeitet
echo "Verarbeite PayPal-Zahlung über {$this->betrag}€ für {$this->email}...";
return true;
}
}
class Banküberweisung extends Zahlungsmethode {
private string $iban;
private string $bic;
public function __construct(float $betrag, string $iban, string $bic) {
parent::__construct($betrag);
$this->iban = $iban;
$this->bic = $bic;
}
public function prozessieren(): bool {
// In einer realen Anwendung würde hier die Banküberweisung verarbeitet
echo "Verarbeite Banküberweisung über {$this->betrag}€ an {$this->iban}...";
return true;
}
}
// Verwenden des Zahlungssystems (polymorphischer Ansatz)
function zahlungDurchführen(Zahlungsmethode $zahlungsmethode): void {
echo "Zahlung über " . $zahlungsmethode->getBetrag() . "€ wird verarbeitet...\n";
if ($zahlungsmethode->prozessieren()) {
echo "Zahlung erfolgreich abgeschlossen!\n";
} else {
echo "Zahlung fehlgeschlagen!\n";
}
}
// Verschiedene Zahlungsmethoden testen
$kreditkarte = new Kreditkarte(99.99, "1234-5678-9012-3456", "12/25", "123");
$paypal = new PayPal(49.99, "kunde@example.com");
$banküberweisung = new Banküberweisung(199.99, "DE89370400440532013000", "COBADEFFXXX");
zahlungDurchführen($kreditkarte);
zahlungDurchführen($paypal);
zahlungDurchführen($banküberweisung);In diesem Beispiel: - Zahlungsmethode ist eine abstrakte
Basisklasse, die gemeinsame Eigenschaften und eine
Schnittstellendefinition bereitstellt - Jede konkrete Zahlungsmethode
erweitert die Basisklasse und implementiert ihre eigene Version von
prozessieren() - Die Funktion
zahlungDurchführen() arbeitet mit jeder Art von
Zahlungsmethode, ohne deren spezifischen Typ zu kennen
Vererbungshierarchie flach halten: Tiefe Vererbungshierarchien werden schnell komplex und schwer zu verwalten. Als Faustregel gilt, nicht mehr als 2-3 Ebenen zu verwenden.
Komposition vs. Vererbung: Manchmal ist Komposition (das Einbetten von Objekten in andere Objekte) besser als Vererbung. Die Regel “Bevorzuge Komposition vor Vererbung” ist oft sinnvoll zu beachten.
Liskovsches Substitutionsprinzip: Objekte einer abgeleiteten Klasse sollten ohne Probleme für Objekte der Basisklasse eingesetzt werden können, ohne dass die Anwendung davon betroffen ist. Dies ist ein grundlegendes Prinzip für guten objektorientierten Code.
Abstrakte Klassen für gemeinsame Implementierungen: Wenn mehrere Klassen gemeinsame Implementierungen haben, aber nicht direkt instanziiert werden sollten, sind abstrakte Klassen das richtige Werkzeug.