Питання 11 · Розділ 2

Як Observer реалізований в Java?

4. @Async для важких операцій 5. WeakReference для запобігання витоків 6. Backpressure у Reactive Streams 7. Spring Events для enterprise додатків

Мовні версії: English Russian Ukrainian

🟢 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.Observer deprecated з 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!");

Типові проблеми

  1. Виняток у слухачі зупиняє всіх
    // ❌ Якщо один слухач кидає 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);
            // Продовжуємо сповіщати решту
        }
    }
    
  2. ConcurrentModificationException
    // ❌ Слухач видаляє себе під час сповіщення
    for (NewsListener listener : listeners) {
        listener.onNewVideo(title);  // Всередині: listeners.remove(this) → Exception!
    }
    
    // ✅ Копія списку або CopyOnWriteArrayList
    private final List<NewsListener> listeners = new CopyOnWriteArrayList<>();
    
  3. Витік пам’яті
    // ❌ Слухач не видалений → витік
    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

  1. Ізолюйте слухачів — try/catch навколо кожного
  2. CopyOnWriteArrayList для thread-safety
  3. Видаляйте слухачів при знищенні
  4. @Async для важких операцій
  5. WeakReference для запобігання витоків
  6. Backpressure у Reactive Streams
  7. 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]] — поведінкові патерни