Wähle das richtige Design-Werkzeug - einen Vertrag, ein Algorithmus-Gerüst oder horizontale Wiederverwendung - indem du die Kompromisse jedes Ansatzes kennst.
Diese Lektion in Kodokon öffnenPHP 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
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']]);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
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');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
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');log(). Was passiert?insteadof aufgelöst wird