Наблюдатель (Observer)
Observer связывает publisher с набором subscribers. Publisher сообщает об изменении через общий контракт и не знает конкретные типы получателей.
classDiagram class OrderPublisher { +subscribe(listener) +unsubscribe(listener) +publish(event) } class OrderListener { <<interface>> +onEvent(event) } OrderPublisher --> "*" OrderListener
Пример
public record OrderChanged(long orderId, String state) {
}
@FunctionalInterface
public interface OrderListener {
void onEvent(OrderChanged event);
}
public final class OrderPublisher {
private final List<OrderListener> listeners =
new CopyOnWriteArrayList<>();
public void subscribe(OrderListener listener) {
listeners.add(listener);
}
public void unsubscribe(OrderListener listener) {
listeners.remove(listener);
}
public void publish(OrderChanged event) {
for (OrderListener listener : listeners) {
listener.onEvent(event);
}
}
}CopyOnWriteArrayList подходит, когда subscriptions меняются редко, а рассылка идёт часто. Это не универсальный выбор: каждая запись копирует массив.
Вопросы, которые скрывает простой пример
Синхронно или асинхронно?
В примере publish вызывает listeners в текущем потоке. Медленный subscriber задерживает publisher, а исключение способно прервать рассылку.
Асинхронная доставка требует executor или message broker и сразу добавляет:
- очередь;
- порядок;
- retry;
- backpressure;
- остановку;
- наблюдаемость.
Что происходит при ошибке listener?
Контракт должен быть явным:
- прекратить рассылку;
- продолжить и собрать ошибки;
- изолировать каждого listener;
- отправить событие повторно.
Без правила один случайный subscriber управляет поведением всех остальных.
Как отписаться?
Publisher удерживает ссылку на subscriber. Если долгоживущий publisher не удаляет краткоживущих listeners, возникает memory leak. Удобно возвращать subscription handle:
AutoCloseable subscription =
publisher.subscribe(listener);
subscription.close();Observer и reactive streams
Идея publisher/subscriber похожа, но Reactive Streams задаёт дополнительный protocol: subscriber запрашивает число элементов, получает terminal signal и может отменить subscription. Это не просто другое имя Observer.
См. Реактивное программирование Java и Spring Reactor.
Где встречается
- listeners в UI;
- domain events внутри процесса;
- lifecycle events framework;
- change notifications;
- event bus.
Для межсервисной доставки Observer в памяти недостаточен: после падения процесса событие пропадёт. Там нужны durable log/broker и отдельная модель delivery, например Kafka.
Вопросы для собеседования
- Что ослабляет Observer? Publisher зависит от общего интерфейса, а не от конкретных consumers.
- Гарантирует ли паттерн асинхронность? Нет.
- Откуда memory leak? Долгоживущий publisher удерживает subscriber.
- Как обрабатывать исключение observer? Это часть контракта рассылки, единого ответа нет.
- Чем reactive publisher богаче? Subscription, demand/backpressure, cancel и terminal signals.