Subject and Observer
The Observer pattern defines a one-to-many dependency so that when one object changes state, all dependents are notified.
Core Structure
┌──────────────────────────┐
│ Subject (Publisher) │
├──────────────────────────┤
│ - observers: List │
│ - state: State │
├──────────────────────────┤
│ + attach(observer) │
│ + detach(observer) │
│ + notify() │
└──────────┬───────────────┘
│ notifies
┌─────┴──────┐
│ │
┌────┴─────┐ ┌───┴──────┐
│ObserverA │ │ObserverB │
└──────────┘ └──────────┘
Observer Interface
// Observer interface
public interface Observer {
void update(Event event);
}
// Subject interface
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers();
}
Subject Implementation
public class OrderService implements Subject {
private List<Observer> observers = new ArrayList<>();
private OrderStatus status;
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(new OrderEvent(this.status));
}
}
public void completeOrder(Order order) {
// Process order...
this.status = OrderStatus.COMPLETED;
notifyObservers();
}
}
Observer Implementations
public class EmailObserver implements Observer {
@Override
public void update(Event event) {
if (event.getType() == OrderEvent.Type.COMPLETED) {
emailService.send(event.getUser(), "Order complete!");
}
}
}
public class InventoryObserver implements Observer {
@Override
public void update(Event event) {
if (event.getType() == OrderEvent.Type.COMPLETED) {
inventoryService.reserve(event.getItems());
}
}
}
public class AnalyticsObserver implements Observer {
@Override
public void update(Event event) {
analytics.track("order_completed", event.getData());
}
}
Usage
OrderService orderService = new OrderService();
// Register observers
orderService.attach(new EmailObserver());
orderService.attach(new InventoryObserver());
orderService.attach(new AnalyticsObserver());
// When order completes, all observers are notified
orderService.completeOrder(order);
Implementation
Different implementation approaches for the Observer pattern.
Push Model vs Pull Model
// Push Model: Subject sends data to observers
public interface Observer {
void update(Order order, OrderStatus status, Map<String, Object> data);
}
// Pull Model: Observers pull what they need
public interface Observer {
void update(Subject subject);
}
public class OrderObserver implements Observer {
@Override
public void update(Subject subject) {
OrderService orderService = (OrderService) subject;
OrderStatus status = orderService.getStatus(); // Pull what needed
}
}
Thread-Safe Observer
public class ThreadSafeSubject implements Subject {
private final List<Observer> observers =
Collections.synchronizedList(new ArrayList<>());
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
synchronized (observers) {
for (Observer observer : observers) {
observer.update(getState());
}
}
}
}
Event-Based Observer
// Generic event system
public class EventBus {
private Map<Class<?>, List<Consumer<?>>> listeners = new HashMap<>();
public <T> void subscribe(Class<T> eventType, Consumer<T> listener) {
listeners.computeIfAbsent(eventType, k -> new ArrayList<>())
.add(listener);
}
public <T> void publish(T event) {
List<Consumer<?>> eventListeners = listeners.get(event.getClass());
if (eventListeners != null) {
for (Consumer<?> listener : eventListeners) {
((Consumer<T>) listener).accept(event);
}
}
}
}
// Usage
EventBus bus = new EventBus();
bus.subscribe(OrderCompletedEvent.class, event -> {
emailService.send(event.getUser(), "Order complete!");
});
bus.subscribe(OrderCompletedEvent.class, event -> {
inventoryService.reserve(event.getItems());
});
bus.publish(new OrderCompletedEvent(order));
Weak Reference Observer
// Prevent memory leaks with weak references
public class WeakSubject {
private List<WeakReference<Observer>> observers = new ArrayList<>();
public void attach(Observer observer) {
observers.add(new WeakReference<>(observer));
}
public void notifyObservers() {
observers.removeIf(ref -> ref.get() == null); // Clean dead refs
for (WeakReference<Observer> ref : observers) {
Observer observer = ref.get();
if (observer != null) {
observer.update(getState());
}
}
}
}
Event Systems
Real-world event systems built on the Observer pattern.
E-Commerce Event System
// Event hierarchy
public interface DomainEvent {
String getAggregateId();
Instant getTimestamp();
}
public class OrderEvent implements DomainEvent {
public enum Type { CREATED, PAID, SHIPPED, DELIVERED, CANCELLED }
private final Type type;
private final String orderId;
private final Map<String, Object> data;
// ...
}
// Event handlers
public class OrderEventHandler {
@EventHandler
public void onOrderCreated(OrderEvent event) {
inventoryService.reserve(event.getItems());
}
@EventHandler
public void onOrderPaid(OrderEvent event) {
paymentService.confirm(event.getPaymentId());
}
@EventHandler
public void onOrderShipped(OrderEvent event) {
notificationService.sendTracking(event.getUser(), event.getTrackingId());
}
}
Message Queue Observer
// Distributed observer using message queue
public class MessageQueueObserver implements Observer {
private final MessageProducer producer;
@Override
public void update(Event event) {
// Publish to message queue for distributed observers
producer.send("order-events", event.toJson());
}
}
// Observers in different services consume events
public class InventoryEventHandler {
@KafkaListener(topics = "order-events")
public void handle(OrderEvent event) {
if (event.getType() == Type.COMPLETED) {
inventoryService.reserve(event.getItems());
}
}
}
Observer Pattern in Frameworks
| Framework | Implementation |
|---|---|
| Java | PropertyChangeListener, EventListener |
| Spring | ApplicationEvent, @EventListener |
| React | State management (Redux-like) |
| Node.js | EventEmitter |
| Android | LiveData, Observer interface |
Event System Best Practices
- Keep events immutable: Don't modify events after creation
- Include context: Aggregate ID, timestamp, user info
- Handle failures: What if observer throws exception?
- Order matters: If order of notification matters, document it
- Memory management: Detach observers when done
- Async option: Consider async notification for performance
Practice Problems
Design a scalable Observer Pattern system. Cover high-level architecture, data model, and API design.
Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliabilityHow would you scale Observer Pattern to handle 10x the current load? Identify bottlenecks and solutions.
Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decompositionAnalyze potential failure modes for Observer Pattern and design mitigation strategies.
Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradationQuiz
1. What is the Observer pattern?
2. What is the difference between push and pull models?
3. What is a risk of the Observer pattern?
4. How does the Observer pattern support loose coupling?
5. Which is an example of Observer pattern?
Flashcards
Question
What is the Observer pattern?
Click to reveal answer
Answer
Defines a one-to-many dependency. When subject changes state, all observers are notified. Enables loose coupling between publisher and subscribers.
Question
Push vs Pull model in Observer?
Click to reveal answer
Answer
Push: subject sends all data to observers. Pull: subject notifies, observers pull what they need. Pull gives observers more control.
Question
What is a memory leak risk in Observer?
Click to reveal answer
Answer
If observers aren't detached when no longer needed, they remain referenced by the subject, preventing garbage collection.
Question
How to implement Observer in Java?
Click to reveal answer
Answer
PropertyChangeListener, EventListener interface, or custom Observer interface. Spring uses @EventListener annotation.
Question
Name real-world Observer examples.
Click to reveal answer
Answer
GUI event listeners, message queues, React state, Spring events, order status notifications, social media feeds.
Revision Notes
Key Takeaways
- 1.Observer defines one-to-many dependency for automatic notifications
- 2.Subject knows observers only through the Observer interface (loose coupling)
- 3.Push model sends data; pull model lets observers request data
- 4.Always detach observers to prevent memory leaks
- 5.Observer is the foundation of event-driven systems
Interview Tips
- •Show Observer pattern for event notification systems
- •Discuss push vs pull trade-offs for your use case
- •Mention memory management when discussing Observer
- •Show how Observer enables loose coupling between components
Cheat Sheet
Observer Pattern - Cheat Sheet
Structure:
Subject (Publisher)
├── attach(observer)
├── detach(observer)
└── notify()
│
▼
Observer (Subscriber)
└── update(event)
Models:
- Push: subject sends data
- Pull: observers request data
Key Points:
- Loose coupling (subject only knows interface)
- One-to-many notification
- Dynamic registration/deregistration
- Risk: memory leaks if not detached
Use Cases:
- Event handling systems
- Order status notifications
- Social media feeds
- Message queues
- GUI frameworks
Implementation:
- Define Observer interface
- Subject maintains observer list
- Notify on state change
- Detach when done