JavaRush /Курсы /JAVA 25 SELF /Pattern Observer (наблюдатель)

Pattern Observer (наблюдатель)

JAVA 25 SELF
50 уровень , 3 лекция
Открыта

1. Знакомимся с паттерном «Наблюдатель»

Паттерн «Наблюдатель» (Observer) — один из самых известных и фундаментальных паттернов проектирования. Он описывает ситуацию, когда один объект (наблюдаемый, или subject) сообщает о своих изменениях другим объектам (наблюдателям, observers), которые подписались на эти изменения.

Если говорить проще: у нас есть телеграм‑канал (наблюдаемый объект), и есть «подписчики» (наблюдатели). Каждый раз, когда выходит новый пост, канал уведомляет всех подписчиков, а те уже решают, что с этим делать — читать, игнорировать или отписаться.

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

Где встречается паттерн «Наблюдатель»?

  • В графических интерфейсах (Swing, AWT, JavaFX) — слушатели событий.
  • В реактивных библиотеках (RxJava, Project Reactor).
  • В бизнес‑логике: реагирование на изменение состояния модели.
  • В игровых движках (события столкновений, победы, проигрыша и т.д.).
  • Везде, где нужно отделить «что произошло» от «что с этим делать».

Связь паттерна с событиями и слушателями в Java

Фактически, вся событийная модель Java построена на «Наблюдателе». Когда вы пишете button.addActionListener(listener);, вы реализуете этот паттерн:

  • Наблюдаемый — кнопка (или другой компонент).
  • Наблюдатель — ваш слушатель, реализующий метод actionPerformed().
  • Событие — пользователь кликнул, навёл мышь и т.п.
  • Оповещение — компонент вызывает actionPerformed().

Всё это — классическая реализация Observer!

2. Классическая реализация паттерна «Наблюдатель»

Разберём, как реализовать паттерн на своих классах — без Swing и AWT, чтобы увидеть, что магии нет.

Основные элементы паттерна

  • Observable (Subject) — наблюдаемый объект. Хранит список наблюдателей и уведомляет их об изменениях.
  • Observer — интерфейс наблюдателя, обычно с методом update().

Пример: Термометр и кондиционер

Интерфейс наблюдателя

public interface TemperatureObserver {
    void temperatureChanged(int newTemperature);
}

Класс «Термометр» (наблюдаемый)

import java.util.*;

public class Thermometer {
    private int temperature;
    private final List<TemperatureObserver> observers = new ArrayList<>();

    public void addObserver(TemperatureObserver observer) {
        observers.add(observer);
    }

    public void removeObserver(TemperatureObserver observer) {
        observers.remove(observer);
    }

    public void setTemperature(int newTemperature) {
        if (this.temperature != newTemperature) {
            this.temperature = newTemperature;
            notifyObservers();
        }
    }

    private void notifyObservers() {
        for (TemperatureObserver observer : observers) {
            observer.temperatureChanged(temperature);
        }
    }
}

Пример наблюдателя — «Кондиционер»

public class AirConditioner implements TemperatureObserver {
    @Override
    public void temperatureChanged(int newTemperature) {
        if (newTemperature > 25) {
            System.out.println("Кондиционер включён! Жарко: " + newTemperature + "°C");
        } else {
            System.out.println("Кондиционер выключен. Температура: " + newTemperature + "°C");
        }
    }
}

Использование

public class Main {
    public static void main(String[] args) {
        Thermometer thermometer = new Thermometer();
        AirConditioner conditioner = new AirConditioner();

        thermometer.addObserver(conditioner);

        thermometer.setTemperature(22); // Кондиционер выключен. Температура: 22°C
        thermometer.setTemperature(28); // Кондиционер включён! Жарко: 28°C
    }
}

Вот и вся магия! Можно добавить ещё хоть сто наблюдателей — все они получат уведомления при изменении температуры.

Графическая схема паттерна

flowchart LR T["Термометр (Observable)"] -- уведомляет --> AC["Кондиционер (Observer)"] T -- уведомляет --> L["Логгер (Observer)"] T -- уведомляет --> Alarm["Сигнализация (Observer)"]

Современные детали: устаревший Observable и новые подходы

В стандартной библиотеке Java существовали java.util.Observable и java.util.Observer, но с Java 9 они помечены как устаревшие (deprecated). Причина — недостаточная гибкость (например, Observable — это класс, а не интерфейс, из‑за чего сложнее наследоваться от другого класса).

Современный подход — проектировать собственные интерфейсы слушателей и логику подписки/отписки (как в примере выше). Это гибче, безопаснее и лучше соответствует реальным задачам.

3. Пример: Мини‑приложение с подписчиками

Сделаем «счётчик нажатий» с возможностью подписки на изменение значения.

Интерфейс слушателя

public interface CounterListener {
    void counterChanged(int newValue);
}

Класс‑счётчик

import java.util.*;

public class Counter {
    private int value = 0;
    private final List<CounterListener> listeners = new ArrayList<>();

    public void addCounterListener(CounterListener l) {
        listeners.add(l);
    }

    public void removeCounterListener(CounterListener l) {
        listeners.remove(l);
    }

    public void increment() {
        value++;
        notifyListeners();
    }

    private void notifyListeners() {
        for (CounterListener l : listeners) {
            l.counterChanged(value);
        }
    }

    public int getValue() {
        return value;
    }
}

Слушатель: выводим сообщение

public class ConsoleCounterListener implements CounterListener {
    @Override
    public void counterChanged(int newValue) {
        System.out.println("Счётчик изменился: " + newValue);
    }
}

Использование

public class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();
        counter.addCounterListener(new ConsoleCounterListener());

        counter.increment(); // Счётчик изменился: 1
        counter.increment(); // Счётчик изменился: 2
    }
}

4. Полезные нюансы

Современные альтернативы и расширения

В реальных проектах часто используют анонимные классы или лямбда‑выражения для подписки: counter.addCounterListener(newValue -> System.out.println("Новое значение: " + newValue));

(Чтобы так сделать, интерфейс должен быть функциональным — иметь единственный абстрактный метод.)

Также популярны реактивные библиотеки (RxJava, Project Reactor), где «Наблюдатель» реализован с поддержкой потоков событий, фильтрации, асинхронности и т.д. Для понимания сути достаточно классической схемы, разобранной выше.

Применение паттерна «Наблюдатель» в жизни

  • Модели данных. Изменение модели (списка задач, товаров, пользователей) уведомляет представления для обновления.
  • Логирование. Подписчик‑логгер реагирует на события по всей системе.
  • Уведомления. При изменении состояния — отправка email, пуш‑уведомлений, сообщений в Telegram.
  • Игры. Изменение здоровья, появление врага, завершение уровня.
  • Многопоточность. Один поток публикует события, другие реагируют.

5. Типичные ошибки при реализации паттерна «Наблюдатель»

Ошибка №1: Забыли удалить слушателя. Если слушатель больше не нужен, но не был удалён, он продолжит получать уведомления. В долгоживущих приложениях это может привести к утечкам памяти.

Ошибка №2: Долгие или блокирующие операции в обработчиках. Если обработчик выполняет тяжёлую работу (IO, БД), приложение может «подвисать», особенно если уведомления идут из UI‑потока. Переносите тяжёлые задачи в фоновые потоки.

Ошибка №3: Исключения в слушателях. Исключение в одном слушателе может прервать рассылку остальным. Оборачивайте вызовы слушателей в try-catch и логируйте ошибки.

Ошибка №4: Множественная регистрация одного и того же слушателя. Если один слушатель добавлен несколько раз, он получит событие дублирующееся количество раз. Следите за регистрацией и применяйте защиту от повторного добавления.

Ошибка №5: Жёсткая связь между наблюдаемым и наблюдателем. Если наблюдаемый знает о конкретных реализациях наблюдателей, нарушается слабая связанность. Используйте только интерфейсы (например, TemperatureObserver, CounterListener).

1
Задача
JAVA 25 SELF, 50 уровень, 3 лекция
Недоступна
Основание Техно-Новостного Агентства 📰
Основание Техно-Новостного Агентства 📰
1
Задача
JAVA 25 SELF, 50 уровень, 3 лекция
Недоступна
Распространение горячих новостей 🌐
Распространение горячих новостей 🌐
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ