Як Observer реалізований в Java?
4. @Async для важких операцій 5. WeakReference для запобігання витоків 6. Backpressure у Reactive Streams 7. Spring Events для enterprise додатків
🟢 Junior Level
Observer (Спостерігач) — патерн, за яким один об’єкт (Subject) повідомляє інших спостерігачів (Observers) про зміни.
Проста аналогія: Підписка на YouTube канал. Коли автор публікує відео, усі підписники отримують сповіщення.
Приклад:
// Спостерігач
interface NewsListener {
void onNewVideo(String title);
}
// Subject (видавець)
class YouTuber {
private List<NewsListener> listeners = new ArrayList<>();
public void subscribe(NewsListener listener) {
listeners.add(listener);
}
public void unsubscribe(NewsListener listener) {
listeners.remove(listener);
}
public void publishVideo(String title) {
System.out.println("New video: " + title);
// Сповіщуємо всіх підписників
for (NewsListener listener : listeners) {
listener.onNewVideo(title);
}
}
}
// Використання
YouTuber youtuber = new YouTuber();
youtuber.subscribe(title -> System.out.println("I'll watch: " + title));
youtuber.publishVideo("Java Tutorial");
Де використовується:
- Кнопки і події в UI
- Підписка на новини
- Реактивні системи
🟡 Middle Level
⚠️
java.util.Observableіjava.util.Observerdeprecated з Java 9. Альтернативи:PropertyChangeListener, Flow API (Java 9+), Spring Events.
Java Bean PropertyChangeListener
// Вбудована реалізація Observer в Java
public class Model {
private final PropertyChangeSupport support = new PropertyChangeSupport(this);
private String name;
public void addPropertyChangeListener(PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}
public void setName(String name) {
String old = this.name;
this.name = name;
support.firePropertyChange("name", old, name); // Сповіщення!
}
}
// Використання
Model model = new Model();
model.addPropertyChangeListener(evt ->
System.out.println(evt.getPropertyName() + ": " + evt.getOldValue() + " → " + evt.getNewValue())
);
model.setName("New Name");
Functional Observer (Java 8+)
// Замість інтерфейсу — лямбди
public class EventSource {
private final List<Consumer<String>> listeners = new ArrayList<>();
public void onEvent(Consumer<String> listener) {
listeners.add(listener);
}
public void emit(String event) {
listeners.forEach(listener -> listener.accept(event));
}
}
// Використання
EventSource source = new EventSource();
source.onEvent(e -> System.out.println("Listener 1: " + e));
source.onEvent(e -> System.out.println("Listener 2: " + e));
source.emit("Hello!");
Типові проблеми
- Виняток у слухачі зупиняє всіх
// ❌ Якщо один слухач кидає exception — решта не отримають сповіщення for (NewsListener listener : listeners) { listener.onNewVideo(title); // Exception → цикл переривається! } // ✅ Ізоляція слухачів for (NewsListener listener : listeners) { try { listener.onNewVideo(title); } catch (Exception e) { log.error("Listener failed", e); // Продовжуємо сповіщати решту } } - ConcurrentModificationException
// ❌ Слухач видаляє себе під час сповіщення for (NewsListener listener : listeners) { listener.onNewVideo(title); // Всередині: listeners.remove(this) → Exception! } // ✅ Копія списку або CopyOnWriteArrayList private final List<NewsListener> listeners = new CopyOnWriteArrayList<>(); - Витік пам’яті
// ❌ Слухач не видалений → витік model.addPropertyChangeListener(listener); // Забули видалити → модель тримає слухача вічно // ✅ Завжди видаляйте model.removePropertyChangeListener(listener);
🔴 Senior Level
Event-driven архітектура
// Spring Events — Observer на стероїдах
@Component
public class OrderService {
private final ApplicationEventPublisher publisher;
public void createOrder(Order order) {
// Зберігаємо замовлення
orderRepository.save(order);
// Публікуємо подію
publisher.publishEvent(new OrderCreatedEvent(order));
}
}
// Слухач
@Component
public class NotificationService {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
sendEmail(event.getOrder().getEmail());
}
}
// Асинхронний слухач
@Component
public class AnalyticsService {
@Async
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
trackAnalytics(event.getOrder()); // Не блокує основний потік!
}
}
Reactive Streams (Java 9+)
// Flow API — сучасна реалізація Observer
public class NewsPublisher implements Flow.Publisher<String> {
private final List<Flow.Subscriber<String>> subscribers = new ArrayList<>();
@Override
public void subscribe(Flow.Subscriber<String> subscriber) {
subscribers.add(subscriber);
subscriber.onSubscribe(new Flow.Subscription() {
public void request(long n) { /* backpressure */ }
public void cancel() { subscribers.remove(subscriber); }
});
}
public void publish(String news) {
subscribers.forEach(s -> s.onNext(news));
}
}
Thread Safety
// Проблеми багатопотоковості в Observer
public class EventSource {
// ❌ ArrayList не thread-safe
private List<Consumer<String>> listeners = new ArrayList<>();
// ✅ CopyOnWriteArrayList для частого читання, рідкісного запису
private List<Consumer<String>> listeners = new CopyOnWriteArrayList<>();
// ✅ Або synchronized
public synchronized void addListener(Consumer<String> listener) {
listeners.add(listener);
}
// ✅ AtomicReference для singleton слухача
private final AtomicReference<Consumer<String>> listener = new AtomicReference<>();
}
WeakReference Observers
// Запобігання витокам через слабкі посилання
public class WeakEventSource {
private final List<WeakReference<Consumer<String>>> listeners = new ArrayList<>();
public void addListener(Consumer<String> listener) {
listeners.add(new WeakReference<>(listener));
}
public void emit(String event) {
// Копіюємо список щоб уникнути ConcurrentModificationException
// якщо listener додасть новий listener під час notify
new ArrayList<>(listeners).forEach(l -> l.onEvent(event));
}
}
Production Experience
Реальний сценарій: Blocking listener убив API
- Синхронний @EventListener надсилав email
- Email сервіс гальмував → весь API чекав
- Рішення: @Async для слухача
- Результат: API < 100ms стабільно
Best Practices
- Ізолюйте слухачів — try/catch навколо кожного
- CopyOnWriteArrayList для thread-safety
- Видаляйте слухачів при знищенні
- @Async для важких операцій
- WeakReference для запобігання витоків
- Backpressure у Reactive Streams
- Spring Events для enterprise додатків
Резюме для Senior
- Observer = основа event-driven архітектури
- Spring @EventListener = production-ready реалізація
- Reactive Streams = backpressure + non-blocking
- Thread-safety: CopyOnWriteArrayList або synchronized
- Async listeners не блокують видавця
- WeakReference запобігає витокам
- Isolate кожен слухач (try/catch)
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
- java.util.Observable і Observer deprecated з Java 9, альтернативи: PropertyChangeListener, Flow API, Spring Events
- Spring @EventListener — production-ready Observer: синхронний і @Async для неблокуючих слухачів
- Flow API (Java 9+) — Reactive Streams з backpressure: Publisher, Subscriber, Subscription
- Thread-safety: CopyOnWriteArrayList для частого читання/рідкісного запису, або synchronized
- Виняток в одному слухачі не повинен зупиняти решту — try/catch навколо кожного
- WeakReference Observers запобігають витокам пам’яті при забутому видаленні
- ConcurrentModificationException при видаленні слухача під час сповіщення — вирішується копією списку
Часті уточнювальні запитання:
- Чому Observable/Observer deprecated? — Застарілий дизайн, немає підтримки backpressure, синхронний
- Як Spring Events вирішує проблему Observer? — @EventListener + ApplicationEventPublisher, @Async для неблокуючих
- Що таке backpressure у Reactive Streams? — Механізм, що дозволяє підписнику контролювати швидкість отримання даних
- Як уникнути витоку пам’яті в Observer? — Завжди видаляти слухачів, використовувати WeakReference
Червоні прапорці (НЕ говорити):
- “Я використовую java.util.Observable” — deprecated з Java 9
- “Слухачі не можуть викликати deadlock” — синхронний слухач + блокування = deadlock
- “Конкурентна модифікація не проблема” — ConcurrentModificationException при видаленні під час notify
- “Всі слухачі мають бути синхронними” — @Async для важких операцій (email, analytics)
Пов’язані теми:
- [[15. Що таке патерн Iterator]] — перебір колекцій
- [[2. Які категорії патернів існують]] — Behavioral патерни
- [[14. Які типи Proxy існують]] — Proxy у Spring Events
- [[10. Коли використовувати Strategy]] — заміна алгоритмів
- [[13. В чому різниця між State та Strategy]] — поведінкові патерни