Skip to content

Interfaces on Abstracts?

Published: at 10:00

🎉 Als PHP mit Version 5 Interfaces und abstrakte Klassen einführte, war ich aus dem Häuschen!

🤔 Wie alt kann man sich beim Bloggen eigentlich fühlen? 😅

Heutzutage denkt man kaum noch darüber nach: Als PHP abstrakte Klassen einführte, war das für mich ein echter Meilenstein. Endlich konnte ich Teile eines Interfaces in eine abstrakte Klasse auslagern. Das bedeutete: Gemeinsame Logik zentralisieren und die konkreten Klassen nur noch das implementieren lassen, was wirklich nötig war. 🧹✨

Das führte zu einem massiven Refactoring – viele redundante Implementierungen konnten einfach verschwinden. 🪄

🐢🐦 Von der Tierklasse zur eleganten Abstraktion

Vorher sah mein Code etwa so aus:

class Animal  {
    public function canFly() {
        return false;
    } 
    
    public function isCarnivore() {
        return false;
    }
    
    public function isFlyingMeatEater() {
        return $this->canFly() && $this->isCarnivore();
    }
} 

class TurtleDove extends Animal {
    public function canFly() {
        return true;
    } 
    
    public function isCarnivore() {
        return true;
    }    
} 

Nach dem Refactoring wurde daraus:

interface Animal {
    public function canFly();
   
    public function isCarnivore();
    
    public function isFlyingMeatEater();
}

abstract class AbstractAnimal implements Animal {
    public function isFlyingMeatEater() {
        return $this->canFly() && $this->isCarnivore();
    }
} 

class TurtleDove extends AbstractAnimal {
    // Implementiere nur den Rest!
} 

“Leider” ist das mittlerweile so normal, dass die PHP-Dokumentation dieses Thema noch nicht mal mehr wirklich aktiv anzeigt, außer in Beispiel 7 der Interfaces 😉

🤔 Also, warum sollte man Interfaces auf abstrakte Klassen anwenden?

1️⃣ Der erste Grund ist eine bekannte und grundlegende Heuristik: DRY (Don’t Repeat Yourself).

Duplication is the primary enemy of a well-designed system. It represents additional work, additional risk, and additional unnecessary complexity.

- C., Martin Robert. Clean Code: A Handbook of Agile Software Craftsmanship (Robert C. Martin Series) (English Edition) (p. 366). (Function). Kindle Edition.

Onkel Bob geht sogar so weit, es als die Wurzel allen Übels zu bezeichnen. Und ich kenne viele Entwickler, die dies als eines der größten Probleme in Teams ansehen.

Duplication may be the root of all evil in software.

- C., Martin Robert. Clean Code: A Handbook of Agile Software Craftsmanship (Robert C. Martin Series) (English Edition) (p. 136). (Function). Kindle Edition.

Es lohnt sich also, auch nur ein paar Zeilen Code zu refaktorisieren.

even […] just a few lines of code.

- C., Martin Robert. Clean Code: A Handbook of Agile Software Craftsmanship (Robert C. Martin Series) (English Edition) (p. 366). (Function). Kindle Edition.

2️⃣ Der zweite Grund ist das Liskovsche Substitutionsprinzip (LSP). Klassisch und prominent wird bei der Betrachtung des LSP eine Version genutzt, die auch Wikipedia verwendet:

an object (such as a class) may be replaced by a sub-object (such as a class that extends the first class) without breaking the program.

- https://en.wikipedia.org/wiki/Liskov_substitution_principle

Dabei wird jedoch die Quelle unterschlagen; Ausgangspunkt für dieses Gesetzt ist immer noch die Basisklasse. Und damit gilt das Prinzip umgekehrt genauso. Oder wie es Onkel Bob formuliert:

SUBTYPES MUST BE SUBSTITUTABLE FOR THEIR BASE TYPES.

- Martin, Robert C. Agile Software Development, Principles, Patterns, and Practices: Pearson New International Edition (English Edition) (p. 111). (Function). Kindle Edition.

3️⃣ Und der Dritte Grund: Die Kombination einer abstrakten Klasse mit einem Interface erzwingt das TEMPLATE METHOD Pattern auf natürliche Weise.

Und die Entwicklung gegen ein Interface statt gegen eine Basisklasse verschafft maximale Flexibilität und sorgt für die Einhaltung des Dependency Inversion Principle, dem “D” in SOLID.

💡 Mein Vorschlag

Mein Vorschlag wäre also: Wenn es sich um ein allgemeines Interface für Kindklassen mit geteilter Logik handelt, dann sollte das Interface am besten auf eine Basisklasse angewendet werden, die die Basislogik zur Verfügung stellt.

Möchte man aber anzeigen, dass die konkrete Klasse das jeweilige Interface aus speziellen Gründen im Gegensatz zu ihrer Elternklasse umsetzt, nur dann würde ich das Interface nicht auf die Basisklasse setzen.

interface FlyingRat {
    public function doesCareWhatItEats();
}

class TurtleDove extends AbstractAnimal implements FlyingRat {
    // Implementiere nur den Rest und das andere Interface!
} 

Next Post
Claude Code, der sehr schnelle Junior im Team