Interface vs. Abstrakte Klasse
Teil des Abschnitts Objektorientierte Programmierung der PHP-Journey von Coddy — Lektion 30 von 91.
Nachdem Sie nun sowohl Interfaces als auch abstrakte Klassen kennengelernt haben, wollen wir klären, wann welches zu verwenden ist. Obwohl sie sich ähnlich sehen mögen, dienen sie unterschiedlichen Zwecken und haben unterschiedliche Fähigkeiten.
| Merkmal | Interface | Abstrakte Klasse |
|---|---|---|
| Methoden | Nur Methodensignaturen | Sowohl abstrakte als auch konkrete Methoden |
| Eigenschaften | Nur Konstanten | Kann Eigenschaften haben |
| Mehrfachvererbung | Eine Klasse kann mehrere implementieren | Eine Klasse kann nur eine erweitern |
| Konstruktor | Nicht erlaubt | Kann einen Konstruktor haben |
Verwenden Sie ein Interface, wenn Sie einen Vertrag definieren möchten, den nicht verwandte Klassen erfüllen können. Beispielsweise könnten sowohl ein Printer als auch ein Logger ein Writable-Interface implementieren, obwohl sie keinen gemeinsamen Vorfahren haben.
Verwenden Sie eine abstrakte Klasse, wenn Sie verwandte Klassen haben, die gemeinsamen Code teilen. Wenn Ihre Dog- und Cat-Klassen beide dieselbe eat()-Methodenimplementierung benötigen, ermöglicht es Ihnen eine abstrakte Animal-Klasse, diesen Code einmal zu schreiben.
Sie können auch beide Ansätze kombinieren. Eine Klasse kann eine abstrakte Klasse erweitern und gleichzeitig mehrere Schnittstellen implementieren:
<?php
abstract class Animal {
protected $name;
public function eat() {
return $this->name . " is eating";
}
}
interface Swimmable {
public function swim();
}
class Duck extends Animal implements Swimmable {
public function __construct($name) {
$this->name = $name;
}
public function swim() {
return $this->name . " is swimming";
}
}
Wichtiger Punkt: Wählen Sie Interfaces, um Funktionen über nicht verwandte Klassen hinweg zu definieren. Wählen Sie abstrakte Klassen, wenn Sie Code zwischen verwandten Klassen teilen müssen.
Aufgabe
EinfachLassen Sie uns ein Fahrzeugsystem bauen, das demonstriert, wann eine abstrakte Klasse gegenüber einem Interface verwendet werden sollte — und wie man beide Ansätze effektiv kombiniert.
Sie werden ein System erstellen, in dem Fahrzeuge gemeinsame Eigenschaften durch Vererbung teilen, aber auch zusätzliche Fähigkeiten durch Interfaces erhalten können. Dies spiegelt reale Design-Entscheidungen wider: Ein Auto "ist ein" Fahrzeug (Vererbung), aber es "hat auch die Fähigkeit", betankbar zu sein (Interface).
Organisieren Sie Ihren Code über vier Dateien:
Refuelable.php— Definieren Sie einRefuelableInterface mit einer einzigen Methodensignatur:refuel($amount). Diese Fähigkeit kann auf jede Klasse angewendet werden, die betankt werden muss, unabhängig von ihrer Vererbungshierarchie.Vehicle.php— Definieren Sie eine abstrakteVehicleKlasse, die gemeinsame Funktionalität für alle Fahrzeuge bereitstellt. Sie sollte eine geschützte$brandEigenschaft und eine geschützte$fuelLevelEigenschaft (beginnend bei 0) haben. Der Konstruktor akzeptiert den Markennamen. Fügen Sie eine konkretegetBrand()Methode hinzu, die die Marke zurückgibt, eine konkretegetFuelLevel()Methode, die den aktuellen Kraftstoffstand zurückgibt, und eine abstraktedrive()Methode, die Kindklassen implementieren müssen.Car.php— Erstellen Sie eineCarKlasse, dieVehicleerweitert undRefuelableimplementiert. Binden Sie beide erforderlichen Dateien oben ein. Der Konstruktor akzeptiert die Marke und übergibt sie an den Eltern-Konstruktor. Implementieren Siedrive(), um"[brand] car is driving"zurückzugeben. Implementieren Sierefuel($amount), um den Betrag zum Kraftstoffstand zu addieren und"[brand] refueled to [fuelLevel] liters"zurückzugeben.main.php— Binden Sie die Car-Datei ein. Sie erhalten zwei Eingaben: einen Markennamen und eine Kraftstoffmenge (in einen Integer umwandeln). Erstellen Sie einCarmit der Marke. Geben Sie das Ergebnis vondrive()in der ersten Zeile aus und geben Sie dann das Ergebnis vonrefuel()mit der Kraftstoffmenge in der zweiten Zeile aus.
Diese Herausforderung veranschaulicht den entscheidenden Unterschied: Die abstrakte Vehicle Klasse teilt Code und stellt eine "ist-ein"-Beziehung her, während das Refuelable Interface eine Fähigkeit hinzufügt, die auf andere, nicht verwandte Klassen (wie einen Generator oder ein Boot) angewendet werden könnte, ohne dass diese Fahrzeuge sein müssen.
Spickzettel
Interfaces und abstrakte Klassen dienen unterschiedlichen Zwecken:
| Merkmal | Interface | Abstrakte Klasse |
|---|---|---|
| Methoden | Nur Methodensignaturen | Sowohl abstrakte als auch konkrete Methoden |
| Eigenschaften | Nur Konstanten | Kann Eigenschaften haben |
| Mehrfachvererbung | Eine Klasse kann viele implementieren | Eine Klasse kann nur eine erweitern |
| Konstruktor | Nicht erlaubt | Kann einen Konstruktor haben |
Verwenden Sie ein Interface, um einen Vertrag für nicht verwandte Klassen zu definieren. Verwenden Sie eine abstrakte Klasse, um gemeinsamen Code zwischen verwandten Klassen zu teilen.
Eine Klasse kann eine abstrakte Klasse erweitern und gleichzeitig mehrere Interfaces implementieren:
<?php
abstract class Animal {
protected $name;
public function eat() {
return $this->name . " is eating";
}
}
interface Swimmable {
public function swim();
}
class Duck extends Animal implements Swimmable {
public function __construct($name) {
$this->name = $name;
}
public function swim() {
return $this->name . " is swimming";
}
}
Probier es selbst
<?php
require_once 'Car.php';
// Eingabe lesen
$brand = trim(fgets(STDIN));
$fuelAmount = intval(trim(fgets(STDIN)));
// TODO: Erstelle ein Auto mit der Marke
// TODO: Gib das Ergebnis von drive() in der ersten Zeile aus
// TODO: Gib das Ergebnis von refuel() mit der Kraftstoffmenge in der zweiten Zeile 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