Паттерн Observer (События)
Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy. Урок 54 из 70.
Паттерн «Наблюдатель» устанавливает отношение «один ко многим», при котором один объект (субъект) уведомляет несколько зависимых объектов (наблюдателей) при изменении своего состояния. В C# события предоставляют встроенный способ элегантно реализовать этот паттерн.
Этот шаблон включает издателя, который генерирует события, и подписчиков, которые на них реагируют. Вот практический пример монитора цен на акции:
public class Stock
{
private decimal _price;
public event Action<decimal> PriceChanged;
public decimal Price
{
get => _price;
set
{
_price = value;
PriceChanged?.Invoke(_price); // Уведомить всех наблюдателей
}
}
}
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}");
}
}Подписчики привязывают свои методы к событию с помощью += и могут отсоединить их с помощью -=:
var stock = new Stock();
var alert = new PriceAlert();
var logger = new PriceLogger();
stock.PriceChanged += alert.OnPriceChanged;
stock.PriceChanged += logger.OnPriceChanged;
stock.Price = 150.50m;
// Вывод:
// Оповещение: цена теперь 150.50
// Записано в журнал: 150.50
stock.PriceChanged -= logger.OnPriceChanged; // Отписаться
stock.Price = 155.00m;
// Вывод: оповещение: цена теперь 155.00Паттерн «Наблюдатель» способствует слабой связанности: классу Stock не нужно ничего знать о своих наблюдателях. Новые наблюдатели могут подписываться, не изменяя издателя, что упрощает расширение и поддержку системы.
Задание
ЛегкоСоздадим систему мониторинга температуры с помощью паттерна «Наблюдатель» и событий. Ты создашь датчик, который будет уведомлять нескольких наблюдателей при каждом изменении температуры, — это идеально подходит для таких сценариев, как системы климат-контроля, где разные компоненты должны реагировать на изменения температуры.
Ты распределишь код по трём файлам:
TemperatureSensor.cs: создай классTemperatureSensorв пространстве имёнMonitoring. Это издатель — субъект, за которым будут наблюдать другие объекты. В датчике должны быть:- Закрытое поле для хранения текущей температуры
- Событие с именем
TemperatureChanged, использующееAction<double>для уведомления подписчиков о новой температуре - Свойство
Temperature, которое вызывает событие всякий раз, когда температуре присваивается новое значение
Observers.cs: создай в том же пространстве имён два класса-наблюдателя, которые будут подписываться на изменения температуры:DisplayUnit— содержит методOnTemperatureChanged(double temp), который выводитDisplay: {temp}CAlarmSystem— содержит методOnTemperatureChanged(double temp), который выводитAlarm Check: {temp}C
Program.cs: объедини все компоненты, создав датчик и обоих наблюдателей. Подпиши обоих наблюдателей на событие датчика, затем обнови температуру. После этого отпиши систему сигнализации и обнови температуру ещё раз, чтобы показать, что уведомление получает только блок отображения.
На вход будут поданы два значения:
- Первое значение температуры (например,
22.5) - Второе значение температуры (например,
28.0)
Твоя программа должна:
- Создать датчик и обоих наблюдателей
- Подписать обоих наблюдателей на событие
TemperatureChanged - Установить для датчика значение температуры, равное первому входному значению (уведомление должны получить оба наблюдателя)
- Отписать
AlarmSystemот события - Установить для датчика значение температуры, равное второму входному значению (уведомление должен получить только блок отображения)
Например, если на вход поданы значения 22.5 и 28.0, вывод должен быть таким:
Display: 22.5C
Alarm Check: 22.5C
Display: 28CЭто демонстрирует ключевое преимущество паттерна «Наблюдатель»: датчик не знает и не заботится о том, кто за ним следит. Наблюдатели могут динамически подписываться и отписываться, а издатель просто уведомляет всех, кто подписан в данный момент!
Попробуйте сами
using System;
using Monitoring;
class Program
{
public static void Main(string[] args)
{
// Считать входные значения температуры
double temp1 = Convert.ToDouble(Console.ReadLine());
double temp2 = Convert.ToDouble(Console.ReadLine());
// TODO: Создать экземпляр TemperatureSensor
// TODO: Создать экземпляры DisplayUnit и AlarmSystem
// TODO: Подписать обоих наблюдателей на событие TemperatureChanged
// TODO: Установить температуру датчика равной temp1 (оба наблюдателя получат уведомление)
// TODO: Отписать AlarmSystem от события
// TODO: Установить температуру датчика равной temp2 (уведомление получит только DisplayUnit)
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыПространства имен и директивыВведение в классы и объектыКлючевое слово 'this'Методы и параметрыПоля и свойстваКонструкторыИнициализаторы объектовИтоги — Простой калькулятор4Наследование
Основы синтаксиса наследования (:)Ключевое слово 'base'Ключевые слова Virtual и OverrideЗапечатанные классыБазовый класс 'object'Итоги — Иерархия сотрудников7Продвинутые возможности
Перегрузка операторовИндексаторы (this[])Переопределение ToString()Методы расширенияПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПотокобезопасный SingletonПаттерн FactoryПаттерн Observer (События)Паттерн Strategy13Финальные испытания
Платформа для онлайн-обученияКомпонент игрового персонажаСервис аренды транспорта2Свойства и статические члены
Автоматически реализуемые свойстваСвойства только для чтения и записиСтатические поля и методыСтатические классыЧлены в виде выражений5Полиморфизм и интерфейсы
Полиморфизм: компиляция vs выполнениеИнтерфейс vs абстрактный классМножественные интерфейсыЯвные интерфейсыUpcasting и DowncastingИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция вместо наследованияGenerics (классы и методы)Делегаты и событияАтрибуты и рефлексияIDisposable и оператор usingОсновы Dependency Injection11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Архитектура классов
Экземплярные и статические данныеКлючевые слова 'readonly' и 'const'Опорные поляПовторение — Менеджер банковских счетов6Инкапсуляция
Модификаторы доступаСвойства для инкапсуляцииРеализация скрытия данныхПаттерны неизменяемостиИтоги — Записи студентов9Переменное количество аргументов
Ключевое слово 'params'Необязательные параметрыИменованные аргументыПерегрузка методовПотренируйтесь самостоятельно: Онлайн-компилятор C#