Dlaczego Observer pattern jest ważny?
Wyobraź sobie aplikację z interfejsem użytkownika – gdy użytkownik zmieni dane, różne części aplikacji muszą się o tym dowiedzieć: widok musi się odświeżyć, logi muszą być zapisane, notyfikacje wysłane. Observer pattern rozwiązuje ten problem elegancko, bez bezpośredniego łączenia komponentów.
Co się nauczysz:
- Jak działa Observer pattern i kiedy go używać
- Implementacja pattern od podstaw w Javie
- Wykorzystanie wbudowanych klas Observable i Observer
- Praktyczne przykłady z event handling w GUI
- Zalety i wady tego wzorca projektowego
Struktura Observer Pattern
Observer pattern składa się z czterech głównych elementów:
Implementacja od podstaw
Zacznijmy od stworzenia interfejsów dla naszego wzorca:
// Observer.java - interfejs dla obserwatorów public interface Observer { void update(String message); } // Subject.java - interfejs dla obiektu obserwowanego public interface Subject { void addObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers(); }
Teraz stwórzmy konkretną implementację – system powiadomień w sklepie internetowym:
// ProductStore.java - Konkretny Subject import java.util.ArrayList; import java.util.List; public class ProductStore implements Subject { private Listobservers; private String productName; private boolean inStock; public ProductStore() { this.observers = new ArrayList<>(); } @Override public void addObserver(Observer observer) { observers.add(observer); System.out.println("Nowy obserwator dodany. Liczba obserwatorów: " + observers.size()); } @Override public void removeObserver(Observer observer) { observers.remove(observer); System.out.println("Obserwator usunięty. Liczba obserwatorów: " + observers.size()); } @Override public void notifyObservers() { String message = productName + " jest " + (inStock ? "dostępny" : "niedostępny"); for (Observer observer : observers) { observer.update(message); } } // Metoda biznesowa - gdy stan produktu się zmienia public void setProductAvailability(String productName, boolean inStock) { this.productName = productName; this.inStock = inStock; System.out.println("Stan produktu zmieniony: " + productName + " - " + inStock); notifyObservers(); // Automatyczne powiadomienie obserwatorów } }
Teraz stwórzmy konkretnych obserwatorów – różne typy klientów:
// EmailNotifier.java - Obserwator wysyłający emaile public class EmailNotifier implements Observer { private String customerEmail; public EmailNotifier(String customerEmail) { this.customerEmail = customerEmail; } @Override public void update(String message) { System.out.println("📧 Email do " + customerEmail + ": " + message); // Tu byłaby logika wysyłania prawdziwego emaila } } // SMSNotifier.java - Obserwator wysyłający SMS public class SMSNotifier implements Observer { private String phoneNumber; public SMSNotifier(String phoneNumber) { this.phoneNumber = phoneNumber; } @Override public void update(String message) { System.out.println("📱 SMS na " + phoneNumber + ": " + message); // Tu byłaby logika wysyłania prawdziwego SMS } } // LoggerObserver.java - Obserwator logujący zmiany public class LoggerObserver implements Observer { @Override public void update(String message) { System.out.println("📝 LOG: " + java.time.LocalDateTime.now() + " - " + message); // Tu byłaby logika zapisu do pliku/bazy danych } }
Testowanie naszej implementacji
// Main.java - Test Observer pattern public class Main { public static void main(String[] args) { // Tworzymy sklep (Subject) ProductStore store = new ProductStore(); // Tworzymy obserwatorów EmailNotifier emailClient1 = new EmailNotifier("jan@example.com"); EmailNotifier emailClient2 = new EmailNotifier("anna@example.com"); SMSNotifier smsClient = new SMSNotifier("+48123456789"); LoggerObserver logger = new LoggerObserver(); // Rejestrujemy obserwatorów store.addObserver(emailClient1); store.addObserver(emailClient2); store.addObserver(smsClient); store.addObserver(logger); System.out.println("\n=== Zmiana dostępności produktu ===="); // Symulujemy zmianę stanu produktu store.setProductAvailability("iPhone X", true); System.out.println("\n=== Usuwamy jednego obserwatora ===="); store.removeObserver(emailClient1); // Kolejna zmiana - mniej obserwatorów zostanie powiadomionych store.setProductAvailability("iPhone X", false); } }
Java’s Built-in Observer Support
Java oferuje wbudowane klasy Observable i Observer (dostępne w Java 8, deprecated w Java 9):
// Używanie wbudowanego Observable (Java 8) import java.util.Observable; import java.util.Observer; // WeatherStation.java - Używa wbudowanego Observable public class WeatherStation extends Observable { private float temperature; public void setTemperature(float temperature) { this.temperature = temperature; setChanged(); // Oznacz że stan się zmienił notifyObservers(temperature); // Powiadom obserwatorów } public float getTemperature() { return temperature; } } // WeatherDisplay.java - Implementuje wbudowany Observer public class WeatherDisplay implements Observer { private String displayName; public WeatherDisplay(String displayName) { this.displayName = displayName; } @Override public void update(Observable o, Object arg) { if (o instanceof WeatherStation) { float temp = (Float) arg; System.out.println(displayName + " pokazuje temperaturę: " + temp + "°C"); } } }
Zalety i wady Observer Pattern
Zalety | Wady |
---|---|
Loose coupling – Subject nie zna konkretnych Observer | Może prowadzić do memory leaks jeśli Observer nie zostanie usunięty |
Dynamiczne dodawanie/usuwanie obserwatorów w runtime | Trudne debugowanie w złożonych systemach |
Zgodny z Open/Closed Principle | Przypadkowe powiadomienia mogą degradować wydajność |
Separacja logiki biznesowej od prezentacji | Obserwatorzy nie wiedzą o sobie nawzajem |
Używaj gdy jeden obiekt musi powiadamiać wiele innych o zmianach, ale nie chcesz twardego powiązania między nimi. Typowe przypadki: GUI events, MVC architecture, notification systems.
Tak, Event Listener to praktyczna implementacja Observer pattern. W Swing czy Android używasz tego wzorca za każdym razem gdy dodajesz OnClickListener.
Zawsze pamiętaj o wywołaniu removeObserver() gdy obiekt nie jest już potrzebny. Możesz też używać WeakReference lub automatic cleanup w destruktorze.
Tak, możesz stworzyć różne metody update() dla różnych typów event lub używać enum/klasy jako parametru do określenia typu powiadomienia.
Może, jeśli masz dużo obserwatorów i częste powiadomienia. Rozważ asynchroniczne powiadomienia lub batch notifications dla lepszej wydajności.
🚀 Zadanie dla Ciebie
Stwórz system monitorowania konta bankowego używając Observer pattern. Konto (Subject) powinno powiadamiać obserwatorów o: wypłatach powyżej 1000 zł, niskim saldzie (poniżej 100 zł) i dużych wpłatach (powyżej 5000 zł). Dodaj różnych obserwatorów: EmailNotifier, SMSAlert i SecurityLogger.
Przydatne zasoby:
- Java 8 Observer API Documentation
- Refactoring Guru – Observer Pattern
- Java Design Patterns na GitHub
- Baeldung – Observer Pattern Tutorial
Czy używałeś już Observer pattern w swoich projektach? W jakich sytuacjach okazał się najbardziej przydatny?