Polymorphie über Schnittstellen
Teil des Abschnitts Objektorientierte Programmierung der PHP-Journey von Coddy. Lektion 34 von 91.
In der vorherigen Lektion hast du Polymorphie durch Vererbung kennengelernt, bei der Kindklassen Methoden der Elternklassen überschreiben. Interfaces bieten eine weitere leistungsstarke Möglichkeit, Polymorphie zu erreichen, insbesondere bei der Arbeit mit nicht verwandten Klassen, die ein gemeinsames Verhalten aufweisen.
Bei Schnittstellen entsteht Polymorphie aus dem Vertrag selbst. Jede Klasse, die eine Schnittstelle implementiert, garantiert, dass sie bestimmte Methoden besitzt, 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() funktioniert mit jedem Notifiable-Objekt. Diese Klassen haben keine gemeinsame übergeordnete Klasse – sie sind völlig unabhängig voneinander, abgesehen davon, dass sie dieselbe Schnittstelle implementieren. Dies ist der entscheidende Vorteil gegenüber der vererbungsbasierten Polymorphie: Du kannst unabhängige Klassen nach ihren Fähigkeiten statt nach ihrer Abstammung gruppieren.
Wichtiger Punkt: Schnittstellenbasierter Polymorphismus ermöglicht es, unabhängige Klassen aufgrund ihres gemeinsamen Verhaltens austauschbar zu verwenden, wodurch dein Code flexibler und von spezifischen Implementierungen entkoppelt wird.
Aufgabe
EinfachLass uns ein Speichersystem entwickeln, das polymorphes Verhalten auf Basis eines Interfaces demonstriert. Du wirst verschiedene Speicher-Backends erstellen, ein Dateisystem und eine Datenbank, die nichts gemeinsam haben, außer dass sie beide dasselbe Speicher-Interface implementieren. Eine einzelne Funktion wird mit jedem Speichertyp funktionieren und zeigen, wie Interfaces Polymorphismus zwischen vollständig unabhängigen Klassen ermöglichen.
Du wirst deinen Code auf vier Dateien verteilen:
Storable.php: Definiere einStorable-Interface 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: Erstelle eineFileStorage-Klasse, dieStorableimplementiert. Binde die Interface-Datei ein. Die Klasse soll eine private Eigenschaft$directorybesitzen, die über den Konstruktor festgelegt wird. Implementieresave($key, $data), sodass"Saving '[data]' to file [directory]/[key].txt"zurückgegeben wird. Implementiereretrieve($key), sodass"Reading from file [directory]/[key].txt"zurückgegeben wird.DatabaseStorage.php: Erstelle eineDatabaseStorage-Klasse, die ebenfallsStorableimplementiert. Binde die Interface-Datei ein. Die Klasse soll eine private Eigenschaft$tableNamebesitzen, die über den Konstruktor festgelegt wird. Implementieresave($key, $data), sodass"Inserting '[data]' into table [tableName] with key [key]"zurückgegeben wird. Implementiereretrieve($key), sodass"Selecting from table [tableName] where key = [key]"zurückgegeben wird.main.php: Binde beide Speicherdateien ein. Erstelle eine Funktion namensstoreData, die einenStorable-Parameter, einen Schlüssel und Daten akzeptiert. Die Funktion soll das Ergebnis des Aufrufs vonsave()mit dem Schlüssel und den Daten zurückgeben. Erstelle eine weitere Funktion namensfetchData, die einenStorable-Parameter und einen Schlüssel akzeptiert und das Ergebnis des Aufrufs vonretrieve()zurückgibt.
Du erhältst vier Eingaben: einen Verzeichnispfad, einen Tabellennamen, einen Schlüssel und einige zu speichernde Daten. Erstelle ein FileStorage mit dem Verzeichnis und ein DatabaseStorage mit dem Tabellennamen. Verwende deine Funktion storeData(), um die Daten zuerst mit dem Dateispeicher und anschließend mit dem Datenbankspeicher zu speichern. Gib jedes Ergebnis in einer eigenen Zeile aus. Verwende dann fetchData(), um Daten aus dem Dateispeicher abzurufen, und gib dieses Ergebnis aus.
Beachte, wie storeData() und fetchData() mit beiden Speichertypen identisch funktionieren. Sie berücksichtigen nur, dass das Objekt Storable implementiert. Das Dateisystem und die Datenbank verfügen über völlig unterschiedliche interne Implementierungen, sind durch den Interface-Vertrag jedoch austauschbar.
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: Erstelle eine Funktion storeData, die ein Storable, key und data akzeptiert
// Sie sollte das Ergebnis des Aufrufs von save() mit dem key und data zurückgeben
// TODO: Erstelle eine Funktion fetchData, die ein Storable und key akzeptiert
// Sie sollte das Ergebnis des Aufrufs von retrieve() mit dem key zurückgeben
// TODO: Erstelle eine FileStorage-Instanz mit dem directory
// TODO: Erstelle eine DatabaseStorage-Instanz mit dem table name
// TODO: Verwende storeData(), um Daten mit Dateispeicher zu speichern, und gib dann das Ergebnis aus
// TODO: Verwende storeData(), um Daten mit Datenbankspeicher zu speichern, und gib dann das Ergebnis aus
// TODO: Use fetchData() to retrieve from file storage, then print the result
?>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 OOPKlassen vs. ObjekteDas Schlüsselwort $thisMethodenEigenschaftenKonstruktor (__construct)Destruktor (__destruct)Rückblick – Einfacher Taschenrechner4Vererbung
Grundlegende VererbungDas Schlüsselwort parent::MethodenüberschreibungDas Schlüsselwort finalAbstrakte KlassenRückblick – Mitarbeiterhierarchie7Kapselung
Öffentlich, geschützt, privatZugriffsmodifikatoren im DetailGetter und SetterInformationsverbergungKonstruktor-Promotion (8.0)Wiederholung – System zur Verwaltung von Schülerdatensätzen2Namespaces & Autoloading
Einführung in NamespacesDas use-SchlüsselwortPSR-4-Autoloading-StandardComposer-AutoloaderRückblick – Organisiertes Projekt5Schnittstellen & Verträge
Einführung in SchnittstellenSchnittstellen implementierenMehrere Schnittstellen implementierenSchnittstelle vs. abstrakte KlasseTyp-Hinweise mit SchnittstellenRückblick – Formrechner8Magische Methoden
Einführung in magische Methoden__toString & __debugInfo__get, __set, __isset, __unset__call & __callStatic__clone & Objektklonen__serialize & __unserializeRückblick – Benutzerdefinierte Sammlung11Typsystem & Fehlerbehandlung
TypdeklarationenNullable-TypenUnion- & Intersection-TypenException-KlassenBenutzerdefinierte Exception-HierarchieTry, Catch, FinallyRückblick – Formularvalidator14Projekt: Bibliotheksverwaltung
ProjektübersichtBuch- und Benutzerklassen3Klassen-Eigenschaften
Instanz- vs. statische EigenschaftenKonstanten in KlassenStatische Methoden & EigenschaftenPrivate & geschützte EigenschaftenSchreibgeschützte Eigenschaften (PHP 8.1)Zusammenfassung – Bankkonto-Manager6Polymorphie
Methodenüberschreibung erneut betrachtetPolymorphie über SchnittstellenTyp-Hinweise & Union-TypenSpätes statisches BindenRückblick – Zahlungsabwickler9Traits
Einführung in TraitsMehrere Traits verwendenAuflösung von Trait-KonfliktenAbstrakte Methoden in TraitsTraits vs. Vererbung12Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterObserver-MusterStrategy-Muster15Abschließende Herausforderungen
E-Learning-PlattformBankingsystemEntwicklung einer SpielfigurFahrzeugvermietungÜbe selbstständig: Online-PHP-Compiler