CommunityCodierung & Entwicklunggithub.com

viccom/design-patterns

Gängige Entwurfsmuster mit Java-Beispielen (Factory, Builder, Strategy, Observer, Decorator usw.). Verwenden, wenn der Benutzer „Muster implementieren“, „Factory verwenden“, „Strategy-Muster“ fragt oder wenn erweiterbare Komponenten entworfen werden.

Was ist design-patterns?

Diese Skill bietet eine umfassende, praktische Referenz zu gängigen Entwurfsmustern (Design Patterns) in Java mit modernen, sofort einsetzbaren Codebeispielen. Sie deckt die drei Hauptkategorien ab: Erzeugungsmuster (Builder, Factory Method, Singleton), Verhaltensmuster (Strategy, Observer, Template Method) und Strukturmuster (Decorator, Adapter). Der Schwerpunkt liegt auf der Vermittlung von Anti-Patterns und der Gegenüberstellung mit eleganteren Lösungen, z.B. wird das Teleskopierende-Konstruktor-Anti-Pattern durch den Builder-Pattern ersetzt oder der klassische Observer durch Spring Events. Die Skill ist für Entwickler gedacht, die in Java-Codebasen arbeiten und ihre Architektur durch den Einsatz von Entwurfsmustern flexibler, wartbarer und erweiterbarer gestalten möchten. Sie ist ideal für Code-Reviews, Refactoring und wenn Benutzer explizit nach der Implementierung eines bestimmten Musters fragen.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/viccom/viccom-agent-skills/tree/HEAD/skills/design-patterns

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Skill „Entwurfsmuster“

Praktische Referenz zu Entwurfsmustern (Design Patterns) für Java mit modernen Beispielen.

Verwendung

  • Der Benutzer bittet um die Implementierung eines bestimmten Musters
  • Entwurf erweiterbarer/flexibler Komponenten
  • Refactoring von starren Codestrukturen
  • Code-Review schlägt die Verwendung eines Musters vor

Kurzreferenz: Wann verwendet man was

ProblemMuster
Komplexe ObjektkonstruktionBuilder
Objekte ohne Angabe der Klasse erstellenFactory
Mehrere Algorithmen, zur Laufzeit austauschbarStrategy
Verhalten hinzufügen, ohne die Klasse zu ändernDecorator
Mehrere Objekte über Änderungen benachrichtigenObserver
Einzelne Instanz sicherstellenSingleton
Inkompatible Schnittstellen konvertierenAdapter
Algorithmusskelett definierenTemplate Method

Erzeugungsmuster

Builder

Verwendung: Objekt hat viele Parameter, einige optional.

// ❌ Teleskopierendes Konstruktor-Anti-Pattern
public class User {
    public User(String name) { }
    public User(String name, String email) { }
    public User(String name, String email, int age) { }
    public User(String name, String email, int age, String phone) { }
    // ... Explosion von Konstruktoren
}

// ✅ Builder-Pattern
public class User {
    private final String name;      // erforderlich
    private final String email;     // erforderlich
    private final int age;          // optional
    private final String phone;     // optional
    private final String address;   // optional

    private User(Builder builder) {
        this.name = builder.name;
        this.email = builder.email;
        this.age = builder.age;
        this.phone = builder.phone;
        this.address = builder.address;
    }

    public static Builder builder(String name, String email) {
        return new Builder(name, email);
    }

    public static class Builder {
        // Erforderlich
        private final String name;
        private final String email;
        // Optional mit Standardwerten
        private int age = 0;
        private String phone = "";
        private String address = "";

        private Builder(String name, String email) {
            this.name = name;
            this.email = email;
        }

        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public Builder phone(String phone) {
            this.phone = phone;
            return this;
        }

        public Builder address(String address) {
            this.address = address;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }
}

// Verwendung
User user = User.builder("John", "[email protected]")
    .age(30)
    .phone("+1234567890")
    .build();

Mit Lombok:

@Builder
@Getter
public class User {
    private final String name;
    private final String email;
    @Builder.Default private int age = 0;
    private String phone;
}

Factory Method

Verwendung: Objekte erstellen, ohne die genaue Klasse anzugeben.

// ✅ Factory-Method-Pattern
public interface Notification {
    void send(String message);
}

public class EmailNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("E-Mail: " + message);
    }
}

public class SmsNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("SMS: " + message);
    }
}

public class PushNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("Push: " + message);
    }
}

// Factory
public class NotificationFactory {

    public static Notification create(String type) {
        return switch (type.toUpperCase()) {
            case "EMAIL" -> new EmailNotification();
            case "SMS" -> new SmsNotification();
            case "PUSH" -> new PushNotification();
            default -> throw new IllegalArgumentException("Unbekannter Typ: " + type);
        };
    }
}

// Verwendung
Notification notification = NotificationFactory.create("EMAIL");
notification.send("Hallo!");

Mit Spring (bevorzugt):

public interface NotificationSender {
    void send(String message);
    String getType();
}

@Component
public class EmailSender implements NotificationSender {
    @Override public void send(String message) { /* ... */ }
    @Override public String getType() { return "EMAIL"; }
}

@Component
public class SmsSender implements NotificationSender {
    @Override public void send(String message) { /* ... */ }
    @Override public String getType() { return "SMS"; }
}

@Component
public class NotificationFactory {
    private final Map<String, NotificationSender> senders;

    public NotificationFactory(List<NotificationSender> senderList) {
        this.senders = senderList.stream()
            .collect(Collectors.toMap(
                NotificationSender::getType,
                Function.identity()
            ));
    }

    public NotificationSender getSender(String type) {
        return Optional.ofNullable(senders.get(type))
            .orElseThrow(() -> new IllegalArgumentException("Unbekannt: " + type));
    }
}

Singleton

Verwendung: Genau eine Instanz wird benötigt (sparsam verwenden!).

// ✅ Modernes Singleton (enum-basiert, threadsicher)
public enum DatabaseConnection {
    INSTANCE;

    private Connection connection;

    DatabaseConnection() {
        // Verbindung initialisieren
    }

    public Connection getConnection() {
        return connection;
    }
}

// Verwendung
Connection conn = DatabaseConnection.INSTANCE.getConnection();

Mit Spring (bevorzugt):

@Component  // Standard-Scope ist Singleton
public class DatabaseConnection {
    // Spring verwaltet die einzelne Instanz
}

Warnung: Singletons können problematisch sein:

  • Schwer zu testen (globaler Zustand)
  • Versteckte Abhängigkeiten
  • Stattdessen Abhängigkeitsinjektion in Betracht ziehen

Verhaltensmuster

Strategy

Verwendung: Mehrere Algorithmen für denselben Vorgang, zur Laufzeit austauschbar.

// ✅ Strategy-Pattern
public interface PaymentStrategy {
    void pay(BigDecimal amount);
}

public class CreditCardPayment implements PaymentStrategy {
    private final String cardNumber;

    public CreditCardPayment(String cardNumber) {
        this.cardNumber = cardNumber;
    }

    @Override
    public void pay(BigDecimal amount) {
        System.out.println("Bezahlt " + amount + " mit Karte " + cardNumber);
    }
}

public class PayPalPayment implements PaymentStrategy {
    private final String email;

    public PayPalPayment(String email) {
        this.email = email;
    }

    @Override
    public void pay(BigDecimal amount) {
        System.out.println("Bezahlt " + amount + " via PayPal: " + email);
    }
}

public class CryptoPayment implements PaymentStrategy {
    private final String walletAddress;

    public CryptoPayment(String walletAddress) {
        this.walletAddress = walletAddress;
    }

    @Override
    public void pay(BigDecimal amount) {
        System.out.println("Bezahlt " + amount + " an Wallet: " + walletAddress);
    }
}

// Kontext
public class ShoppingCart {
    private PaymentStrategy paymentStrategy;

    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }

    public void checkout(BigDecimal total) {
        paymentStrategy.pay(total);
    }
}

// Verwendung
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CreditCardPayment("4111-1111-1111-1111"));
cart.checkout(new BigDecimal("99.99"));

// Strategie zur Laufzeit ändern
cart.setPaymentStrategy(new PayPalPayment("[email protected]"));
cart.checkout(new BigDecimal("49.99"));

Mit Java 8+ (funktional):

// Strategie als funktionales Interface
@FunctionalInterface
public interface PaymentStrategy {
    void pay(BigDecimal amount);
}

// Verwendung mit Lambdas
PaymentStrategy creditCard = amount ->
    System.out.println("Kartenzahlung: " + amount);

PaymentStrategy paypal = amount ->
    System.out.println("PayPal-Zahlung: " + amount);

cart.setPaymentStrategy(creditCard);

Observer

Verwendung: Objekte müssen über Änderungen an einem anderen Objekt benachrichtigt werden.

// ✅ Observer-Pattern (modernes Java)
public interface OrderObserver {
    void onOrderPlaced(Order order);
}

public class OrderService {
    private final List<OrderObserver> observers = new ArrayList<>();

    public void addObserver(OrderObserver observer) {
        observers.add(observer);
    }

    public void removeObserver(OrderObserver observer) {
        observers.remove(observer);
    }

    public void placeOrder(Order order) {
        // Bestellung verarbeiten
        saveOrder(order);

        // Alle Beobachter benachrichtigen
        observers.forEach(observer -> observer.onOrderPlaced(order));
    }
}

// Beobachter
public class InventoryService implements OrderObserver {
    @Override
    public void onOrderPlaced(Order order) {
        // Lagerbestand reduzieren
        order.getItems().forEach(item ->
            reduceStock(item.getProductId(), item.getQuantity())
        );
    }
}

public class EmailNotificationService implements OrderObserver {
    @Override
    public void onOrderPlaced(Order order) {
        sendConfirmationEmail(order.getCustomerEmail(), order);
    }
}

public class AnalyticsService implements OrderObserver {
    @Override
    public void onOrderPlaced(Order order) {
        trackOrderEvent(order);
    }
}

// Einrichtung
OrderService orderService = new OrderService();
orderService.addObserver(new InventoryService());
orderService.addObserver(new EmailNotificationService());
orderService.addObserver(new AnalyticsService());

Mit Spring Events (bevorzugt):

// Event
public record OrderPlacedEvent(Order order) {}

// Publisher
@Service
public class OrderService {
    private final ApplicationEventPublisher eventPublisher;

    public void placeOrder(Order order) {
        saveOrder(order);
        eventPublisher.publishEvent(new OrderPlacedEvent(order));
    }
}

// Listener (Beobachter)
@Component
public class InventoryListener {
    @EventListener
    public void handleOrderPlaced(OrderPlacedEvent event) {
        // Lagerbestand reduzieren
    }
}

@Component
public class EmailListener {
    @EventListener
    public void handleOrderPlaced(OrderPlacedEvent event) {
        // E-Mail senden
    }

    @EventListener
    @Async  // Asynchrone Verarbeitung
    public void handleOrderPlacedAsync(OrderPlacedEvent event) {
        // E-Mail asynchron senden
    }
}

Template Method

Verwendung: Algorithmusskelett definieren, Unterklassen füllen die Schritte aus.

// ✅ Template-Method-Pattern
public abstract class DataProcessor {

    // Template-Methode - definiert den Algorithmus
    public final void process() {
        readData();
        processData();
        writeData();
        if (shouldNotify()) {
            notifyCompletion();
        }
    }

    // Schritte, die von Unterklassen implementiert werden
    protected abstract void readData();
    protected abstract void processData();
    protected abstract void writeData();

    // Hook - optionale Überschreibung
    protected boolean shouldNotify() {
        return true;
    }

    protected void notifyCompletion() {
        System.out.println("Verarbeitung abgeschlossen!");
    }
}

public class CsvDataProcessor extends DataProcessor {
    @Override
    protected void readData() {
        System.out.println("Lese CSV-Datei...");
    }

    @Override
    protected void processData() {
        System.out.println("Verarbeite CSV-Daten...");
    }

    @Override
    protected void writeData() {
        System.out.println("Schreibe in Datenbank...");
    }
}

public class ApiDataProcessor extends DataProcessor {
    @Override
    protected void readData() {
        System.out.println("Rufe von API ab...");
    }

    @Override
    protected void processData() {
        System.out.println("Transformiere API-Antwort...");
    }

    @Override
    protected void writeData() {
        System.out.println("Schreibe in Cache...");
    }

    @Override
    protected boolean shouldNotify() {
        return false;  // Hook überschreiben
    }
}

// Verwendung
DataProcessor csvProcessor = new CsvDataProcessor();
csvProcessor.process();

DataProcessor apiProcessor = new ApiDataProcessor();
apiProcessor.process();

Strukturmuster

Decorator

Verwendung: Verhalten dynamisch hinzufügen, ohne vorhandene Klassen zu ändern.

// ✅ Decorator-Pattern
public interface Coffee {
    String getDescription();
    BigDecimal getCost();
}

public class SimpleCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "Kaffee";
    }

    @Override
    public BigDecimal getCost() {
        return new BigDecimal("2.00");
    }
}

// Basis-Dekorator
public abstract class CoffeeDecorator implements Coffee {
    protected final Coffee coffee;

    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    @Override
    public String getDescription() {
        return coffee.getDescription();
    }

    @Override
    public BigDecimal getCost() {
        return coffee.getCost();
    }
}

// Konkrete Dekoratoren
public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + ", Milch";
    }

    @Override
    public BigDecimal getCost() {
        return coffee.getCost().add(new BigDecimal("0.50"));
    }
}

public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + ", Zucker";
    }

    @Override
    public BigDecimal getCost() {
        return coffee.getCost().add(new BigDecimal("0.20"));
    }
}

public class WhippedCreamDecorator extends CoffeeDecorator {
    public WhippedCreamDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + ", Schlagsahne";
    }

    @Override
    public BigDecimal getCost() {
        return coffee.getCost().add(new BigDecimal("0.70"));
    }
}

// Verwendung
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getDescription() + " kostet " + coffee.getCost());

Coffee milkCoffee = new MilkDecorator(new SimpleCoffee());
System.out.println(milkCoffee.getDescription() + " kostet " + milkCoffee.getCost());

Coffee fancyCoffee = new WhippedCreamDecorator(new SugarDecorator(new MilkDecorator(new SimpleCoffee())));
System.out.println(fancyCoffee.getDescription() + " kostet " + fancyCoffee.getCost());

Adapter

Verwendung: Zwei inkompatible Schnittstellen zusammenbringen.

// ✅ Adapter-Pattern
// Angenommen, ein externer Dienst verwendet ein Legacy-Format
public class LegacyUserService {
    public String getUserXml(int userId) {
        return "<user><id>" + userId + "</id></user>";
    }
}

// Moderne Schnittstelle, die wir verwenden möchten
public interface ModernUserService {
    User getUser(int userId);
}

public record User(int id, String name, String email) {}

// Adapter
public class UserServiceAdapter implements ModernUserService {
    private final LegacyUserService legacyService;
    private final XmlParser xmlParser;

    public UserServiceAdapter(LegacyUserService legacyService, XmlParser xmlParser) {
        this.legacyService = legacyService;
        this.xmlParser = xmlParser;
    }

    @Override
    public User getUser(int userId) {
        String xml = legacyService.getUserXml(userId);
        return xmlParser.parseUser(xml);
    }
}

// Verwendung
LegacyUserService legacyUserService = new LegacyUserService();
ModernUserService userService = new UserServiceAdapter(legacyUserService, new XmlParser());
User user = userService.getUser(123);

Best Practices

Wann verwenden

  • Entwurfsmuster sind Lösungen für wiederkehrende Probleme, nicht Ziele
  • Überspringe ein Muster, wenn es die Komplexität unnötig erhöht
  • Ziehe funktionale Ansätze (Lambdas, Streams) einfachen OOP-Mustern vor
  • Spring und andere Frameworks bieten integrierte "Pattern"-Unterstützung

Anti-Patterns zu vermeiden

  • Singleton-Überbeanspruchung: Ein Singleton pro Anwendung ist akzeptabel, ein Singleton pro Klasse nicht
  • Factory-Überbeanspruchung: Erstelle keine Factory, wenn es nur eine Implementierung gibt
  • Muster auswendig lernen: Verstehe das Problem, nicht nur das Muster
  • Komplexität um der Komplexität willen: Manchmal ist einfacher Code besser

Moderne Alternativen

  • Strategy -> Lambda-Ausdrücke und Funktionsreferenzen
  • Observer -> Reactive Streams, Spring Events
  • Decorator -> Zusammensetzung mit Streams/bibliotheksspezifischen Mitteln
  • Factory -> Spring Beans + Dependency Injection
  • Builder -> Lombok @Builder, Records mit benannten Argumenten (falls unterstützt)

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

Verwandte Skills