Komposition oder Vererbung: Flexibilität und Code-Wiederverwendung im Vergleich
Die Konzepte der Komposition und Vererbung sind zentrale Bausteine der objektorientierten Programmierung. Beide verfolgen das Ziel der Code-Wiederverwendbarkeit, unterscheiden sich jedoch grundlegend in ihrer Implementierung und Anwendung. Im Folgenden werden diese beiden Ansätze erläutert und gegenübergestellt, um Ihnen dabei zu helfen, zu entscheiden, wann welcher Ansatz sinnvoll ist.
Komposition: „Hat-ein“-Beziehung
Komposition ermöglicht es Ihnen, eine „hat-ein“-Beziehung zwischen Objekten herzustellen. Dies wird erreicht, indem ein Objekt als Instanzvariable in einem anderen Objekt verwendet wird. Zum Beispiel kann eine Person ein Job-Objekt haben, wie das folgende Java-Beispiel zeigt:
public class Job {
// Attribute und Methoden für den Job
}
public class Person {
// Komposition: "Hat-ein"-Beziehung
private Job job;
// Konstruktoren, Methoden etc.
}
In diesem Beispiel hat die Klasse Person
eine Instanz der Klasse Job
, was die Komposition zwischen den beiden Objekten verdeutlicht. Diese Herangehensweise erlaubt es Ihnen, Objekte unabhängig voneinander zu ändern oder zu erweitern, ohne die Struktur der anderen Klasse direkt zu beeinflussen.
Vererbung: „Ist-ein“-Beziehung
Im Gegensatz dazu bildet die Vererbung eine „ist-ein“-Beziehung ab. Sie ermöglicht es einer Klasse, die Eigenschaften und Methoden einer übergeordneten Klasse zu erben. Dies wird durch das Schlüsselwort extends
in Java realisiert. Ein klassisches Beispiel wäre die Vererbung einer Animal
-Klasse durch eine Cat
-Klasse:
public class Animal {
// Attribute und Methoden des Tieres
}
public class Cat extends Animal {
// Attribute und Methoden der Katze
}
Hier erbt die Klasse Cat
alle Attribute und Methoden der Klasse Animal
. Vererbung bietet sich dann an, wenn die Unterklasse eine spezifische Erweiterung der Oberklasse darstellt und alle Eigenschaften der Oberklasse benötigt.
Komposition gegenüber Vererbung
Während beide Ansätze die Wiederverwendbarkeit von Code fördern, bietet die Komposition eine größere Flexibilität und Modularität, da die Beziehungen zwischen Objekten lockerer geknüpft sind. Vererbung hingegen schafft eine engere Kopplung, die zu Problemen führen kann, wenn sich die Oberklasse ändert.
Beispiel: Vererbung kann zu Problemen führen
Sehen wir uns folgendes Beispiel der Vererbung an:
public class ClassA {
public void foo() {}
}
public class ClassB extends ClassA {
public void bar() {}
}
Wenn nun die Oberklasse ClassA
erweitert wird und eine neue Methode bar()
hinzufügt:
public class ClassA {
public void foo() {}
public int bar() {
return 0;
}
}
führt dies zu einem Kompilierungsfehler, da die Methode bar()
in der Klasse ClassB
einen anderen Rückgabewert hat. Dieser Fehler könnte vermieden werden, wenn stattdessen Komposition verwendet würde:
public class ClassB {
private ClassA classA = new ClassA();
public void bar() {
classA.foo();
classA.bar();
}
}
In diesem Fall bleibt die Implementierung der Klasse ClassB
unabhängig von Änderungen in der Klasse ClassA
, was die Wartbarkeit und Flexibilität des Codes erhöht.
Vorteile der Komposition
Ein weiterer Vorteil der Komposition ist die Kontrolle über den Zugriff auf Methoden. In der Vererbung sind alle Methoden der Oberklasse auch für die Unterklasse und deren Benutzer zugänglich, was potenzielle Sicherheitsrisiken bergen kann. Mit Komposition können Sie jedoch gezielt entscheiden, welche Methoden für externe Klassen zugänglich sind:
public class ClassB {
private ClassA classA = new ClassA();
public void foo() {
classA.foo();
}
}
Hier hat ClassB
nur die Methode foo()
der Klasse ClassA
freigegeben und nicht alle anderen Methoden. Dies bietet mehr Sicherheit und Flexibilität.
Komplexe Szenarien: Komposition bietet mehr Flexibilität
In komplexeren Szenarien mit mehreren Unterklassen ermöglicht die Komposition auch eine flexiblere Methode zur Methode-Invocation, ohne dass für jede Unterklasse eine Instanz erstellt werden muss. Ein Beispiel:
abstract class Abs {
abstract void foo();
}
public class ClassA extends Abs {
public void foo() {}
}
public class ClassB extends Abs {
public void foo() {}
}
public class Test {
private Abs obj;
public Test(Abs o) {
this.obj = o;
}
public void foo() {
this.obj.foo();
}
}
Durch die Verwendung eines abstrakten Typs und der Komposition kann der Testklasse jede Unterklasse übergeben werden, was den Code modularer und anpassungsfähiger macht.
Komposition erleichtert das Testen
Ein letzter wichtiger Punkt ist das Testen. Komposition macht Unit-Tests einfacher, da Sie genau wissen, welche Methoden von anderen Klassen verwendet werden. Diese Methoden können gezielt isoliert und getestet werden. Bei der Vererbung hingegen müssen Sie möglicherweise alle Methoden der Oberklasse testen, was zu einem höheren Testaufwand führt.
Fazit: Wann sollten Sie Komposition über Vererbung wählen?
Die Entscheidung zwischen Komposition und Vererbung hängt von der spezifischen Situation ab. Vererbung ist dann nützlich, wenn Sie sicher sind, dass sich die Oberklasse nicht ändert und alle Unterklassen die gleichen Eigenschaften benötigen. Komposition bietet hingegen mehr Flexibilität und ist weniger anfällig für Fehler, insbesondere bei größeren oder dynamischen Projekten. Verwenden Sie Komposition, wenn Sie eine lose Kopplung und mehr Kontrolle über den Code möchten.
Sie haben nun einen Überblick über die Unterschiede zwischen Komposition und Vererbung sowie einige Gründe, warum in vielen Fällen die Komposition zu bevorzugen ist.