Komposition vs. Vererbung
Teil des Abschnitts Objektorientierte Programmierung der Java-Journey von Coddy — Lektion 47 von 87.
Vererbung erzeugt eine „ist-ein“-Beziehung: ein Dog ist ein Animal. Aber was ist, wenn eine Klasse Funktionalität von etwas benötigt, das sie nicht ist? Hier kommt die Komposition ins Spiel, die stattdessen eine „hat-ein“-Beziehung herstellt.
Bei der Komposition enthält eine Klasse Instanzen anderer Klassen als Felder, anstatt sie zu erweitern:
// Vererbungsansatz: Auto IST ein Motor (macht keinen Sinn)
class Car extends Engine { }
// Kompositionsansatz: Auto HAT einen Motor (macht Sinn)
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.ignite();
}
}Komposition bietet mehrere Vorteile. Sie können das zusammengesetzte Objekt zur Laufzeit ändern, wie zum Beispiel das Austauschen von Motoren.
Sie können auch Verhaltensweisen aus mehreren Klassen kombinieren, was durch Vererbung in Java nicht möglich ist. Die zusammengesetzte Klasse legt nur die Methoden offen, die sie selbst bestimmt, was eine bessere Kapselung bietet.
Ein praktischer Leitfaden: Verwenden Sie Vererbung, wenn eine echte „is-a“-Beziehung besteht und die Unterklasse tatsächlich eine spezialisierte Version der Elternklasse darstellt.
Verwenden Sie Komposition, wenn Sie die Funktionalität einer anderen Klasse nutzen müssen, ohne dieser Typ zu sein. Viele erfahrene Entwickler folgen dem Prinzip: „Komposition gegenüber Vererbung bevorzugen“, da dies zu flexiblerem, wartbarerem Code führt.
Aufgabe
EinfachLassen Sie uns ein Computersystem mittels Komposition aufbauen, um die „hat-ein“-Beziehung (has-a) zu demonstrieren. Anstatt dass ein Computer von seinen Komponenten erbt (was keinen Sinn ergeben würde), wird Ihr Computer die Teile, die er zum Funktionieren benötigt, enthalten.
Sie werden Ihren Code über drei Dateien organisieren:
Processor.java: Erstellen Sie eine Klasse, die eine CPU repräsentiert. Ein Processor hat zwei private Felder:brand(String) undspeedGHz(double). Fügen Sie einen Konstruktor zur Initialisierung beider Felder und Getter-Methoden für jedes Feld hinzu. Fügen Sie eine Methodeprocess()hinzu, die einen String zurückgibt:[brand] processing at [speedGHz] GHzMemory.java: Erstellen Sie eine Klasse, die den Arbeitsspeicher (RAM) repräsentiert. Ein Memory hat zwei private Felder:type(String, wie "DDR4" oder "DDR5") undsizeGB(int). Fügen Sie einen Konstruktor, Getter und eine Methodeload()hinzu, die Folgendes zurückgibt:Loading [sizeGB]GB [type] memoryComputer.java: Hier glänzt die Komposition! Ihre Computer-Klasse sollte einen Processor und einen Memory als private Felder haben – sie erweitert diese nicht, sie enthält sie. Fügen Sie einen Konstruktor hinzu, der beide Komponenten akzeptiert. Fügen Sie eine Methodeboot()hinzu, die einen mehrzeiligen String zurückgibt, der den Startvorgang des Computers unter Verwendung seiner Komponenten zeigt:Fügen Sie außerdem eine MethodeBooting computer... [processor.process() result] [memory.load() result] System ready!getSpecs()hinzu, die Folgendes zurückgibt:Specs: [processor brand] CPU, [memory sizeGB]GB [memory type]Main.java: Erwecken Sie Ihr zusammengesetztes System zum Leben! Sie erhalten vier Eingaben: Prozessor-Marke (String), Prozessor-Geschwindigkeit (double), Speichertyp (String) und Speichergröße (int).Erstellen Sie einen Processor und einen Memory mit diesen Werten und setzen Sie diese dann zu einem Computer zusammen. Geben Sie das Ergebnis des Aufrufs von
boot()aus und geben Sie anschließend das Ergebnis vongetSpecs()aus.
Sie erhalten vier Eingaben in der folgenden Reihenfolge: Prozessor-Marke, Prozessor-Geschwindigkeit (GHz), Speichertyp und Speichergröße (GB).
Beachten Sie, wie der Computer Aufgaben an seine Komponenten delegiert, anstatt zu versuchen, diese selbst zu sein – das ist die Stärke der Komposition! Der Computer kann jeden Processor oder Memory verwenden, den Sie ihm geben, was das Design flexibel und realistisch macht.
Spickzettel
Komposition erzeugt eine „hat-ein“-Beziehung, bei der eine Klasse Instanzen anderer Klassen als Felder enthält, anstatt sie zu erweitern.
Vererbung vs. Komposition:
// Vererbung: Car IST ein Engine (macht keinen Sinn)
class Car extends Engine { }
// Komposition: Car HAT einen Engine (macht Sinn)
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.ignite();
}
}Vorteile der Komposition:
- Objekte können zur Laufzeit geändert werden
- Kann Verhaltensweisen von mehreren Klassen kombinieren
- Bessere Kapselung – nur ausgewählte Methoden werden offengelegt
- Flexiblerer und wartungsfreundlicherer Code
Wann man was verwendet:
- Vererbung: Verwenden Sie diese, wenn eine echte „ist-ein“-Beziehung besteht und die Unterklasse eine spezialisierte Version der Elternklasse ist
- Komposition: Verwenden Sie diese, wenn Sie die Funktionalität einer anderen Klasse benötigen, ohne von diesem Typ zu sein
Best Practice: „Bevorzugen Sie Komposition gegenüber Vererbung“
Probier es selbst
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Eingaben lesen
String processorBrand = scanner.nextLine();
double processorSpeed = Double.parseDouble(scanner.nextLine());
String memoryType = scanner.nextLine();
int memorySize = Integer.parseInt(scanner.nextLine());
// TODO: Erstelle einen Processor mit der Marke und Geschwindigkeit
// TODO: Erstelle einen Memory mit dem Typ und der Größe
// TODO: Erstelle einen Computer durch Zusammensetzen von Processor und Memory
// TODO: Gib das Ergebnis von boot() aus
// TODO: Gib das Ergebnis von getSpecs() aus
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienEinführung in die OOPKlassen vs. ObjekteDas this-SchlüsselwortMethodenFelder (Attribute)Konstruktor-MethodeKonstruktor-ÜberladungRückblick – Einfacher Taschenrechner4Vererbung
Grundlagen der Vererbung (extends)Das super-SchlüsselwortMethoden überschreiben (@Override)Konstruktor-VerkettungDie Object-KlasseEinfache & mehrstufige VererbungWarum keine Mehrfachvererbung von KlassenZusammenfassung - Mitarbeiter-Hierarchie7Spezielle Methoden & Object-Klasse
toString()-Methodeequals() und hashCode()clone()-MethodecompareTo() und ComparableComparator-InterfaceZusammenfassung – Benutzerdefiniertes Sortieren2Zugriffsmodifikatoren & Kapselung
Überblick über ZugriffsebenenGetter- und Setter-MethodenInformation HidingDas final-SchlüsselwortRückblick – Bankkonto-Manager5Polymorphismus
Grundlagen der MethodenüberladungMethodenüberschreibung (Laufzeit)Upcasting und DowncastingDer instanceof-OperatorAbstrakte Klassen und MethodenZusammenfassung – Formenrechner8Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungAggregation vs. KompositionInnere, geschachtelte & anonyme KlassenEnums und Enum-MethodenRecords (Java 16+)Sealed Classes (Java 17+)11Design Patterns Teil 1
Einführung in Design PatternsSingleton-PatternFactory-PatternBuilder-PatternObserver-PatternStrategy-Pattern14Abschlussherausforderungen
E-Learning-PlattformBanksystemSpielcharakter-EntwicklungFahrzeugvermietung3Klasseneigenschaften & Statische Member
Instanz- vs. statische VariablenStatische MethodenStatische BlöckeKonstanten (static final)Zusammenfassung – Zähler & Utility6Interfaces & Abstrakte Klassen
Einführung in InterfacesInterfaces implementierenImplementierung mehrerer InterfacesDefault- & statische Methoden in InterfacesAbstrakte Klassen vs. InterfacesFunktionale InterfacesZusammenfassung - Zahlungssystem