Observer – operacyjny (behawioralny) wzorzec projektowy, który umożliwia automatyczne powiadomienie i aktualizację obiektu (klienta) przez obiekt, który jest nasłuchiwany (tzw. subject).
Przede wszystkim sprawdza się on w przypadkach, kiedy musimy zaktualizować obiekt klienta, ale nie mamy jasnej informacji, kiedy subject zakończy swoją pracę.
W związku z tym ten wzorzec projektowy jest bardzo często wykorzystywany w aplikacjach wielowątkowych oraz systemach rozproszonych.
Wzorzec Observer – założenia aplikacji
Na przykład chcemy zrobić aplikacje, która sprawi, że Czytelnik bloga zawsze zostanie powiadomiony o nowym wpisie. Dlatego jednym z rozwiązań jest ciągłe odpytanie Bloga czy już pojawił się nowy wpis. Jednak odpytywanie będzie musiało odbywać się cyklicznie co bliżej nieokreślony interwał czasowy – co samo w sobie nie jest optymalne.
Znacznie lepszym rozwiązaniem jest zapisanie się na blogu do listy subskrybentów, a następnie zostanie powiadomionym o tym kiedy pojawi się nowy wpis. Dlatego przykład ten stanowi dobre, praktyczne odzwierciedlenie tego jak działa wzorzec projektowy Observer.
Implementacja wzorca Observer
Rozpocznijmy od zaimplementowania interfejsu, który umożliwi nam dokonywanie powiadomień:
public interface MailObserver { void newsletter(String mail); }
Zawiera metoda przyjmuje parametr mail – czyli daną, którą będzie mógł otrzymać użytkownik.
User – klasa nasłuchująca
Klasa użytkownika musi tą informacje odczytywać, więc należy w niej zaimplementować powyższy interfejs.
public class User implements MailObserver { private String name; public User(String name) { this.name = name; } @Override public void newsletter(String mail) { System.out.println(name + " got email! " + mail); } }
Dla rozróżnienia użytkowników dodałem w modelu imię odbiorcy i przykładowo nadpisałem metodę pochodzącą z interfejsu MailObserver.
Blog – klasa nasłuchiwana
Teraz najważniejszy element – Subject. Innymi słowy Subject, to klasa, która będzie nasłuchiwana – w naszym przypadku będzie to Blog. Przede wszystkim klasa ta musi mieć możliwość przyjmowania obiektów nasłuchujących – typu MailObserver.
Dodatkowo w klasie uwzględniłem wątek, który symuluje działanie wykonywania operacji. Ponadto w wątku losowany jest czas, co który ma zostać wysłany komunikat do użytkownika. Tym komunikatem jest losowy ciąg tekstowy.
Na końcu operacji notyfikowane są wszystkie obiekty nasłuchujące:
public class Blog { private List<MailObserver> mailObservers; public Blog() { mailObservers = new ArrayList<>(); } public void subscribe(MailObserver observers) { this.mailObservers.add(observers); } public void startWork() { Thread thread = new Thread(() -> { while (Thread.currentThread().isAlive()) { int randomDelay = new Random().nextInt(1000); try { Thread.sleep(randomDelay); } catch (InterruptedException e) { e.printStackTrace(); } for (MailObserver observer : mailObservers) { observer.newsletter("time: " + randomDelay + ", content: " + UUID.randomUUID().toString()); } } }); thread.start(); } }
Inicjalizacja obiektów i uruchomienie przykładu
Samo wykonanie przykładu polega na utworzenie instancji klasy Blog, a następnie przypisanie mu kilku użytkowników, którzy będą go nasłuchiwali.
public class Main { public static void main(String[] args) { Blog blog = new Blog(); blog.subscribe(new User("Przemysław")); blog.subscribe(new User("Adam")); blog.startWork(); } }
Na koniec rezultat otrzymania danych z konsoli:
Przemysław got email! time: 357, content: a3dc8676-7c59-4493-9a1f-5cf849a76e22 Adam got email! time: 357, content: 7e150df1-4eff-46be-8d0b-a66b5d408f1d Przemysław got email! time: 851, content: 907ae678-ba7a-43f0-8104-6572c005a9cb Adam got email! time: 851, content: d4ce70e1-363f-4a65-8412-b502bddbcc0b Przemysław got email! time: 537, content: e4c681bc-26a2-4dd6-b1cb-a2cb24f68f5a Adam got email! time: 537, content: e5d8e84a-bc60-42e4-9107-b135fa15aa85 Przemysław got email! time: 449, content: 60ac3584-e6b5-4d30-ae8f-807e3bbc5b3b Adam got email! time: 449, content: 17309826-8060-40ae-a488-991c52c6ce86
Moje zastosowania wzorca Observer
Przede wszystkim Observer to mój ulubiony wzorzec. Sprawdził mi się, kiedy tworzyłem aplikacje mobilne i musiałem poczekać na odpowiedź z serwera zdalnego. Jednak do tego czasu nie mogłem wyświetlić użytkownikowi danych na interfejsie użytkownika, co zastąpiłem komunikatem „Proszę czekać…” a w momencie, kiedy komplet danych został wczytany to mój interfejs graficzny dzięki wykorzystaniu tego wzorca otrzymał notyfikacje i w ten sposób wyświetlił co trzeba 😉
Tutaj link do repo: https://github.com/bykowski/design-pattern-observer i proszę o łapkę w górę! 🙂
Przy okazji warto zapoznać się programowaniem reaktywnym, ponieważ to podejście do programowania, które narodziło się z tego wzorca 🙂