Kodokon kodokon.com

Vererbung, Interfaces, Traits und abstrakte Klassen

Wähle das richtige Design-Werkzeug - einen Vertrag, ein Algorithmus-Gerüst oder horizontale Wiederverwendung - indem du die Kompromisse jedes Ansatzes kennst.

9 Min. · 3 Fragen

Diese Lektion in Kodokon öffnen

PHP gibt dir vier Design-Werkzeuge an die Hand, die sich teilweise überschneiden: konkrete Vererbung, abstrakte Klassen, Interfaces und Traits. Die Wahl läuft auf eine einzige Frage hinaus: Was willst du teilen? Einen Vertrag: Interface. Einen Vertrag plus ein Implementierungs-Gerüst: abstrakte Klasse. Code, der zwischen Klassen ohne hierarchische Verbindung geteilt wird: Trait. Die konkrete Vererbung ihrerseits lässt sich immer schwerer rechtfertigen: Sie koppelt stark, und PHP erlaubt nur eine einzige Elternklasse.

PHP
<?php

declare(strict_types=1);

abstract class Exporter
{
    public function export(array $rows): string
    {
        return $this->head() . $this->body($rows);
    }

    abstract protected function head(): string;

    abstract protected function body(
        array $rows
    ): string;
}

final class CsvExporter extends Exporter
{
    protected function head(): string
    {
        return "id;name\n";
    }

    protected function body(array $rows): string
    {
        $lines = array_map(
            fn (array $r): string => implode(';', $r),
            $rows,
        );

        return implode("\n", $lines);
    }
}

$csv = new CsvExporter();
echo $csv->export([[1, 'Ada'], [2, 'Linus']]);
Template Method: Die Elternklasse legt den Algorithmus fest, die Kinder liefern die Details.

Das Interface ist der reine Vertrag: Signaturen und Konstanten, kein Zustand und keine Implementierung. Sein entscheidender Vorteil gegenüber der abstrakten Klasse: Eine Klasse kann mehrere davon implementieren, und jede bestehende Klasse kann den Vertrag nachträglich übernehmen. Die abstrakte Klasse trägt dagegen Zustand und gemeinsamen Code, verbraucht aber deinen einzigen Vererbungsplatz. In der Praxis: Typisiere deine Parameter mit Interfaces (Cache, nicht ArrayCache), damit du Implementierungen austauschen kannst, in Tests genauso wie in Produktion.

PHP
<?php

declare(strict_types=1);

interface Cache
{
    public function get(string $key): ?string;

    public function set(
        string $key,
        string $value
    ): void;
}

final class ArrayCache implements Cache
{
    private array $items = [];

    public function get(string $key): ?string
    {
        return $this->items[$key] ?? null;
    }

    public function set(
        string $key,
        string $value
    ): void {
        $this->items[$key] = $value;
    }
}

$cache = new ArrayCache();
$cache->set('lang', 'PHP');
echo $cache->get('lang');
Der Client-Code hängt von Cache ab: Die Implementierung bleibt austauschbar.

Traits lösen das horizontale Teilen: dieselben Methoden in Klassen einschleusen, die keinen gemeinsamen Vorfahren haben. Wenn zwei Traits eine Methode mit demselben Namen bereitstellen, weigert sich PHP, für dich zu entscheiden, und verlangt eine explizite Auflösung: insteadof bestimmt die Version, die du behältst, as erstellt einen Alias, um die andere zugänglich zu halten. Das ist bewusst ausführlich - der Konflikt muss im Code sichtbar bleiben.

PHP
<?php

declare(strict_types=1);

trait FileLogger
{
    public function log(string $message): void
    {
        echo "[file] $message\n";
    }
}

trait AuditLogger
{
    public function log(string $message): void
    {
        echo "[audit] $message\n";
    }
}

final class PaymentService
{
    use FileLogger, AuditLogger {
        FileLogger::log insteadof AuditLogger;
        AuditLogger::log as audit;
    }
}

$service = new PaymentService();
$service->log('Payment received');
$service->audit('Payment received');
Trait-Konflikt: insteadof entscheidet, as behält einen Alias.

Wissenscheck

Stelle sicher, dass du die wichtigsten Punkte dieser Lektion behalten hast.

  1. Welchen entscheidenden Vorteil hat das Interface gegenüber der abstrakten Klasse?
    • Es kann gemeinsame Eigenschaften halten
    • Eine Klasse kann mehrere Interfaces implementieren, aber nur von einer einzigen Klasse erben
    • Seine Methoden laufen schneller
  2. Zwei Traits, die von derselben Klasse genutzt werden, deklarieren jeweils eine Methode log(). Was passiert?
    • PHP nutzt die Methode des zuerst aufgeführten Traits
    • Fataler Fehler, sofern er nicht explizit mit insteadof aufgelöst wird
    • PHP führt die beiden Methoden zu einer einzigen zusammen
  3. Was kann eine abstrakte Klasse enthalten, ein Interface aber nicht?
    • Konstanten
    • Eigenschaften und implementierte Methoden
    • Signaturen öffentlicher Methoden