Obserwator w języku Java
Obserwator to behawioralny wzorzec projektowy pozwalający obiektom powiadamiać inne obiekty o zmianach swojego stanu.
Obserwator daje możliwość subskrypcji lub zrezygnowania z subskrypcji zdarzeń dowolnego obiektu implementującego interfejs subskrybenta.
Złożoność:
Popularność:
Przykłady użycia: Wzorzec Obserwator jest dość powszechny w kodzie Java, szczególnie w komponentach graficznego interfejsu użytkownika. Pozwala reagować na zdarzenia dotyczące innych obiektów bez konieczności sprzęgania z klasami tych obiektów.
Oto parę przykładów użycia wzorca w głównych bibliotekach Java:
java.util.Observer/java.util.Observable(rzadko stosowane w prawdziwym życiu)- Wszystkie implementacje
java.util.EventListener(praktycznie we wszystkich komponentach Swing) javax.servlet.http.HttpSessionBindingListenerjavax.servlet.http.HttpSessionAttributeListenerjavax.faces.event.PhaseListener
Identyfikacja: Wzorzec Obserwator można poznać po obecności metod służących subskrypcji, które przechowują obiekty w strukturze listy i po wywołaniach metod aktualizacji obiektów z tej listy.
Subskrybowanie zdarzeń
W poniższym przykładzie, wzorzec Obserwator umożliwia pośrednią współpracę pomiędzy obiektami edytora tekstu. Za każdym razem, gdy obiekt Editor ulega zmianie, informuje o tym zdarzeniu subskrybentów. Metody EmailNotificationListener oraz LogOpenListener reagują na te powiadomienia przystępując do wykonywania swoich głównych zadań.
Klasy subskrybentów nie są sprzęgnięte z klasą edytora i mogą zostać wykorzystane ponownie w innych aplikacjach, jeśli zachodzi taka potrzeba. Klasa Editor zależy tylko od abstrakcyjnego interfejsu subskrybenta. Pozwala to dodawać kolejne typy subskrybentów bez konieczności zmiany kodu edytora.
publisher
publisher/EventManager.java: Bazowy publikujący
package refactoring_guru.observer.example.publisher; import refactoring_guru.observer.example.listeners.EventListener; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class EventManager { Map<String, List<EventListener>> listeners = new HashMap<>(); public EventManager(String... operations) { for (String operation : operations) { this.listeners.put(operation, new ArrayList<>()); } } public void subscribe(String eventType, EventListener listener) { List<EventListener> users = listeners.get(eventType); users.add(listener); } public void unsubscribe(String eventType, EventListener listener) { List<EventListener> users = listeners.get(eventType); users.remove(listener); } public void notify(String eventType, File file) { List<EventListener> users = listeners.get(eventType); for (EventListener listener : users) { listener.update(eventType, file); } } } editor
editor/Editor.java: Konkretny publikujący, obserwowany przez inne obiekty
package refactoring_guru.observer.example.editor; import refactoring_guru.observer.example.publisher.EventManager; import java.io.File; public class Editor { public EventManager events; private File file; public Editor() { this.events = new EventManager("open", "save"); } public void openFile(String filePath) { this.file = new File(filePath); events.notify("open", file); } public void saveFile() throws Exception { if (this.file != null) { events.notify("save", file); } else { throw new Exception("Please open a file first."); } } } listeners
listeners/EventListener.java: Wspólny interfejs obserwatora
package refactoring_guru.observer.example.listeners; import java.io.File; public interface EventListener { void update(String eventType, File file); } listeners/EmailNotificationListener.java: Wysyła emaile otrzymawszy powiadomienie
package refactoring_guru.observer.example.listeners; import java.io.File; public class EmailNotificationListener implements EventListener { private String email; public EmailNotificationListener(String email) { this.email = email; } @Override public void update(String eventType, File file) { System.out.println("Email to " + email + ": Someone has performed " + eventType + " operation with the following file: " + file.getName()); } } listeners/LogOpenListener.java: Zapisuje komunikat do dziennika otrzymawszy powiadomienie
package refactoring_guru.observer.example.listeners; import java.io.File; public class LogOpenListener implements EventListener { private File log; public LogOpenListener(String fileName) { this.log = new File(fileName); } @Override public void update(String eventType, File file) { System.out.println("Save to log " + log + ": Someone has performed " + eventType + " operation with the following file: " + file.getName()); } } Demo.java: Kod inicjalizacyjny
package refactoring_guru.observer.example; import refactoring_guru.observer.example.editor.Editor; import refactoring_guru.observer.example.listeners.EmailNotificationListener; import refactoring_guru.observer.example.listeners.LogOpenListener; public class Demo { public static void main(String[] args) { Editor editor = new Editor(); editor.events.subscribe("open", new LogOpenListener("/path/to/log/file.txt")); editor.events.subscribe("save", new EmailNotificationListener("admin@example.com")); try { editor.openFile("test.txt"); editor.saveFile(); } catch (Exception e) { e.printStackTrace(); } } } OutputDemo.txt: Wynik działania
Save to log \path\to\log\file.txt: Someone has performed open operation with the following file: test.txt Email to admin@example.com: Someone has performed save operation with the following file: test.txt