Entwurfsmuster in Java: Von der Theorie zur Praxis
Entwurfsmuster (Design Patterns) sind bewährte Lösungen für typische Aufgaben bei der Entwicklung von Software. Für einen Java-Entwickler ist die Kenntnis von Entwurfsmustern nicht nur ein Vorteil, sondern eine Notwendigkeit. Sie ermöglichen es, flexiblen, wartbaren und skalierbaren Code zu schreiben. In diesem Artikel analysieren wir vier Schlüsselmuster aus dem GoF-Katalog (Gang of Four) mit realen Beispielen in Java.
Warum sind Entwurfsmuster für Java-Entwickler wichtig?
Java ist eine streng typisierte, objektorientierte Sprache, bei der Entwurfsmuster helfen:
- Die Code-Kopplung zu reduzieren — Klassen hängen von Abstraktionen ab, nicht von konkreten Implementierungen.
- Die Wiederverwendbarkeit zu erhöhen — fertige Lösungen lassen sich leicht an neue Aufgaben anpassen.
- Die Teamarbeit zu vereinfachen — Entwurfsmuster sind eine gemeinsame Sprache für Entwickler.
1. Singleton (Einzelstück)
Das Singleton-Muster stellt sicher, dass eine Klasse nur eine einzige Instanz hat, und bietet einen globalen Zugriffspunkt darauf. In Java sieht die klassische Implementierung so aus:
public class DatabaseConnection { private static volatile DatabaseConnection instance; private String connectionString;
private DatabaseConnection(String connectionString) { this.connectionString = connectionString; }
public static DatabaseConnection getInstance(String connectionString) { if (instance == null) { synchronized (DatabaseConnection.class) { if (instance == null) { instance = new DatabaseConnection(connectionString); } } } return instance; }
public void connect() { System.out.println("Connected to: " + connectionString); }}Wann sollte Singleton verwendet werden?
- Verwaltung von Datenbankverbindungen.
- Logger und Konfigurationsmanager.
- Thread-Pool.
Wichtig: In modernen Java-Anwendungen wird Singleton oft durch Dependency Injection ersetzt (DI-Container, Spring).
2. Factory Method (Fabrikmethode)
Die Factory Method definiert eine Schnittstelle zur Erstellung eines Objekts, überlässt es aber den Unterklassen zu entscheiden, welche Klasse instanziiert wird. Betrachten wir ein Beispiel zur Erstellung von Benachrichtigungen:
// Produktschnittstelleinterface Notification { void send(String message);}
// Konkrete Produkteclass EmailNotification implements Notification { @Override public void send(String message) { System.out.println("Sending Email: " + message); }}
class SMSNotification implements Notification { @Override public void send(String message) { System.out.println("Sending SMS: " + message); }}
// Fabrikabstract class NotificationFactory { public abstract Notification createNotification();
public void notifyUser(String message) { Notification notification = createNotification(); notification.send(message); }}
class EmailNotificationFactory extends NotificationFactory { @Override public Notification createNotification() { return new EmailNotification(); }}
class SMSNotificationFactory extends NotificationFactory { @Override public Notification createNotification() { return new SMSNotification(); }}
// Verwendungpublic class Main { public static void main(String[] args) { NotificationFactory factory = new EmailNotificationFactory(); factory.notifyUser("Hello, User!"); }}Vorteile der Factory Method:
- Der Code ist nicht von konkreten Klassen abhängig.
- Das Hinzufügen neuer Benachrichtigungstypen erfordert keine Änderung des vorhandenen Codes (Open/Closed-Prinzip).
- Vereinfacht Unit-Tests.