Abstrakte Klassen und Schnittstellen

Neben den bislang betrachteten konkreten Methoden erlaubt PHP auch die Deklaration so genannter abstrakter Methoden. Im Gegensatz zu konkreten Methoden enthalten diese nur die Signatur (Name der Methode, Anzahl und Namen der Parameter). Die Implementierung einer abstrakten Methode erfolgt erst in einer abgeleiteten Klasse. Eine Klasse, die abstrakte Methoden enthält, wird auch abstrakte Klasse genannt. Von einer abstrakten Klasse können keine Objekte erzeugt werden.

Abstrakte Klassen werden benutzt, um eine Ist-eine-Art-von-Beziehung zu modellieren. So lässt sich beispielsweise eine Datenbanktreiber-Hierarchie abbilden, bei der eine gemeinsame Basisklasse die Signaturen für die Methoden der eigentlichen Treiberklassen vorgeben soll. Die Implementierung erfolgt dann gemäß den vorgegebenen Signaturen in den eigentlichen Treiberklassen.

Abstrakte Klassen und Methoden werden in PHP mit dem Schlüsselwort abstract deklariert. Wie das aussehen kann, zeigt Beispiel 1.21. In einem UML-Klassendiagramm werden der Name der Klasse sowie die Namen der abstrakten Methoden kursiv geschrieben.

Beispiel 1.21: Abstrakte Methoden

<?php
abstract class AbstrakteKlasse {
  public abstract function methode();
}
 
class ImplementierendeKlasse extends AbstrakteKlasse {
  public function methode() {
    print "ImplementierendeKlasse::methode() aufgerufen.\n";
  }
}
 
$objekt = new ImplementierendeKlasse;
$objekt->methode();
?>
ImplementierendeKlasse::methode() aufgerufen.


Eine Schnittstelle (englisch: interface) ist eine vollständig abstrakte Klasse, die nur Namen und die Signaturen der enthaltenen Methoden vereinbart. Eine Klasse kann eine beliebige Anzahl an Schnittstellen über das Schlüsselwort implements implementieren.

Implementiert eine Klasse mehrere Schnittstellen, so entspricht dies einer eingeschränkten Mehrfachvererbung. Da im Gegensatz zur klassenbasierten Mehrfachvererbung, wie sie beispielsweise C++ bietet, keine Funktionalität (sprich: kein Code) vererbt wird, kann es nicht zu den Vererbungseffekten kommen, für die die Mehrfachvererbung berüchtigt ist. Vererbung von Funktionalität kann stattdessen durch Aggregation und Delegation erreicht werden.

Mit Hilfe von Schnittstellen können unterschiedliche Rollen eines Objektes bzw. verschiedene Sichten auf ein Objekt modelliert werden. Zusammen mit den Konzepten von Klasse und Vererbung ermöglicht die Verwendung von Schnittstellen die Einordnung von Objekten in unterschiedliche Hierarchien: Abstammung (Klasse und Elternklasse(n)) auf der einen und Fähigkeiten (Schnittstellen) auf der anderen Seite. In einem guten Design wird ein Objekt auf Basis seiner Fähigkeiten (Schnittstellen) verwendet und nicht in Bezug auf seine Herkunft (Klasse, Elternklasse(n)). Die von einem Objekt implementierten Schnittstellen dienen hierbei als Garantie für eine zur Verfügung gestellte Fähigkeit, die für den aktuellen Verwendungskontext des Objektes minimal vorausgesetzt wird.

In dem folgenden Beispiel modellieren wir einen "Entsafter" sowie einen "Roller". Der Entsafter soll (über eine Methode entsaften()) Lebensmittel (Gemüse oder Obst) entsaften können, solange diese "entsaftbar" sind. Dieses Merkmal wird in den Lebensmittelklassen dadurch zum Ausdruck gebracht, dass die entsprechende Schnittstelle Entsaftbar implementiert wird. Der Roller soll (über eine Methode rollen() beliebige Objekte rollen können, solange diese "rollbar" sind, und entsprechend die Schnittstelle Rollbar implementieren.

Beispiel 1.22: Die Schnittstelle Entsaftbar

<?php
interface Entsaftbar {
  public function entsaften();
}
?>


Beispiel 1.23: Die Schnittstelle Rollbar

<?php
interface Rollbar {
  public function rollen();
}
?>


Die Klasse Tomate (Beispiel 1.24) implementiert beide Schnittstellen, Entsaftbar und Rollbar. Objekte dieser Klasse können daher sowohl mit dem Entsafter (Beispiel 1.26) als auch mit dem Roller (Beispiel 1.27) verwendet werden.

Beispiel 1.24: Die Klasse Tomate

<?php
require_once 'Entsaftbar.php';
require_once 'Rollbar.php';
 
abstract class Gemuese {
}
 
class Tomate extends Gemuese
implements Entsaftbar, Rollbar {
  public function entsaften() {
    return 'Tomatensaft';
  }
 
  public function rollen() {
    print 'Tomate gerollt.';
  }
}
?>


Beispiel 1.25: Die Klasse Banane

<?php
require_once 'Entsaftbar.php';
 
abstract class Obst {
}
 
class Banane extends Obst implements Entsaftbar {
  public function entsaften() {
    return 'Bananensaft';
  }
}
?>


Da die Methode entsaften() der Klasse Entsafter (Beispiel 1.26) als Parameter kein Objekt eines bestimmten Typs (beispielweise Lebensmittelgruppen wie Gemüse oder Obst) erwartet, sondern lediglich auf die von ihr benötigte Eigenschaft der "Entsaftbarkeit" testet, kann sie flexibel mit Objekten unterschiedlicher Vererbungshierarchien umgehen, wie Beispiel 1.28 zeigt.

Beispiel 1.26: Die Klasse Entsafter

<?php
require_once 'Entsaftbar.php';
 
class Entsafter {
  public function entsaften(Entsaftbar $lebensmittel) {
    $saft = $lebensmittel->entsaften();
 
    printf(
      "%s entsaftet: %s.\n",
 
      get_class($lebensmittel),
      $saft
    );
  }
}
?>


Beispiel 1.27: Die Klasse Roller

<?php
require_once 'Rollbar.php';
 
class Roller {
  public function rollen(Rollbar $objekt) {
    $objekt->rollen();
  }
}
?>


Beispiel 1.28: Entsaftbares entsaften, Rollbares rollen

<?php
require_once 'Entsafter.php';
require_once 'Roller.php';
 
require_once 'Banane.php';
require_once 'Tomate.php';
 
$entsafter = new Entsafter;
$entsafter->entsaften(new Banane);
$entsafter->entsaften(new Tomate);
 
$roller = new Roller;
$roller->rollen(new Tomate);
?>
Banane entsaftet: Bananensaft.
Tomate entsaftet: Tomatensaft.
Tomate gerollt.