Wzorzec obserwatora (zdarzenia)
Część sekcji Programowanie obiektowe ścieżki C# w Coddy. Lekcja 54 z 70.
Wzorzec Obserwatora ustanawia relację jeden-do-wielu, w której jeden obiekt (podmiot) powiadamia wiele zależnych obiektów (obserwatorów), gdy zmienia się jego stan. W języku C# zdarzenia zapewniają wbudowany sposób eleganckiej implementacji tego wzorca.
Wzorzec obejmuje wydawcę, który wywołuje zdarzenia, oraz subskrybentów, którzy na nie reagują. Oto praktyczny przykład monitora cen akcji:
public class Stock
{
private decimal _price;
public event Action<decimal> PriceChanged;
public decimal Price
{
get => _price;
set
{
_price = value;
PriceChanged?.Invoke(_price); // Powiadom wszystkich obserwatorów
}
}
}
public class PriceAlert
{
public void OnPriceChanged(decimal newPrice)
{
Console.WriteLine($"Alert: Price is now {newPrice}");
}
}
public class PriceLogger
{
public void OnPriceChanged(decimal newPrice)
{
Console.WriteLine($"Logged: {newPrice}");
}
}Subskrybenci dołączają swoje metody do zdarzenia za pomocą += i mogą je odłączyć za pomocą -=:
var stock = new Stock();
var alert = new PriceAlert();
var logger = new PriceLogger();
stock.PriceChanged += alert.OnPriceChanged;
stock.PriceChanged += logger.OnPriceChanged;
stock.Price = 150.50m;
// Wynik:
// Alert: Cena wynosi teraz 150.50
// Zarejestrowano: 150.50
stock.PriceChanged -= logger.OnPriceChanged; // Anuluj subskrypcję
stock.Price = 155.00m;
// Wynik: Alert: Cena wynosi teraz 155.00Wzorzec Obserwator promuje luźne powiązanie — klasa Stock nie musi nic wiedzieć o swoich obserwatorach. Nowi obserwatorzy mogą się subskrybować bez modyfikowania wydawcy, co ułatwia rozszerzanie i utrzymywanie systemu.
Wyzwanie
ŁatwyZbudujmy system monitorowania temperatury z wykorzystaniem wzorca Obserwator i zdarzeń. Utworzysz czujnik, który powiadamia wielu obserwatorów za każdym razem, gdy zmienia się temperatura — idealne rozwiązanie w scenariuszach takich jak systemy kontroli klimatu, w których różne komponenty muszą reagować na zmiany temperatury.
Rozmieścisz kod w trzech plikach:
TemperatureSensor.cs: Utwórz klasęTemperatureSensorw przestrzeni nazwMonitoring. To Twój wydawca — obiekt obserwowany przez inne obiekty. Twój czujnik powinien mieć:- Prywatne pole do przechowywania bieżącej temperatury
- Zdarzenie o nazwie
TemperatureChanged, które używaAction<double>do powiadamiania subskrybentów o nowej temperaturze - Właściwość
Temperature, która wywołuje zdarzenie za każdym razem, gdy temperatura zostaje ustawiona na nową wartość
Observers.cs: Utwórz dwie klasy obserwatorów w tej samej przestrzeni nazw, które będą subskrybować zmiany temperatury:DisplayUnit— ma metodęOnTemperatureChanged(double temp), która wyświetlaDisplay: {temp}CAlarmSystem— ma metodęOnTemperatureChanged(double temp), która wyświetlaAlarm Check: {temp}C
Program.cs: Połącz wszystko, tworząc czujnik i obu obserwatorów. Zasubskrybuj obu obserwatorów do zdarzenia czujnika, a następnie zaktualizuj temperaturę. Potem anuluj subskrypcję systemu alarmowego i ponownie zaktualizuj temperaturę, aby pokazać, że powiadomienie otrzymuje już tylko wyświetlacz.
Otrzymasz dwa wejścia:
- Pierwszy odczyt temperatury (np.
22.5) - Drugi odczyt temperatury (np.
28.0)
Twój program powinien:
- Utworzyć czujnik i obu obserwatorów
- Zasubskrybować obu obserwatorów do zdarzenia
TemperatureChanged - Ustawić temperaturę czujnika na wartość pierwszego wejścia (powiadomieni powinni zostać obaj obserwatorzy)
- Anulować subskrypcję
AlarmSystemna zdarzenie - Ustawić temperaturę czujnika na wartość drugiego wejścia (powiadomiony powinien zostać tylko wyświetlacz)
Na przykład, jeśli wejściami są 22.5 i 28.0, wynik powinien być następujący:
Display: 22.5C
Alarm Check: 22.5C
Display: 28CTo pokazuje podstawową zaletę wzorca Obserwator — czujnik nie wie ani nie musi się przejmować tym, kto go nasłuchuje. Obserwatorzy mogą dynamicznie subskrybować i anulować subskrypcję, a wydawca po prostu powiadamia tych, którzy aktualnie nasłuchują!
Spróbuj swoich sił
using System;
using Monitoring;
class Program
{
public static void Main(string[] args)
{
// Odczytaj podane temperatury
double temp1 = Convert.ToDouble(Console.ReadLine());
double temp2 = Convert.ToDouble(Console.ReadLine());
// TODO: Utwórz instancję TemperatureSensor
// TODO: Utwórz instancje DisplayUnit i AlarmSystem
// TODO: Zarejestruj obu obserwatorów do obsługi zdarzenia TemperatureChanged
// TODO: Ustaw temperaturę czujnika na temp1 (obaj obserwatorzy otrzymają powiadomienie)
// TODO: Wyrejestruj AlarmSystem z obsługi zdarzenia
// TODO: Ustaw temperaturę czujnika na temp2 (powiadomienie otrzyma tylko wyświetlacz)
}
}
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ętrznePrzestrzenie nazw i dyrektywyWprowadzenie do klas i obiektówSłowo kluczowe „this”Metody i parametryPola a właściwościKonstruktoryInicjalizatory obiektówPodsumowanie — prosty kalkulator4Dziedziczenie
Podstawowa składnia dziedziczenia (:)Słowo kluczowe 'base'Słowa kluczowe virtual i overrideKlasy sealedBazowa klasa 'object'Powtórzenie – hierarchia pracowników7Zaawansowane funkcje
Przeciążanie operatorówIndeksery (this[])Nadpisywanie ToString()Metody rozszerzającePodsumowanie – własna lista10Wzorce projektowe — część 1
Wprowadzenie do wzorców projektowychSingleton bezpieczny wątkowoWzorzec fabrykiWzorzec obserwatora (zdarzenia)Wzorzec strategii2Właściwości i składowe statyczne
Właściwości automatyczneWłaściwości tylko do odczytu i zapisuPola i metody statyczneKlasy statyczneSkładowe z ciałem wyrażeniowym5Polimorfizm i interfejsy
Polimorfizm czasu kompilacji a czasu wykonaniaInterfejs a klasa abstrakcyjnaWiele interfejsówJawne implementowanie interfejsówRzutowanie w górę i w dółPowtórka — kalkulator kształtów8Zaawansowane koncepcje OOP
Kompozycja zamiast dziedziczeniaTypy generyczne (klasy i metody)Delegaty i zdarzeniaAtrybuty i refleksjaIDisposable i instrukcja usingPodstawy wstrzykiwania zależności11Wzorce projektowe, część 2
Wzorzec poleceniaWzorzec adapteraWzorzec dekoratoraWzorzec metody szablonowejWzorzec stanuWzorzec kompozytu3Architektura klas
Dane instancji a dane statyczneSłowa kluczowe 'readonly' i 'const'Pola zapasowePowtórzenie — menedżer kont bankowych6Hermetyzacja
Modyfikatory dostępuWłaściwości w hermetyzacjiImplementacja ukrywania danychWzorce niezmiennościPowtórka — dane studentów9Argumenty zmiennej liczby
Słowo kluczowe „params”Parametry opcjonalneArgumenty nazwanePrzeciążanie metodPoćwicz samodzielnie: Kompilator C# online