SOLID-Prinzipien
SOLID sind fünf Entwurfsprinzipien für objektorientierten Code. Sie sind keine Gesetze, sondern Fragen, die du an dein Design stellst, bevor es zu groß wird.
Ansicht
01
Verstehen
Lies die Kernidee und das Beispiel.
02
Prüfen
Beantworte die Aktiv-Checks.
03
Anwenden
Setze die Transferaufgabe um.
Mini-Erfolg
Du hast eine Klasse in zwei Verantwortlichkeiten geteilt und eine Abhängigkeit gegen ein Interface gedreht.
Warum das wichtig ist: SOLID klingt nach Theorie, beantwortet aber jeden Tag die Frage: Warum ist diese Änderung plötzlich so teuer?
Das darf schwer sein
Das ist ein Fortgeschrittenen-Thema. Lies es einmal in Ruhe, baue das Mini-Projekt und komm später zurück. Du musst es nicht sofort können.
Vorher hilfreich
Lerninhalt
Verstehen & anwenden
Eine Aufgabe pro Klasse (SRP)
Das Single-Responsibility-Prinzip sagt: Eine Klasse sollte genau einen Grund haben, sich zu ändern.
Berechnen, speichern und formatieren sind drei Aufgaben - und damit drei mögliche Änderungsgründe.
Aufteilen heißt nicht, jede Zeile in eine eigene Klasse zu schieben. Es heißt, Verantwortlichkeiten sichtbar zu trennen.
record Rechnung(double betrag) {}
class RechnungDrucker {
String formatiere(Rechnung rechnung) {
return String.format("Rechnung: %.2f EUR", rechnung.betrag());
}
}
// Die Rechnung kennt nur ihre Daten,
// der Drucker kennt nur die Darstellung.Offen für Erweiterung (OCP)
Das Open-Closed-Prinzip meint: Neues Verhalten soll ohne Änderung am bestehenden Code möglich sein.
Ein Interface plus eine neue Implementierung erreicht das oft besser als eine wachsende if-Kette.
Wichtig ist die Richtung: Der bestehende Aufrufer bleibt stabil, die Erweiterung kommt von außen dazu.
interface Rabatt {
double berechne(double summe);
}
class TreueRabatt implements Rabatt {
public double berechne(double summe) {
return summe * 0.90;
}
}
class Checkout {
private final Rabatt rabatt;
Checkout(Rabatt rabatt) {
this.rabatt = rabatt;
}
double gesamt(double summe) {
return rabatt.berechne(summe);
}
}Verträge einhalten (LSP, ISP, DIP)
LSP: Eine Unterklasse muss überall funktionieren, wo die Oberklasse erwartet wird.
ISP: Lieber mehrere kleine Interfaces als ein riesiges. DIP: Abhängigkeiten zeigen auf Interfaces, nicht auf konkrete Klassen.
Dependency Injection ist die praktische Folge von DIP: Das Objekt bekommt seine Abhängigkeit von außen, statt sie selbst mit new zu erzeugen. Dadurch lässt sie sich austauschen und testen.
interface Benachrichtigung {
void sende(String text);
}
class Bestellung {
private final Benachrichtigung kanal;
Bestellung(Benachrichtigung kanal) {
this.kanal = kanal;
}
void abschliessen() {
kanal.sende("Bestellung abgeschlossen");
}
}Vertiefung
Prinzipien als Fragen
SOLID ist kein Checklisten-Formalismus. SRP fragt: Wie viele Gründe gibt es, diese Klasse zu ändern? OCP fragt: Muss ich für eine neue Variante bestehenden Code anfassen? DIP fragt: Hängt mein Kern an einem Detail, das ich austauschen will?
Die Prinzipien können sich widersprechen. Mehr Interfaces bedeuten mehr Flexibilität, aber auch mehr Indirektion. Gute Entscheidungen entstehen aus dem konkreten Änderungsrisiko, nicht aus dem Wunsch, alle fünf Prinzipien gleichzeitig zu erfüllen.
// Vorher: eine Klasse mit drei Änderungsgründen
class Kundenbericht {
void berechne() { /* ... */ }
void speichere() { /* ... */ }
void drucke() { /* ... */ }
}
// Nachher: drei Klassen, drei klare Aufgaben
class BerichtRechner { /* ... */ }
class BerichtRepository { /* ... */ }
class BerichtDrucker { /* ... */ }Merksatz: SOLID hilft, Änderungen zu lokalisieren. Wende ein Prinzip dort an, wo eine Änderung sonst mehrere Stellen gleichzeitig trifft.
Übungsaufgabe
Rabattregeln entkoppeln
Baue einen Checkout, der die Rabattberechnung über ein Interface bezieht. Ergänze zwei Rabattarten und danach eine dritte, ohne den Checkout zu ändern.
Das soll dein Programm können
- Interface Rabatt mit berechne(double)
- Implementierungen StandardRabatt und TreueRabatt
- Checkout erhält den Rabatt per Konstruktor
- Neue Rabattart ohne Änderung an Checkout
Schreibe den Code hier oder in deiner IDE und vergleiche ihn dann mit der Lösung.
Dein Code wird automatisch auf diesem Gerät gespeichert.
Hinweis anzeigen
Definiere Rabatt mit einer Methode, implementiere zwei Klassen und übergib eine Instanz an den Checkout. Für die Ausgabe reicht System.out.printf.
Lösung anzeigen
interface Rabatt {
double berechne(double summe);
}
class StandardRabatt implements Rabatt {
public double berechne(double summe) {
return summe;
}
}
class TreueRabatt implements Rabatt {
public double berechne(double summe) {
return summe * 0.9;
}
}
class Checkout {
private final Rabatt rabatt;
Checkout(Rabatt rabatt) {
this.rabatt = rabatt;
}
double gesamt(double summe) {
return rabatt.berechne(summe);
}
public static void main(String[] args) {
Checkout checkout = new Checkout(new TreueRabatt());
System.out.printf("Zu zahlen: %.2f EUR%n", checkout.gesamt(100.0));
}
}Warum funktioniert das?
Der Checkout kennt nur das Interface. Eine neue Rabattart ist eine neue Klasse, kein Eingriff in bestehenden Code. Genau das ist das Open-Closed-Prinzip in der Praxis.
Aktiv-Check
Teste dein Verständnis
Erkennen: Welcher Abschnitt passt?
Welcher Abschnitt passt zu diesen Begriffen?
Spaßprojekt
Rabattregeln entkoppeln
Baue einen Checkout, der Rabatte über ein Interface bezieht. Ergänze danach einen neuen Rabatt, ohne den Checkout zu ändern.
Selbstcheck
Kann ich das schon?
Hake ab, was du wirklich erklären oder selbst bauen könntest.
Fortschritt
Bereit zum Abschließen?
Markiere die Lektion als abgeschlossen, um XP und Streak zu erhalten.