Wzorzec Observer
Część sekcji Programowanie obiektowe ścieżki Java w Coddy. Lekcja 68 z 87.
Wzorzec Obserwator to behawioralny wzorzec projektowy, który ustanawia relację jeden-do-wielu między obiektami. Gdy jeden obiekt (podmiot) zmienia stan, wszystkie jego obiekty zależne (obserwatorzy) są automatycznie powiadamiane i aktualizowane. Możesz o tym myśleć jak o subskrypcji newslettera: subskrybenci otrzymują aktualizacje za każdym razem, gdy publikowane są nowe treści.
Wzorzec obejmuje dwa główne elementy: podmiot, który przechowuje listę obserwatorów i powiadamia ich o zmianach, oraz obserwatorów, którzy definiują metodę aktualizacji reagującą na powiadomienia:
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(String message);
}
class NewsPublisher {
private List<Observer> subscribers = new ArrayList<>();
public void subscribe(Observer observer) {
subscribers.add(observer);
}
public void unsubscribe(Observer observer) {
subscribers.remove(observer);
}
public void publish(String news) {
for (Observer subscriber : subscribers) {
subscriber.update(news);
}
}
}
class EmailSubscriber implements Observer {
private String email;
public EmailSubscriber(String email) {
this.email = email;
}
public void update(String message) {
System.out.println("Email to " + email + ": " + message);
}
}Gdy wydawca publikuje wiadomości, wszyscy subskrybenci są automatycznie powiadamiani:
NewsPublisher publisher = new NewsPublisher();
publisher.subscribe(new EmailSubscriber("alice@mail.com"));
publisher.subscribe(new EmailSubscriber("bob@mail.com"));
publisher.publish("Breaking news!");
// Wynik:
// E-mail do alice@mail.com: Breaking news!
// E-mail do bob@mail.com: Breaking news!Wzorzec Obserwatora promuje luźne powiązania: podmiot nie musi znać konkretnych klas swoich obserwatorów, a jedynie wiedzieć, że implementują interfejs Observer. Dzięki temu można łatwo dodawać nowe typy obserwatorów bez modyfikowania podmiotu.
Wyzwanie
ŁatwyZbudujmy system alertów o cenach akcji z użyciem wzorca Obserwator! Stworzysz system, w którym notowania akcji powiadamiają wielu inwestorów za każdym razem, gdy zmieni się cena akcji, dzięki czemu każdy inwestor może reagować na aktualizacje cen na swój sposób.
Rozmieścisz swój kod w czterech plikach:
Observer.java: Zdefiniuj interfejsObserver, który zaimplementują wszyscy inwestorzy. Powinien deklarować jedną metodęupdate(String stockName, double price), za pomocą której obserwatorzy będą otrzymywać powiadomienia o zmianach cen.StockTicker.java: Utwórz klasę podmiotu, która przechowuje listę obserwatorów i powiadamia ich o zmianach cen. Twoja klasaStockTickerpowinna mieć:Prywatne pole typu String przechowujące nazwę akcji (ustawiane przez konstruktor).
Prywatne pole typu double przechowujące bieżącą cenę (początkowo 0).
Prywatną listę do przechowywania subskrybujących obserwatorów.
Metodę
subscribe(Observer observer), która dodaje obserwatora do listy.Metodę
unsubscribe(Observer observer), która usuwa obserwatora z listy.Metodę
setPrice(double price), która aktualizuje cenę, a następnie powiadamia wszystkich obserwatorów, wywołując ich metodęupdatez nazwą akcji i nową ceną.Investors.java: Utwórz dwa różne typy inwestorów implementujące interfejsObserver:DayTrader— ma prywatne pole z nazwą ustawianą przez konstruktor. Po aktualizacji wyświetla[name] received alert: [stockName] is now $[price] - Making quick trade!LongTermInvestor— ma prywatne pole z nazwą ustawianą przez konstruktor. Po aktualizacji wyświetla[name] received alert: [stockName] is now $[price] - Holding steady.Formatuj cenę z dokładnością do dwóch miejsc po przecinku.
Main.java: Połącz elementy systemu obserwatorów! Otrzymasz trzy dane wejściowe: nazwę akcji (String), pierwszą aktualizację ceny (double) i drugą aktualizację ceny (double).Utwórz obiekt
StockTickerz podaną nazwą akcji. Utwórz obiektDayTradero nazwie"Alice"oraz obiektLongTermInvestoro nazwie"Bob", a następnie zasubskrybuj ich obu w notowaniach.Ustaw cenę na pierwszą wartość wejściową (wszyscy obserwatorzy otrzymają powiadomienie). Następnie anuluj subskrypcję Alice. Na koniec ustaw cenę na drugą wartość wejściową (tym razem powiadomienie powinien otrzymać tylko Bob).
Otrzymasz trzy dane wejściowe w podanej kolejności: nazwę akcji (String), pierwszą cenę (double), drugą cenę (double).
To wyzwanie pokazuje możliwości wzorca Obserwator: StockTicker nie wie nic o konkretnych typach inwestorów. Wie tylko, że implementują interfejs Observer. Gdy anulujesz subskrypcję Alice, przestaje ona otrzymywać aktualizacje, a Bob nadal otrzymuje powiadomienia. To luźne powiązanie ułatwia dodawanie nowych typów inwestorów bez zmieniania notowań!
Spróbuj swoich sił
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Wczytaj dane wejściowe
String stockName = scanner.nextLine();
double firstPrice = scanner.nextDouble();
double secondPrice = scanner.nextDouble();
// TODO: Utwórz StockTicker z podaną nazwą akcji
// TODO: Utwórz DayTrader o nazwie "Alice"
// TODO: Utwórz LongTermInvestor o nazwie "Bob"
// TODO: Zasubskrybuj Alice i Bob do tickera
// TODO: Ustaw cenę na firstPrice (obaj obserwatorzy otrzymają powiadomienie)
// TODO: Usuń subskrypcję Alice
// TODO: Ustaw cenę na secondPrice (tylko Bob otrzyma powiadomienie)
scanner.close();
}
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneWprowadzenie do programowania obiektowegoKlasy a obiektySłowo kluczowe thisMetodyPola (atrybuty)Metoda konstruktoraPrzeciążanie konstruktoraPodsumowanie — prosty kalkulator4Dziedziczenie
Podstawy dziedziczenia (extends)Słowo kluczowe superPrzesłanianie metod (@Override)Łańcuch wywołań konstruktorówKlasa ObjectDziedziczenie jedno- i wielopoziomoweDlaczego Java nie obsługuje wielodziedziczenia klasPodsumowanie – hierarchia pracowników7Metody specjalne i klasa Object
Metoda toString()equals() i hashCode()Metoda clone()compareTo() i ComparableInterfejs ComparatorPodsumowanie — własne sortowanie10Obsługa wyjątków w programowaniu obiektowym
Hierarchia klas wyjątkówWłasne wyjątkiBłędy sprawdzane a niesprawdzaneWzorzec try-with-resourcesPodsumowanie – zweryfikowany użytkownik2Modyfikatory dostępu i enkapsulacja
Przegląd poziomów dostępuMetody pobierające i ustawiająceUkrywanie informacjiSłowo kluczowe finalPodsumowanie — menedżer kont bankowych5Polimorfizm
Podstawy przeciążania metodPrzesłanianie metod (w czasie wykonywania)Rzutowanie w górę i w dółOperator instanceofKlasy i metody abstrakcyjnePowtórka — kalkulator figur8Zaawansowane koncepcje OOP
Kompozycja a dziedziczenieAgregacja a kompozycjaKlasy wewnętrzne, zagnieżdżone i anonimoweEnumy i metody enumówRekordy (Java 16+)Klasy zapieczętowane (Java 17+)11Wzorce projektowe — część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonWzorzec FactoryWzorzec BuilderWzorzec ObserverWzorzec Strategy3Właściwości klas i składowe statyczne
Zmienne instancji a zmienne statyczneMetody statyczneBloki statyczneStałe (static final)Powtórka – Counter i Utility6Interfejsy i klasy abstrakcyjne
Wprowadzenie do interfejsówImplementowanie interfejsówImplementacja wielu interfejsówMetody domyślne i statyczne w interfejsieKlasy abstrakcyjne a interfejsyInterfejsy funkcyjnePodsumowanie — system płatnościPoćwicz samodzielnie: Kompilator Java online