Polymorphismus über Interfaces
Teil des Abschnitts Objektorientierte Programmierung der PHP-Journey von Coddy — Lektion 34 von 91.
In der vorherigen Lektion haben Sie Polymorphismus durch Vererbung kennengelernt, bei dem Kindklassen Methoden der Elternklassen überschreiben. Interfaces bieten eine weitere leistungsstarke Möglichkeit, Polymorphismus zu erreichen, insbesondere bei der Arbeit mit nicht verwandten Klassen, die gemeinsames Verhalten teilen.
Bei Interfaces ergibt sich der Polymorphismus aus dem Vertrag selbst. Jede Klasse, die ein Interface implementiert, garantiert, dass sie über bestimmte Methoden verfügt, unabhängig von ihrer Vererbungshierarchie:
<?php
interface Notifiable {
public function send(string $message);
}
class EmailNotifier implements Notifiable {
public function send(string $message) {
return "Email: " . $message;
}
}
class SMSNotifier implements Notifiable {
public function send(string $message) {
return "SMS: " . $message;
}
}
class PushNotifier implements Notifiable {
public function send(string $message) {
return "Push: " . $message;
}
}
function notify(Notifiable $notifier, string $message) {
return $notifier->send($message);
}
echo notify(new EmailNotifier(), "Hello") . "\n";
echo notify(new SMSNotifier(), "Hello") . "\n";
echo notify(new PushNotifier(), "Hello");
Ausgabe:
Email: Hello
SMS: Hello
Push: HelloDie Funktion notify() arbeitet mit jedem Notifiable-Objekt. Diese Klassen teilen keine gemeinsame Elternklasse – sie stehen in keinerlei Beziehung zueinander, außer dass sie dasselbe Interface implementieren. Dies ist der entscheidende Vorteil gegenüber vererbungsbasierter Polymorphie: Sie können nicht verwandte Klassen nach ihren Fähigkeiten gruppieren, anstatt nach ihrer Abstammung.
Kernpunkt: Interface-basierte Polymorphie ermöglicht es, nicht verwandte Klassen basierend auf gemeinsamem Verhalten austauschbar zu verwenden, wodurch Ihr Code flexibler und von spezifischen Implementierungen entkoppelt wird.
Aufgabe
EinfachLassen Sie uns ein Speichersystem bauen, das Interface-basierte Polymorphie demonstriert. Sie werden verschiedene Speicher-Backends erstellen — ein Dateisystem und eine Datenbank —, die nichts gemeinsam haben, außer dass sie beide dasselbe Storage-Interface implementieren. Eine einzige Funktion wird mit jedem Speichertyp funktionieren und zeigen, wie Interfaces Polymorphie zwischen völlig unabhängigen Klassen ermöglichen.
Sie werden Ihren Code über vier Dateien organisieren:
Storable.php— Definieren Sie einStorableInterface mit zwei Methodensignaturen:save($key, $data)undretrieve($key). Dieser Vertrag stellt sicher, dass jedes Speichersystem Daten speichern und abrufen kann, unabhängig davon, wie es intern implementiert ist.FileStorage.php— Erstellen Sie eineFileStorageKlasse, dieStorableimplementiert. Binden Sie die Interface-Datei ein. Die Klasse sollte eine private$directoryEigenschaft haben, die über den Konstruktor gesetzt wird. Implementieren Siesave($key, $data)so, dass es"Saving '$data' to file $this->directory/$key.txt"zurückgibt. Implementieren Sieretrieve($key)so, dass es"Reading from file $this->directory/$key.txt"zurückgibt.DatabaseStorage.php— Erstellen Sie eineDatabaseStorageKlasse, die ebenfallsStorableimplementiert. Binden Sie die Interface-Datei ein. Die Klasse sollte eine private$tableNameEigenschaft haben, die über den Konstruktor gesetzt wird. Implementieren Siesave($key, $data)so, dass es"Inserting '$data' into table $this->tableName with key $key"zurückgibt. Implementieren Sieretrieve($key)so, dass es"Selecting from table $this->tableName where key = $key"zurückgibt.main.php— Binden Sie beide Speicher-Dateien ein. Erstellen Sie eine Funktion namensstoreData, die einenStorableParameter, einen Key und Daten akzeptiert. Die Funktion sollte das Ergebnis des Aufrufs vonsave()mit dem Key und den Daten zurückgeben. Erstellen Sie eine weitere Funktion namensfetchData, die einenStorableParameter und einen Key akzeptiert und das Ergebnis des Aufrufs vonretrieve()zurückgibt.
Sie erhalten vier Eingaben: einen Verzeichnispfad, einen Tabellennamen, einen Key und einige zu speichernde Daten. Erstellen Sie ein FileStorage Objekt mit dem Verzeichnis und ein DatabaseStorage Objekt mit dem Tabellennamen. Verwenden Sie Ihre storeData() Funktion, um die Daten zuerst mit dem Dateispeicher und dann mit dem Datenbankspeicher zu speichern. Geben Sie jedes Ergebnis in einer eigenen Zeile aus. Verwenden Sie dann fetchData(), um Daten aus dem Dateispeicher abzurufen, und geben Sie dieses Ergebnis aus.
Beachten Sie, wie storeData() und fetchData() mit beiden Speichertypen identisch funktionieren — es ist ihnen nur wichtig, dass das Objekt Storable implementiert. Das Dateisystem und die Datenbank haben völlig unterschiedliche interne Implementierungen, sind aber durch den Interface-Vertrag austauschbar.
Spickzettel
Schnittstellen ermöglichen Polymorphismus, indem sie es erlauben, nicht verwandte Klassen basierend auf gemeinsamem Verhalten anstatt auf einer Vererbungshierarchie austauschbar zu verwenden.
Jede Klasse, die eine Schnittstelle implementiert, garantiert, dass sie über bestimmte Methoden verfügt, wodurch ein Vertrag entsteht, der polymorphes Verhalten ermöglicht:
<?php
interface Notifiable {
public function send(string $message);
}
class EmailNotifier implements Notifiable {
public function send(string $message) {
return "Email: " . $message;
}
}
class SMSNotifier implements Notifiable {
public function send(string $message) {
return "SMS: " . $message;
}
}
function notify(Notifiable $notifier, string $message) {
return $notifier->send($message);
}
echo notify(new EmailNotifier(), "Hello");
echo notify(new SMSNotifier(), "Hello");Die Funktion notify() akzeptiert jedes Objekt, das Notifiable implementiert, unabhängig von der Klassenhierarchie. Dies macht den Code flexibler und entkoppelt ihn von spezifischen Implementierungen.
Hauptvorteil: Schnittstellenbasierter Polymorphismus gruppiert nicht verwandte Klassen nach ihren Fähigkeiten, nicht nach ihrer Abstammung, wodurch völlig unterschiedliche Implementierungen durch einen gemeinsamen Vertrag austauschbar verwendet werden können.
Probier es selbst
<?php
require_once 'FileStorage.php';
require_once 'DatabaseStorage.php';
// Eingaben lesen
$directory = trim(fgets(STDIN));
$tableName = trim(fgets(STDIN));
$key = trim(fgets(STDIN));
$data = trim(fgets(STDIN));
// TODO: Erstellen Sie eine Funktion storeData, die ein Storable, einen Schlüssel und Daten akzeptiert
// Sie sollte das Ergebnis des Aufrufs von save() mit dem Schlüssel und den Daten zurückgeben
// TODO: Erstellen Sie eine Funktion fetchData, die ein Storable und einen Schlüssel akzeptiert
// Sie sollte das Ergebnis des Aufrufs von retrieve() mit dem Schlüssel zurückgeben
// TODO: Erstellen Sie eine FileStorage-Instanz mit dem Verzeichnis
// TODO: Erstellen Sie eine DatabaseStorage-Instanz mit dem Tabellennamen
// TODO: Verwenden Sie storeData(), um Daten mit dem Dateispeicher zu speichern, und geben Sie dann das Ergebnis aus
// TODO: Verwenden Sie storeData(), um Daten mit dem Datenbankspeicher zu speichern, und geben Sie dann das Ergebnis aus
// TODO: Verwenden Sie fetchData(), um Daten aus dem Dateispeicher abzurufen, und geben Sie dann das Ergebnis aus
?>Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienEinführung in die OOPKlassen vs. ObjekteDas $this-SchlüsselwortMethodenEigenschaftenKonstruktor (__construct)Destruktor (__destruct)Rückblick - Einfacher Taschenrechner4Vererbung
Grundlagen der VererbungDas parent:: SchlüsselwortMethoden überschreibenDas final SchlüsselwortAbstrakte KlassenZusammenfassung – Mitarbeiter-Hierarchie7Kapselung
Public, Protected, PrivateZugriffsmodifikatoren im DetailGetter und SetterInformation HidingConstructor Promotion (8.0)Zusammenfassung – Studentenverwaltungssystem2Namensräume & Autoloading
Einführung in NamensräumeDas use-SchlüsselwortPSR-4 Autoloading-StandardComposer AutoloaderZusammenfassung – Organisiertes Projekt5Interfaces & Verträge
Einführung in InterfacesInterfaces implementierenMehrfache Interface-ImplementierungInterface vs. Abstrakte KlasseType Hinting mit InterfacesZusammenfassung – Formen-Rechner8Magische Methoden
Einführung in magische Methoden__toString & __debugInfo__get, __set, __isset, __unset__call & __callStatic__clone & Objekt-Klonen__serialize & __unserializeZusammenfassung - Eigene Collection11Typsystem & Fehlerbehandlung
TypdeklarationenNullable-TypenUnion- & Intersection-TypenException-KlassenEigene Exception-HierarchieTry, Catch, FinallyZusammenfassung - Formular-Validator14Projekt: Bibliotheksverwaltung
ProjektübersichtBuch- und Benutzerklassen3Klasseneigenschaften
Instanz- vs. statische EigenschaftenKonstanten in KlassenStatische Methoden & EigenschaftenPrivate & geschützte EigenschaftenReadonly-Eigenschaften (PHP 8.1)Zusammenfassung – Bankkonto-Manager6Polymorphismus
Method Overriding erneut betrachtetPolymorphismus über InterfacesType Hinting & Union TypesLate Static BindingZusammenfassung – Payment Processor9Traits
Einführung in TraitsVerwendung mehrerer TraitsKonfliktlösung bei TraitsAbstrakte Methoden in TraitsTraits vs. Vererbung12Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterObserver-MusterStrategy-Muster15Abschlussherausforderungen
E-Learning-PlattformBanksystemSpielcharakter-EntwicklungFahrzeugvermietung