Menu
Coddy logo textTech

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

Используйте Наблюдатель, когда изменения в одном объекте требуют обновления других, и вы не хотите, чтобы эти объекты были тесно связаны друг с другом.

challenge icon

Задание

Легко

Давайте создадим систему мониторинга Weather Station (Метеостанция), используя паттерн Наблюдатель (Observer). Вы создадите метеостанцию, которая отслеживает данные о температуре и автоматически уведомляет несколько дисплеев при каждом изменении температуры — классическое применение этого паттерна в реальном мире.

Вы организуете свой код в трех файлах:

  • Observer.h: Определите интерфейс наблюдателя и конкретный наблюдатель-дисплей.

    Создайте абстрактный класс Observer с чисто виртуальным методом update(double temperature), который наблюдатели реализуют для получения уведомлений, а также виртуальный деструктор.

    Затем создайте класс TemperatureDisplay, который наследуется от Observer. У каждого дисплея есть имя (string), устанавливаемое через конструктор. При вызове update() он должен выводить:

    [DisplayName]: Temperature is [temperature] degrees
  • WeatherStation.h: Создайте субъект, который хранит температуру и уведомляет наблюдателей.

    Ваш класс WeatherStation должен:

    • Хранить список указателей на наблюдателей и текущую температуру (инициализировать значением 0.0)
    • Иметь метод attach(Observer* obs) для регистрации наблюдателей
    • Иметь метод detach(Observer* obs) для удаления наблюдателей
    • Иметь метод setTemperature(double temp), который обновляет температуру и уведомляет всех зарегистрированных наблюдателей
    • Иметь приватный метод notify(), который вызывает update() у каждого наблюдателя с текущей температурой
  • main.cpp: Продемонстрируйте работу паттерна Наблюдатель.

    Считайте три входных значения:

    1. Имя для первого дисплея (string)
    2. Имя для второго дисплея (string)
    3. Значение температуры (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;
}
quiz iconПроверьте себя

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

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