Dlaczego Chain of Responsibility jest ważny
W każdej aplikacji mamy procesy wymagające wieloetapowej obsługi – od walidacji formularzy po autoryzację użytkowników. Chain of Responsibility eliminuje sztywne powiązania między nadawcą żądania a jego obsługą. Zamiast jednej wielkiej klasy z if-else, masz elastyczny łańcuch gdzie każdy element ma jedną odpowiedzialność.
Co się nauczysz
- Jak działa wzorzec Chain of Responsibility i kiedy go stosować
- Implementacja abstrakcyjnej klasy Handler i konkretnych handlerów
- Tworzenie łańcucha obsługi żądań w praktyce
- Systemy walidacji i autoryzacji używające tego wzorca
- Zarządzanie przepływem żądań i obsługa błędów
Wymagania wstępne
Czym jest wzorzec Chain of Responsibility
Chain of Responsibility to wzorzec projektowy który pozwala przekazać żądanie wzdłuż łańcucha potencjalnych obsługujących. Każdy element łańcucha decyduje czy obsłużyć żądanie czy przekazać je dalej.
ConcreteHandler – konkretna implementacja obsługująca specyficzne żądania
Client – wysyła żądanie do pierwszego elementu łańcucha
Podstawowa implementacja w Javie
Zacznijmy od prostego przykładu – systemu obsługi wsparcia technicznego:
// Klasa reprezentująca żądanie class SupportRequest { private String type; private String description; private int priority; // 1=low, 2=medium, 3=high public SupportRequest(String type, String description, int priority) { this.type = type; this.description = description; this.priority = priority; } // Gettery public String getType() { return type; } public String getDescription() { return description; } public int getPriority() { return priority; } } // Abstrakcyjna klasa Handler abstract class SupportHandler { protected SupportHandler nextHandler; public void setNext(SupportHandler handler) { this.nextHandler = handler; } public abstract void handleRequest(SupportRequest request); protected void passToNext(SupportRequest request) { if (nextHandler != null) { nextHandler.handleRequest(request); } else { System.out.println("No handler available for: " + request.getDescription()); } } }
Konkretne implementacje handlerów
Teraz stwórzmy konkretne handlery dla różnych poziomów wsparcia:
// Handler dla podstawowych problemów class Level1SupportHandler extends SupportHandler { @Override public void handleRequest(SupportRequest request) { if (request.getPriority() == 1 && request.getType().equals("basic")) { System.out.println("Level 1 Support: Handling basic request - " + request.getDescription()); } else { System.out.println("Level 1 Support: Escalating to Level 2"); passToNext(request); } } } // Handler dla średnio zaawansowanych problemów class Level2SupportHandler extends SupportHandler { @Override public void handleRequest(SupportRequest request) { if (request.getPriority() <= 2 && (request.getType().equals("technical") || request.getType().equals("basic"))) { System.out.println("Level 2 Support: Handling technical request - " + request.getDescription()); } else { System.out.println("Level 2 Support: Escalating to Level 3"); passToNext(request); } } } // Handler dla krytycznych problemów class Level3SupportHandler extends SupportHandler { @Override public void handleRequest(SupportRequest request) { if (request.getPriority() == 3) { System.out.println("Level 3 Support: Handling critical request - " + request.getDescription()); } else { System.out.println("Level 3 Support: Handling any remaining request - " + request.getDescription()); } // Level 3 nie przekazuje dalej - to ostatni poziom } }
Budowanie i użycie łańcucha
Zobaczmy jak połączyć handlery w działający łańcuch:
public class ChainOfResponsibilityDemo { public static void main(String[] args) { // Tworzenie handlerów SupportHandler level1 = new Level1SupportHandler(); SupportHandler level2 = new Level2SupportHandler(); SupportHandler level3 = new Level3SupportHandler(); // Budowanie łańcucha level1.setNext(level2); level2.setNext(level3); // Testowe żądania SupportRequest[] requests = { new SupportRequest("basic", "Password reset", 1), new SupportRequest("technical", "Database connection issues", 2), new SupportRequest("critical", "Server crashed", 3), new SupportRequest("unknown", "Strange error", 2) }; // Przetwarzanie żądań for (SupportRequest request : requests) { System.out.println("\n--- Processing new request ---"); level1.handleRequest(request); } } }
Zaawansowany przykład - walidacja użytkownika
Praktyczny przykład z walidacją i autoryzacją:
// Klasa użytkownika class User { private String username; private String password; private String role; private boolean isActive; public User(String username, String password, String role, boolean isActive) { this.username = username; this.password = password; this.role = role; this.isActive = isActive; } // Gettery... public String getUsername() { return username; } public String getPassword() { return password; } public String getRole() { return role; } public boolean isActive() { return isActive; } } // Handler walidacji class ValidationHandler extends SupportHandler { @Override public void handleRequest(SupportRequest request) { // W rzeczywistości byłby to bardziej złożony request z danymi użytkownika System.out.println("ValidationHandler: Validating request format"); if (request.getDescription().length() < 5) { System.out.println("Validation failed: Description too short"); return; } System.out.println("Validation passed, forwarding..."); passToNext(request); } } // Handler autoryzacji class AuthorizationHandler extends SupportHandler { @Override public void handleRequest(SupportRequest request) { System.out.println("AuthorizationHandler: Checking user permissions"); // Symulacja sprawdzenia uprawnień if (request.getPriority() > 2) { System.out.println("Authorization check: Admin privileges required"); } System.out.println("Authorization passed, forwarding..."); passToNext(request); } }
Zalety i wady wzorca
Zalety | Wady |
---|---|
Oddzielenie nadawcy od odbiorcy | Żądanie może nie zostać obsłużone |
Dynamiczne budowanie łańcucha | Trudne debugowanie długich łańcuchów |
Single Responsibility Principle | Wydajność - wszystkie handlery są sprawdzane |
Open/Closed Principle | Kolejność ma znaczenie |
Kiedy używać Chain of Responsibility
- Systemów walidacji z wieloma etapami
- Middleware w aplikacjach web
- Workflow i systemy zatwierdzania
- Parsowania i przetwarzania danych
- Event handling w GUI
- Logowania na różnych poziomach
Porównanie z innymi wzorcami
Nie, handler może obsłużyć żądanie i zakończyć łańcuch. Może też obsłużyć częściowo i przekazać dalej do dodatkowego przetwarzania.
Kolejność powinna być od najbardziej specyficznych do ogólnych. Możesz też użyć Builder pattern do łatwego budowania łańcucha lub konfigurować kolejność przez pliki konfiguracyjne.
Zawsze dodaj domyślny handler na końcu łańcucha lub implementuj mechanizm fallback. Możesz też rzucić wyjątek lub zalogować nieobsłużone żądanie.
Testuj każdy handler osobno z mock następnikiem, oraz całe łańcuchy z różnymi scenariuszami żądań. Sprawdź czy właściwy handler obsługuje właściwe żądania.
Tak! To jedna z zalet wzorca. Możesz dynamicznie dodawać, usuwać lub zmieniać kolejność handlerów w zależności od potrzeb aplikacji.
Świetnie! Spring Interceptors używają tego wzorca. Możesz też wstrzykiwać handlery przez DI i budować łańcuchy automatycznie używając @Autowired i @Order.
Może, jeśli łańcuch jest bardzo długi. Każdy handler wymaga sprawdzenia warunków. Optymalizuj przez umieszczanie najczęściej używanych handlerów na początku łańcucha.
Przydatne zasoby
🚀 Zadanie dla Ciebie
Stwórz system obsługi zamówień e-commerce który:
- Waliduje dane zamówienia (adres, produkty, płatność)
- Sprawdza dostępność produktów w magazynie
- Aplikuje promocje i rabaty
- Generuje fakturę
- Wysyła potwierdzenie email
Każdy etap powinien być osobnym handlerem. Dodaj obsługę błędów - jeśli któryś etap się nie powiedzie, cały proces zostaje wstrzymany z odpowiednim komunikatem.
Chain of Responsibility to wzorzec który pomaga budować elastyczne systemy przetwarzania. Pamiętaj o prostych warunkach w handlerach i zawsze testuj różne ścieżki przepływu żądań. W jakich systemach spotkałeś podobne łańcuchy przetwarzania?