Observer pattern w Javie

TL;DR: Observer pattern pozwala obiektom automatycznie powiadamiać inne obiekty o zmianach swojego stanu. To jak system subskrypcji – jeden obiekt (Subject) informuje wielu obserwatorów (Observers) o wydarzeniach. Używany w GUI, event handling i wszędzie tam, gdzie potrzebujemy loose coupling.

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.

Observer pattern to jak kanał YouTube – gdy YouTuber publikuje nowy film (zmiana stanu), wszyscy subskrybenci (obserwatorzy) automatycznie dostają powiadomienie. YouTuber nie musi znać każdego subskrybenta osobno.

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
Wymagania wstępne: Podstawowa znajomość Java, klasy i interfejsy, ArrayList. Pomocna będzie znajomość konceptu dziedziczenia i polimorfizmu.

Struktura Observer Pattern

Observer pattern składa się z czterech głównych elementów:

Subject – obiekt, który jest obserwowany i powiadamia o zmianach
Observer – obiekt, który chce być powiadamiany o zmianach w Subject
ConcreteSubject – konkretna implementacja Subject z rzeczywistymi danymi
ConcreteObserver – konkretna implementacja Observer z logiką reakcji na zmiany

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 List observers;
    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);
    }
}

Gdy uruchomisz ten kod, zobaczysz jak automatycznie wszyscy zarejestrowani obserwatorzy otrzymują powiadomienia o każdej zmianie stanu produktu.

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");
        }
    }
}
Uwaga: Observable i Observer są deprecated od Java 9. W nowych projektach lepiej używaj własnej implementacji lub bibliotek jak RxJava czy EventBus.

Zalety i wady Observer Pattern

ZaletyWady
Loose coupling – Subject nie zna konkretnych ObserverMoże prowadzić do memory leaks jeśli Observer nie zostanie usunięty
Dynamiczne dodawanie/usuwanie obserwatorów w runtimeTrudne debugowanie w złożonych systemach
Zgodny z Open/Closed PrinciplePrzypadkowe powiadomienia mogą degradować wydajność
Separacja logiki biznesowej od prezentacjiObserwatorzy nie wiedzą o sobie nawzajem
Typowy błąd: Zapominanie o usuwaniu obserwatorów może prowadzić do memory leaks. Zawsze pamiętaj o removeObserver() gdy obiekt nie jest już potrzebny.
Kiedy używać Observer pattern?

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.

Czy Observer pattern to to samo co Event Listener?

Tak, Event Listener to praktyczna implementacja Observer pattern. W Swing czy Android używasz tego wzorca za każdym razem gdy dodajesz OnClickListener.

Jak uniknąć memory leaks z Observer pattern?

Zawsze pamiętaj o wywołaniu removeObserver() gdy obiekt nie jest już potrzebny. Możesz też używać WeakReference lub automatic cleanup w destruktorze.

Czy mogę wysyłać różne typy powiadomień?

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.

Czy Observer pattern wpływa na wydajność?

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:

Czy używałeś już Observer pattern w swoich projektach? W jakich sytuacjach okazał się najbardziej przydatny?

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Przewijanie do góry