Menu
CoddyTech

Паттерн 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 не нужно ничего знать о своих наблюдателях. Новые наблюдатели могут подписываться, не изменяя издателя, что упрощает расширение и поддержку системы.

challenge icon

Задание

Легко

Создадим систему мониторинга температуры с помощью паттерна «Наблюдатель» и событий. Ты создашь датчик, который будет уведомлять нескольких наблюдателей при каждом изменении температуры, — это идеально подходит для таких сценариев, как системы климат-контроля, где разные компоненты должны реагировать на изменения температуры.

Ты распределишь код по трём файлам:

  • TemperatureSensor.cs: создай класс TemperatureSensor в пространстве имён Monitoring. Это издатель — субъект, за которым будут наблюдать другие объекты. В датчике должны быть:
    • Закрытое поле для хранения текущей температуры
    • Событие с именем TemperatureChanged, использующее Action<double> для уведомления подписчиков о новой температуре
    • Свойство Temperature, которое вызывает событие всякий раз, когда температуре присваивается новое значение
  • Observers.cs: создай в том же пространстве имён два класса-наблюдателя, которые будут подписываться на изменения температуры:
    • DisplayUnit — содержит метод OnTemperatureChanged(double temp), который выводит Display: {temp}C
    • AlarmSystem — содержит метод OnTemperatureChanged(double temp), который выводит Alarm Check: {temp}C
  • Program.cs: объедини все компоненты, создав датчик и обоих наблюдателей. Подпиши обоих наблюдателей на событие датчика, затем обнови температуру. После этого отпиши систему сигнализации и обнови температуру ещё раз, чтобы показать, что уведомление получает только блок отображения.

На вход будут поданы два значения:

  • Первое значение температуры (например, 22.5)
  • Второе значение температуры (например, 28.0)

Твоя программа должна:

  1. Создать датчик и обоих наблюдателей
  2. Подписать обоих наблюдателей на событие TemperatureChanged
  3. Установить для датчика значение температуры, равное первому входному значению (уведомление должны получить оба наблюдателя)
  4. Отписать AlarmSystem от события
  5. Установить для датчика значение температуры, равное второму входному значению (уведомление должен получить только блок отображения)

Например, если на вход поданы значения 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)
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор C#