Паттерн Observer
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 94 из 104.
Паттерн Observer определяет зависимость «один ко многим» между объектами: когда один объект ( subject ) изменяет состояние, все его зависимые объекты ( observers ) автоматически получают уведомление. Это идеально подходит для систем событий, обновлений пользовательского интерфейса или любых сценариев, в которых нескольким объектам необходимо реагировать на изменения.
Паттерн включает две ключевые роли: Subject, который поддерживает список наблюдателей и уведомляет их, и Observers, которые реализуют интерфейс обновления:
#include <iostream>
#include <vector>
#include <algorithm>
class Observer {
public:
virtual void update(int value) = 0;
virtual ~Observer() = default;
};
class Subject {
std::vector<Observer*> observers;
int state = 0;
public:
void attach(Observer* obs) {
observers.push_back(obs);
}
void detach(Observer* obs) {
observers.erase(
std::remove(observers.begin(), observers.end(), obs),
observers.end());
}
void setState(int value) {
state = value;
notify();
}
void notify() {
for (Observer* obs : observers) {
obs->update(state);
}
}
};
class Display : public Observer {
std::string name;
public:
Display(const std::string& n) : name(n) {}
void update(int value) override {
std::cout << name << " received: " << value << "\n";
}
};
int main() {
Subject sensor;
Display screen1("Screen1"), screen2("Screen2");
sensor.attach(&screen1);
sensor.attach(&screen2);
sensor.setState(42); // Оба дисплея уведомлены
}Когда вызывается setState(), субъект перебирает всех зарегистрированных наблюдателей и вызывает их update() method. Наблюдатели могут attach или detach в любое время, что делает систему гибкой и слабосвязанной.
Используйте Observer, когда изменения одного объекта требуют обновления других, а вы не хотите, чтобы эти объекты были тесно связаны друг с другом.
Задание
ЛегкоДавайте создадим систему мониторинга Weather Station с использованием паттерна Observer. Вы создадите метеостанцию, которая отслеживает данные о температуре и автоматически уведомляет несколько блоков отображения при каждом изменении температуры: классическое применение этого паттерна в реальном мире.
Вы организуете код в трёх файлах:
Observer.h: Определите интерфейс Observer и конкретный объект отображения.Создайте абстрактный
Observerclass с чистым виртуальным методомupdate(double temperature), который observers реализуют для получения уведомлений, а также с виртуальным деструктором.Затем создайте
TemperatureDisplayclass, который наследуется отObserver. У каждого блока отображения есть имя (строка), задаваемое через его constructor. Когда вызываетсяupdate(), он должен вывести:[DisplayName]: Temperature is [temperature] degreesWeatherStation.h: Создайте субъект, который хранит температуру и уведомляет observers.Ваш
WeatherStationclass должен:- Хранить list указателей на observers и текущую температуру (initialize значением
0.0) - Иметь метод
attach(Observer* obs)для регистрации observers - Иметь метод
detach(Observer* obs)для удаления observers - Иметь метод
setTemperature(double temp), который обновляет температуру и уведомляет всех прикреплённых observers - Иметь закрытый метод
notify(), который вызываетupdate()для каждого observer с текущей температурой
- Хранить list указателей на observers и текущую температуру (initialize значением
main.cpp: Продемонстрируйте паттерн Observer в действии.Считайте три inputs:
- Имя для первого блока отображения (строка)
- Имя для второго блока отображения (строка)
- Значение температуры (double)
Создайте
WeatherStationи дваTemperatureDisplayobjects с указанными именами. Прикрепите оба блока отображения к станции, затем установите температуру, равную входному значению. Оба блока отображения должны автоматически получить обновление и вывести свои messages.После этого отсоедините первый блок отображения и установите новую температуру, которая на 5 градусов выше входного значения. Только второй блок отображения должен получить это обновление.
Например, для inputs Kitchen, Bedroom и 22.5:
Kitchen: Temperature is 22.5 degrees
Bedroom: Temperature is 22.5 degrees
Bedroom: Temperature is 27.5 degreesДля inputs Office, Lobby и 18.0:
Office: Temperature is 18 degrees
Lobby: Temperature is 18 degrees
Lobby: Temperature is 23 degreesОбратите внимание, как паттерн Observer создаёт слабосвязанную систему: метеостанции не нужно ничего знать о конкретных блоках отображения — она просто уведомляет всех, кто слушает. Когда вы отсоединяете observer, он автоматически перестаёт получать обновления.
Попробуйте сами
#include <iostream>
#include <string>
#include "WeatherStation.h"
using namespace std;
int main() {
// Считать входные данные
string display1Name, display2Name;
double temperature;
cin >> display1Name;
cin >> display2Name;
cin >> temperature;
// TODO: Создать объект WeatherStation
// TODO: Создать два объекта TemperatureDisplay с входными именами
// TODO: Присоединить оба дисплея к метеостанции
// TODO: Установить температуру на входное значение
// (Оба дисплея должны вывести свои сообщения)
// TODO: Отсоединить первый дисплей
// TODO: Установить температуру на 5 градусов выше входного значения
// (Только второй дисплей должен вывести)
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнерПотренируйтесь самостоятельно: Онлайн-компилятор C++