EMZETT.
Login

Komposition statt Vererbung: Objekte flexibel zusammenbauen

Kurz: Statt Verhalten über tiefe Vererbungsbäume zu teilen, setzt man Objekte aus kleinen Bausteinen zusammen; das ist flexibler, leichter zu testen und vermeidet starre Hierarchien.

Genauer: Vererbung drückt „ist ein“ aus (ein Hund ist ein Tier), Komposition „hat ein“ oder „nutzt ein“ (ein Auto hat einen Motor). Eine bekannte Faustregel des Entwurfs lautet: Komposition vor Vererbung bevorzugen.

Im Detail

Das Problem mit Vererbung

  • Starre Bindung: Die Unterklasse hängt von Interna der Oberklasse ab; Änderungen oben brechen unten (Fragile-Base-Class-Problem).
  • Explodierende Hierarchien: Nach Tier → Vogel → FliegenderVogel → NichtFliegenderVogel (Pinguin, Strauß) wird es unübersichtlich; Kombinationen (Schwimmen, Fliegen, Laufen) führen zu vielen Klassen.
  • Verletzte Erwartungen (Liskov): Ein Quadrat als Unterklasse von Rechteck bricht Annahmen.
  • Nur eine Oberklasse in vielen Sprachen (Java, C#).

Komposition im Beispiel

class Fliegen:
    def bewegen(self): return "flattert"
class Laufen:
    def bewegen(self): return "läuft"
 
class Vogel:
    def __init__(self, bewegung):
        self.bewegung = bewegung      # hat ein Verhalten
    def bewege_dich(self):
        return self.bewegung.bewegen()
 
spatz = Vogel(Fliegen())
pinguin = Vogel(Laufen())

Das Verhalten ist austauschbar (auch zur Laufzeit) und wird über Schnittstellen angesprochen. Das ist das Strategie-Muster; ähnlich arbeiten Dekorierer, Delegation und Dependency Injection.

Wann Vererbung passt

Wenn es wirklich eine „ist ein“-Beziehung gibt, die stabil ist, und die Unterklasse alle Eigenschaften der Oberklasse sinnvoll erfüllt (Liskov-Substitutionsprinzip). Auch Schnittstellen/Interfaces und Traits/Mixins bieten sichere Wiederverwendung. Rust und Go haben bewusst keine klassische Vererbung und setzen auf Komposition und Traits/Interfaces.

Praktische Regeln

  1. Frage: Ist das wirklich ein Spezialfall, oder will ich nur Code wiederverwenden? Für Letzteres Komposition.
  2. Kleine, fokussierte Klassen mit einer Aufgabe (Single Responsibility).
  3. Schnittstellen statt konkreter Klassen als Abhängigkeit.
  4. Hierarchien flach halten (maximal 2 bis 3 Ebenen).
  5. Tests werden einfacher, weil man Bausteine einzeln ersetzen (mocken) kann.

Siehe auch: Polymorphie, SOLID-Prinzipien, Strategie-Muster, Dependency Injection, Abstrakte Klasse oder Interface