Паттерн Observer
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 94 из 104.
Паттерн Наблюдатель (Observer pattern) определяет зависимость «один ко многим» между объектами: когда один объект (субъект) меняет свое состояние, все зависимые от него объекты (наблюдатели) уведомляются автоматически. Это идеально подходит для систем событий, обновлений пользовательского интерфейса или любого сценария, где несколько объектов должны реагировать на изменения.
Паттерн включает две ключевые роли: Субъект (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(). Наблюдатели могут присоединяться или отсоединяться в любое время, что делает систему гибкой и слабосвязанной.
Используйте Наблюдатель, когда изменения в одном объекте требуют обновления других, и вы не хотите, чтобы эти объекты были тесно связаны друг с другом.
Задание
ЛегкоДавайте создадим систему мониторинга Weather Station (Метеостанция), используя паттерн Наблюдатель (Observer). Вы создадите метеостанцию, которая отслеживает данные о температуре и автоматически уведомляет несколько дисплеев при каждом изменении температуры — классическое применение этого паттерна в реальном мире.
Вы организуете свой код в трех файлах:
Observer.h: Определите интерфейс наблюдателя и конкретный наблюдатель-дисплей.Создайте абстрактный класс
Observerс чисто виртуальным методомupdate(double temperature), который наблюдатели реализуют для получения уведомлений, а также виртуальный деструктор.Затем создайте класс
TemperatureDisplay, который наследуется отObserver. У каждого дисплея есть имя (string), устанавливаемое через конструктор. При вызовеupdate()он должен выводить:[DisplayName]: Temperature is [temperature] degreesWeatherStation.h: Создайте субъект, который хранит температуру и уведомляет наблюдателей.Ваш класс
WeatherStationдолжен:- Хранить список указателей на наблюдателей и текущую температуру (инициализировать значением
0.0) - Иметь метод
attach(Observer* obs)для регистрации наблюдателей - Иметь метод
detach(Observer* obs)для удаления наблюдателей - Иметь метод
setTemperature(double temp), который обновляет температуру и уведомляет всех зарегистрированных наблюдателей - Иметь приватный метод
notify(), который вызываетupdate()у каждого наблюдателя с текущей температурой
- Хранить список указателей на наблюдателей и текущую температуру (инициализировать значением
main.cpp: Продемонстрируйте работу паттерна Наблюдатель.Считайте три входных значения:
- Имя для первого дисплея (string)
- Имя для второго дисплея (string)
- Значение температуры (double)
Создайте объект
WeatherStationи два объектаTemperatureDisplayс указанными именами. Прикрепите оба дисплея к станции, затем установите температуру в соответствии с входным значением. Оба дисплея должны автоматически получить обновление и вывести свои сообщения.После этого отсоедините первый дисплей и установите новую температуру, которая на 5 градусов выше входной. Только второй дисплей должен получить это обновление.
Например, при входных данных Kitchen, Bedroom и 22.5:
Kitchen: Temperature is 22.5 degrees
Bedroom: Temperature is 22.5 degrees
Bedroom: Temperature is 27.5 degreesПри входных данных Office, Lobby и 18.0:
Office: Temperature is 18 degrees
Lobby: Temperature is 18 degrees
Lobby: Temperature is 23 degreesОбратите внимание, как паттерн Наблюдатель создает слабосвязанную систему — метеостанции не нужно ничего знать о конкретных дисплеях, она просто уведомляет всех, кто ее «слушает». Когда вы отсоединяете наблюдателя, он автоматически перестает получать обновления.
Шпаргалка
Паттерн Наблюдатель (Observer) определяет зависимость «один ко многим», при которой субъект автоматически уведомляет всех своих наблюдателей об изменении своего состояния.
Паттерн имеет две ключевые роли:
- Субъект (Subject): хранит список наблюдателей и уведомляет их об изменениях состояния
- Наблюдатель (Observer): реализует интерфейс обновления для получения уведомлений
Базовая реализация:
#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";
}
};Ключевые методы:
attach(): регистрирует наблюдателя для получения уведомленийdetach(): удаляет наблюдателя из списка уведомленийnotify(): перебирает всех наблюдателей и вызывает их методupdate()
Используйте «Наблюдатель», когда изменения в одном объекте требуют обновления других без создания жесткой связи между ними.
Попробуйте сами
#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Итоги — Обобщенный контейнер