SOLID Prinzipien in Java: Von der Theorie zu sauberem Code
Jeder Java-Entwickler stößt früher oder später auf das Problem von "schmutzigem" Code: Klassen, die alles auf einmal erledigen, starre Abhängigkeiten und die Unmöglichkeit der Erweiterung, ohne die bestehende Logik zu beschädigen. Genau hier kommen die SOLID Prinzipien ins Spiel – fünf fundamentale Regeln des objektorientierten Designs, formuliert von Robert C. Martin (Uncle Bob). In diesem Artikel analysieren wir jedes Prinzip in der Praxis mit Beispielen in Java, damit Ihr Code flexibel, testbar und leicht wartbar wird.
1. Single Responsibility Principle (SRP) – Prinzip der einzigen Verantwortung
Kernaussage: "Eine Klasse sollte nur einen Grund für eine Änderung haben." Mit anderen Worten, eine Klasse sollte für eine klar definierte Aufgabe verantwortlich sein.
Verletzung des SRP
Betrachten wir die Klasse OrderService, die sich gleichzeitig mit der Preisberechnung, dem Speichern in der Datenbank und dem Senden von E-Mails beschäftigt:
public class OrderService { public void processOrder(Order order) { // 1. Kostenberechnung double total = order.getItems().stream() .mapToDouble(Item::getPrice) .sum();
// 2. Speichern in der DB Database.save(order);
// 3. Senden einer Benachrichtigung EmailService.send("Bestellung aufgegeben über Betrag: " + total); }}Problem: Eine Änderung der Logik zum Senden von E-Mails oder der Speichermethode würde eine Änderung von OrderService erzwingen. Dies verletzt das SRP.
Refactoring nach SRP
Wir teilen die Verantwortlichkeiten auf drei separate Klassen auf:
public class PriceCalculator { public double calculateTotal(Order order) { return order.getItems().stream() .mapToDouble(Item::getPrice) .sum(); }}
public class OrderRepository { public void save(Order order) { Database.save(order); }}
public class NotificationService { public void sendConfirmation(Order order) { EmailService.send("Bestellung aufgegeben über Betrag: " + order.getTotal()); }}
// Client-Codepublic class OrderProcessor { private PriceCalculator calculator; private OrderRepository repository; private NotificationService notifier;
public void processOrder(Order order) { double total = calculator.calculateTotal(order); order.setTotal(total); repository.save(order); notifier.sendConfirmation(order); }}Ergebnis: Jede Klasse hat einen einzigen Grund für eine Änderung. Der Code wird modular und testbar.
2. Open-Closed Principle (OCP) – Prinzip der Offenheit/Abgeschlossenheit
Kernaussage: "Softwareentitäten sollten offen für Erweiterung, aber geschlossen für Modifikation sein." Neue Funktionalität wird durch Erweiterung hinzugefügt, nicht durch Änderung des bestehenden Codes.
Verletzung des OCP
Stellen wir uns ein Rabattsystem vor, bei dem jeder neue Rabatttyp eine Änderung der Methode applyDiscount erfordert:
public class DiscountService { public double applyDiscount(String type, double price) { if (type.equals("SUMMER")) { return price * 0.9; } else if (type.equals("VIP")) { return price * 0.8; } // Hinzufügen eines neuen Typs → Änderung der Klasse return price; }}Lösung durch Polymorphie
Wir verwenden ein Interface und verschiedene Implementierungen:
public interface Discount { double apply(double price);}
public class SummerDiscount implements Discount { @Override public double apply(double price) { return price * 0.9; }}
public class VipDiscount implements Discount { @Override public double apply(double price) { return price * 0.8; }}
// Neuer Typ – einfach eine neue Klasse