Observer Pattern
Observer Pattern: Keeping Everyone in the Loop
Section titled “Observer Pattern: Keeping Everyone in the Loop”Now let’s dive into the Observer Pattern - one of the most commonly used behavioral design patterns.
Why Observer Pattern?
Section titled “Why Observer Pattern?”Imagine you’re subscribed to a YouTube channel. When the creator uploads a new video, you automatically get notified - you don’t have to keep checking! The Observer Pattern works the same way!
The Observer Pattern lets objects notify multiple other objects (observers) about state changes automatically. When the subject (the thing being watched) changes, all observers are notified without the subject needing to know who’s watching.
What’s the Use of Observer Pattern?
Section titled “What’s the Use of Observer Pattern?”The Observer Pattern is useful when:
- One object changes and multiple objects need to be notified
- You want loose coupling - Subject doesn’t need to know about observers
- You need dynamic relationships - Observers can be added/removed at runtime
- You want to avoid polling - No need to constantly check for changes
- You need one-to-many dependency - One subject, many observers
What Happens If We Don’t Use Observer Pattern?
Section titled “What Happens If We Don’t Use Observer Pattern?”Without the Observer Pattern, you might:
- Tightly couple objects - Subject needs to know all observers
- Use polling - Constantly checking for changes (inefficient)
- Scatter notification logic - Update code spread everywhere
- Make it hard to add/remove observers - Need to modify subject code
- Violate Open/Closed Principle - Need to modify code to add observers
Simple Example: The Weather Station
Section titled “Simple Example: The Weather Station”Let’s start with a super simple example that anyone can understand!
The Problem
Section titled “The Problem”You’re building a weather monitoring system. When the temperature changes, you need to update multiple displays (phone app, website, billboard). Without Observer Pattern:
# ❌ Without Observer Pattern - Tight coupling
class WeatherStation: def __init__(self): self.temperature = 0 # Problem: Subject knows about all observers! self.phone_app = None self.website = None self.billboard = None
def set_temperature(self, temp: float): self.temperature = temp # Problem: Need to manually notify each observer if self.phone_app: self.phone_app.update(temp) if self.website: self.website.update(temp) if self.billboard: self.billboard.update(temp) # Every time you add a new display, you need to modify this!
class PhoneApp: def update(self, temperature: float): print(f"📱 Phone: Temperature is {temperature}°C")
class Website: def update(self, temperature: float): print(f"🌐 Website: Temperature is {temperature}°C")
class Billboard: def update(self, temperature: float): print(f"📺 Billboard: Temperature is {temperature}°C")
# Usagestation = WeatherStation()station.phone_app = PhoneApp()station.website = Website()station.billboard = Billboard()
station.set_temperature(25.0)// ❌ Without Observer Pattern - Tight coupling
public class WeatherStation { private double temperature = 0; // Problem: Subject knows about all observers! private PhoneApp phoneApp = null; private Website website = null; private Billboard billboard = null;
public void setTemperature(double temp) { this.temperature = temp; // Problem: Need to manually notify each observer if (phoneApp != null) { phoneApp.update(temp); } if (website != null) { website.update(temp); } if (billboard != null) { billboard.update(temp); } // Every time you add a new display, you need to modify this! }
// Getters and setters public void setPhoneApp(PhoneApp phoneApp) { this.phoneApp = phoneApp; }
public void setWebsite(Website website) { this.website = website; }
public void setBillboard(Billboard billboard) { this.billboard = billboard; }}
public class PhoneApp { public void update(double temperature) { System.out.println("📱 Phone: Temperature is " + temperature + "°C"); }}
public class Website { public void update(double temperature) { System.out.println("🌐 Website: Temperature is " + temperature + "°C"); }}
public class Billboard { public void update(double temperature) { System.out.println("📺 Billboard: Temperature is " + temperature + "°C"); }}
// Usagepublic class Main { public static void main(String[] args) { WeatherStation station = new WeatherStation(); station.setPhoneApp(new PhoneApp()); station.setWebsite(new Website()); station.setBillboard(new Billboard());
station.setTemperature(25.0); }}📱 Phone: Temperature is 25.0°C🌐 Website: Temperature is 25.0°C📺 Billboard: Temperature is 25.0°CProblems:
- WeatherStation knows about all display types (tight coupling)
- Need to modify
set_temperature()every time you add a new display - Hard to remove observers
- Violates Open/Closed Principle
Problems:
- WeatherStation knows about all display types (tight coupling)
- Need to modify
set_temperature()every time you add a new display - Hard to remove observers
- Violates Open/Closed Principle
The Solution: Observer Pattern
Section titled “The Solution: Observer Pattern”Class Structure
Section titled “Class Structure”classDiagram
class Subject {
<<abstract>>
+attach(observer) void
+detach(observer) void
+notify() void
}
class Observer {
<<abstract>>
+update(temperature) void
}
class WeatherStation {
-temperature: float
-observers: List~Observer~
+attach(observer) void
+detach(observer) void
+notify() void
+set_temperature(temp) void
+get_temperature() float
}
class PhoneApp {
+update(temperature) void
}
class Website {
+update(temperature) void
}
class Billboard {
+update(temperature) void
}
Subject <|-- WeatherStation : implements
Observer <|-- PhoneApp : implements
Observer <|-- Website : implements
Observer <|-- Billboard : implements
WeatherStation --> Observer : notifies
note for WeatherStation "Maintains list of observers<br/>Notifies all on state change"
note for Observer "All observers implement<br/>the same interface"
from abc import ABC, abstractmethodfrom typing import List
# Step 1: Define the Observer interfaceclass Observer(ABC): """Interface for all observers"""
@abstractmethod def update(self, temperature: float) -> None: """Called when subject's state changes""" pass
# Step 2: Define the Subject interfaceclass Subject(ABC): """Interface for subjects that can be observed"""
@abstractmethod def attach(self, observer: Observer) -> None: """Attach an observer""" pass
@abstractmethod def detach(self, observer: Observer) -> None: """Detach an observer""" pass
@abstractmethod def notify(self) -> None: """Notify all observers""" pass
# Step 3: Create concrete Subjectclass WeatherStation(Subject): """Weather station - the subject being observed"""
def __init__(self): self._temperature = 0 self._observers: List[Observer] = [] # List of observers
def attach(self, observer: Observer) -> None: """Subscribe an observer""" if observer not in self._observers: self._observers.append(observer) print(f"✅ Observer attached: {observer.__class__.__name__}")
def detach(self, observer: Observer) -> None: """Unsubscribe an observer""" if observer in self._observers: self._observers.remove(observer) print(f"❌ Observer detached: {observer.__class__.__name__}")
def notify(self) -> None: """Notify all observers about temperature change""" for observer in self._observers: observer.update(self._temperature)
def set_temperature(self, temperature: float) -> None: """Set temperature and notify all observers""" self._temperature = temperature print(f"\n🌡️ Temperature changed to {temperature}°C") self.notify() # Notify all observers!
def get_temperature(self) -> float: """Get current temperature""" return self._temperature
# Step 4: Create concrete Observersclass PhoneApp(Observer): """Phone app observer"""
def update(self, temperature: float) -> None: print(f"📱 Phone App: Temperature is {temperature}°C")
class Website(Observer): """Website observer"""
def update(self, temperature: float) -> None: print(f"🌐 Website: Temperature is {temperature}°C")
class Billboard(Observer): """Billboard observer"""
def update(self, temperature: float) -> None: print(f"📺 Billboard: Temperature is {temperature}°C")
# Step 5: Use the patterndef main(): # Create subject weather_station = WeatherStation()
# Create observers phone_app = PhoneApp() website = Website() billboard = Billboard()
# Subscribe observers weather_station.attach(phone_app) weather_station.attach(website) weather_station.attach(billboard)
# Change temperature - all observers get notified automatically! weather_station.set_temperature(25.0)
# Unsubscribe one observer weather_station.detach(website)
# Change temperature again - only subscribed observers get notified weather_station.set_temperature(30.0)
# Add a new observer - no need to modify WeatherStation! class SmartWatch(Observer): def update(self, temperature: float) -> None: print(f"⌚ Smart Watch: Temperature is {temperature}°C")
smart_watch = SmartWatch() weather_station.attach(smart_watch) weather_station.set_temperature(22.0)
if __name__ == "__main__": main()import java.util.ArrayList;import java.util.List;
// Step 1: Define the Observer interfacepublic interface Observer { /** * Called when subject's state changes */ void update(double temperature);}
// Step 2: Define the Subject interfacepublic interface Subject { /** * Attach an observer */ void attach(Observer observer);
/** * Detach an observer */ void detach(Observer observer);
/** * Notify all observers */ void notifyObservers();}
// Step 3: Create concrete Subjectpublic class WeatherStation implements Subject { private double temperature = 0; private List<Observer> observers = new ArrayList<>(); // List of observers
@Override public void attach(Observer observer) { if (!observers.contains(observer)) { observers.add(observer); System.out.println("✅ Observer attached: " + observer.getClass().getSimpleName()); } }
@Override public void detach(Observer observer) { if (observers.remove(observer)) { System.out.println("❌ Observer detached: " + observer.getClass().getSimpleName()); } }
@Override public void notifyObservers() { for (Observer observer : observers) { observer.update(temperature); } }
public void setTemperature(double temperature) { this.temperature = temperature; System.out.println("\n🌡️ Temperature changed to " + temperature + "°C"); notifyObservers(); // Notify all observers! }
public double getTemperature() { return temperature; }}
// Step 4: Create concrete Observerspublic class PhoneApp implements Observer { @Override public void update(double temperature) { System.out.println("📱 Phone App: Temperature is " + temperature + "°C"); }}
public class Website implements Observer { @Override public void update(double temperature) { System.out.println("🌐 Website: Temperature is " + temperature + "°C"); }}
public class Billboard implements Observer { @Override public void update(double temperature) { System.out.println("📺 Billboard: Temperature is " + temperature + "°C"); }}
// Step 5: Use the patternpublic class Main { public static void main(String[] args) { // Create subject WeatherStation weatherStation = new WeatherStation();
// Create observers PhoneApp phoneApp = new PhoneApp(); Website website = new Website(); Billboard billboard = new Billboard();
// Subscribe observers weatherStation.attach(phoneApp); weatherStation.attach(website); weatherStation.attach(billboard);
// Change temperature - all observers get notified automatically! weatherStation.setTemperature(25.0);
// Unsubscribe one observer weatherStation.detach(website);
// Change temperature again - only subscribed observers get notified weatherStation.setTemperature(30.0);
// Add a new observer - no need to modify WeatherStation! class SmartWatch implements Observer { @Override public void update(double temperature) { System.out.println("⌚ Smart Watch: Temperature is " + temperature + "°C"); } }
SmartWatch smartWatch = new SmartWatch(); weatherStation.attach(smartWatch); weatherStation.setTemperature(22.0); }}✅ Observer attached: PhoneApp✅ Observer attached: Website✅ Observer attached: Billboard
🌡️ Temperature changed to 25.0°C📱 Phone App: Temperature is 25.0°C🌐 Website: Temperature is 25.0°C📺 Billboard: Temperature is 25.0°C❌ Observer detached: Website
🌡️ Temperature changed to 30.0°C📱 Phone App: Temperature is 30.0°C📺 Billboard: Temperature is 30.0°C✅ Observer attached: SmartWatch
🌡️ Temperature changed to 22.0°C📱 Phone App: Temperature is 22.0°C📺 Billboard: Temperature is 22.0°C⌚ Smart Watch: Temperature is 22.0°CVisual Representation
Section titled “Visual Representation”Interaction Flow
Section titled “Interaction Flow”Here’s how the Observer Pattern works in practice - showing the sequence of notifications:
sequenceDiagram
participant Subject as WeatherStation
participant Observer1 as PhoneApp
participant Observer2 as Website
participant Observer3 as Billboard
Observer1->>Subject: attach(observer1)
Observer2->>Subject: attach(observer2)
Observer3->>Subject: attach(observer3)
Note over Subject: Observers registered
Subject->>Subject: set_temperature(25.0)
Subject->>Subject: notify()
Subject->>Observer1: update(25.0)
activate Observer1
Observer1-->>Subject: Display updated
deactivate Observer1
Subject->>Observer2: update(25.0)
activate Observer2
Observer2-->>Subject: Display updated
deactivate Observer2
Subject->>Observer3: update(25.0)
activate Observer3
Observer3-->>Subject: Display updated
deactivate Observer3
Note over Subject,Observer3: All observers notified<br/>automatically!
Real-World Software Example: E-Commerce Order System
Section titled “Real-World Software Example: E-Commerce Order System”Now let’s see a realistic software example - an e-commerce system where multiple services need to be notified when an order is placed.
The Problem
Section titled “The Problem”You’re building an e-commerce system. When an order is placed, you need to:
- Send confirmation email
- Update inventory
- Process payment
- Send notification to warehouse
- Update analytics
Without Observer Pattern:
# ❌ Without Observer Pattern
class Order: def __init__(self, order_id: str, items: list, total: float): self.order_id = order_id self.items = items self.total = total self.status = "pending" # Problem: Order knows about all services! self.email_service = None self.inventory_service = None self.payment_service = None self.warehouse_service = None self.analytics_service = None
def place_order(self): """Place order and notify all services""" self.status = "placed"
# Problem: Manual notification to each service if self.email_service: self.email_service.send_confirmation(self.order_id)
if self.inventory_service: self.inventory_service.update_inventory(self.items)
if self.payment_service: self.payment_service.process_payment(self.order_id, self.total)
if self.warehouse_service: self.warehouse_service.notify_warehouse(self.order_id, self.items)
if self.analytics_service: self.analytics_service.record_order(self.order_id, self.total)
# Problems: # - Order class knows about all services # - Need to modify place_order() to add new services # - Hard to test - need to mock all services # - Violates Single Responsibility Principle
class EmailService: def send_confirmation(self, order_id: str): print(f"📧 Email sent: Order {order_id} confirmation")
class InventoryService: def update_inventory(self, items: list): print(f"📦 Inventory updated for {len(items)} items")
class PaymentService: def process_payment(self, order_id: str, amount: float): print(f"💳 Payment processed: ${amount} for order {order_id}")
class WarehouseService: def notify_warehouse(self, order_id: str, items: list): print(f"🏭 Warehouse notified: Order {order_id} with {len(items)} items")
class AnalyticsService: def record_order(self, order_id: str, amount: float): print(f"📊 Analytics: Order {order_id} recorded, amount: ${amount}")
# Usageorder = Order("ORD-123", ["item1", "item2"], 99.99)order.email_service = EmailService()order.inventory_service = InventoryService()order.payment_service = PaymentService()order.warehouse_service = WarehouseService()order.analytics_service = AnalyticsService()
order.place_order()import java.util.List;
// ❌ Without Observer Pattern
public class Order { private String orderId; private List<String> items; private double total; private String status = "pending"; // Problem: Order knows about all services! private EmailService emailService = null; private InventoryService inventoryService = null; private PaymentService paymentService = null; private WarehouseService warehouseService = null; private AnalyticsService analyticsService = null;
public Order(String orderId, List<String> items, double total) { this.orderId = orderId; this.items = items; this.total = total; }
public void placeOrder() { // Place order and notify all services this.status = "placed";
// Problem: Manual notification to each service if (emailService != null) { emailService.sendConfirmation(orderId); }
if (inventoryService != null) { inventoryService.updateInventory(items); }
if (paymentService != null) { paymentService.processPayment(orderId, total); }
if (warehouseService != null) { warehouseService.notifyWarehouse(orderId, items); }
if (analyticsService != null) { analyticsService.recordOrder(orderId, total); }
// Problems: // - Order class knows about all services // - Need to modify placeOrder() to add new services // - Hard to test - need to mock all services // - Violates Single Responsibility Principle }
// Setters public void setEmailService(EmailService emailService) { this.emailService = emailService; }
public void setInventoryService(InventoryService inventoryService) { this.inventoryService = inventoryService; }
public void setPaymentService(PaymentService paymentService) { this.paymentService = paymentService; }
public void setWarehouseService(WarehouseService warehouseService) { this.warehouseService = warehouseService; }
public void setAnalyticsService(AnalyticsService analyticsService) { this.analyticsService = analyticsService; }}
public class EmailService { public void sendConfirmation(String orderId) { System.out.println("📧 Email sent: Order " + orderId + " confirmation"); }}
public class InventoryService { public void updateInventory(List<String> items) { System.out.println("📦 Inventory updated for " + items.size() + " items"); }}
public class PaymentService { public void processPayment(String orderId, double amount) { System.out.println("💳 Payment processed: $" + amount + " for order " + orderId); }}
public class WarehouseService { public void notifyWarehouse(String orderId, List<String> items) { System.out.println("🏭 Warehouse notified: Order " + orderId + " with " + items.size() + " items"); }}
public class AnalyticsService { public void recordOrder(String orderId, double amount) { System.out.println("📊 Analytics: Order " + orderId + " recorded, amount: $" + amount); }}
// Usagepublic class Main { public static void main(String[] args) { Order order = new Order("ORD-123", List.of("item1", "item2"), 99.99); order.setEmailService(new EmailService()); order.setInventoryService(new InventoryService()); order.setPaymentService(new PaymentService()); order.setWarehouseService(new WarehouseService()); order.setAnalyticsService(new AnalyticsService());
order.placeOrder(); }}📧 Email sent: Order ORD-123 confirmation📦 Inventory updated for 2 items💳 Payment processed: $99.99 for order ORD-123🏭 Warehouse notified: Order ORD-123 with 2 items📊 Analytics: Order ORD-123 recorded, amount: $99.99Problems:
- Order class knows about all services (tight coupling)
- Need to modify
place_order()to add new services - Hard to test - need to mock all services
- Violates Single Responsibility Principle
- Can’t easily add/remove services at runtime
The Solution: Observer Pattern
Section titled “The Solution: Observer Pattern”Class Structure
Section titled “Class Structure”classDiagram
class OrderSubject {
<<abstract>>
+attach(observer) void
+detach(observer) void
+notify() void
}
class OrderObserver {
<<abstract>>
+update(order) void
}
class Order {
-orderId: str
-items: List
-amount: float
-observers: List~OrderObserver~
+attach(observer) void
+detach(observer) void
+notify() void
+place_order() void
}
class EmailService {
+update(order) void
+send_confirmation(order) void
}
class InventoryService {
+update(order) void
+update_stock(order) void
}
class PaymentService {
+update(order) void
+process_payment(order) void
}
class WarehouseService {
+update(order) void
+prepare_shipment(order) void
}
class AnalyticsService {
+update(order) void
+record_order(order) void
}
OrderSubject <|-- Order : implements
OrderObserver <|-- EmailService : implements
OrderObserver <|-- InventoryService : implements
OrderObserver <|-- PaymentService : implements
OrderObserver <|-- WarehouseService : implements
OrderObserver <|-- AnalyticsService : implements
Order --> OrderObserver : notifies
note for Order "When order is placed,<br/>all services notified automatically"
note for OrderObserver "Each service handles<br/>its own responsibility"
from abc import ABC, abstractmethodfrom typing import List, Dict, Anyfrom dataclasses import dataclass
# Step 1: Define the Observer interfaceclass OrderObserver(ABC): """Interface for order observers"""
@abstractmethod def on_order_placed(self, order_data: Dict[str, Any]) -> None: """Called when an order is placed""" pass
# Step 2: Define the Subject interfaceclass OrderSubject(ABC): """Interface for order subjects"""
@abstractmethod def attach(self, observer: OrderObserver) -> None: """Attach an observer""" pass
@abstractmethod def detach(self, observer: OrderObserver) -> None: """Detach an observer""" pass
@abstractmethod def notify_order_placed(self, order_data: Dict[str, Any]) -> None: """Notify all observers about order placement""" pass
# Step 3: Create concrete Subject@dataclassclass Order: """Order data class""" order_id: str items: List[str] total: float status: str = "pending"
class OrderService(OrderSubject): """Order service - the subject being observed"""
def __init__(self): self._observers: List[OrderObserver] = []
def attach(self, observer: OrderObserver) -> None: """Subscribe an observer""" if observer not in self._observers: self._observers.append(observer) print(f"✅ Service subscribed: {observer.__class__.__name__}")
def detach(self, observer: OrderObserver) -> None: """Unsubscribe an observer""" if observer in self._observers: self._observers.remove(observer) print(f"❌ Service unsubscribed: {observer.__class__.__name__}")
def notify_order_placed(self, order_data: Dict[str, Any]) -> None: """Notify all observers about order placement""" for observer in self._observers: observer.on_order_placed(order_data)
def place_order(self, order: Order) -> None: """Place an order and notify all observers""" order.status = "placed" print(f"\n🛒 Order {order.order_id} placed!")
order_data = { "order_id": order.order_id, "items": order.items, "total": order.total, "status": order.status }
# Notify all observers automatically! self.notify_order_placed(order_data)
# Step 4: Create concrete Observersclass EmailService(OrderObserver): """Email service observer"""
def on_order_placed(self, order_data: Dict[str, Any]) -> None: order_id = order_data["order_id"] print(f"📧 Email sent: Order {order_id} confirmation")
class InventoryService(OrderObserver): """Inventory service observer"""
def on_order_placed(self, order_data: Dict[str, Any]) -> None: items = order_data["items"] print(f"📦 Inventory updated for {len(items)} items")
class PaymentService(OrderObserver): """Payment service observer"""
def on_order_placed(self, order_data: Dict[str, Any]) -> None: order_id = order_data["order_id"] amount = order_data["total"] print(f"💳 Payment processed: ${amount} for order {order_id}")
class WarehouseService(OrderObserver): """Warehouse service observer"""
def on_order_placed(self, order_data: Dict[str, Any]) -> None: order_id = order_data["order_id"] items = order_data["items"] print(f"🏭 Warehouse notified: Order {order_id} with {len(items)} items")
class AnalyticsService(OrderObserver): """Analytics service observer"""
def on_order_placed(self, order_data: Dict[str, Any]) -> None: order_id = order_data["order_id"] amount = order_data["total"] print(f"📊 Analytics: Order {order_id} recorded, amount: ${amount}")
# Step 5: Use the patterndef main(): # Create order service order_service = OrderService()
# Create services (observers) email_service = EmailService() inventory_service = InventoryService() payment_service = PaymentService() warehouse_service = WarehouseService() analytics_service = AnalyticsService()
# Subscribe all services order_service.attach(email_service) order_service.attach(inventory_service) order_service.attach(payment_service) order_service.attach(warehouse_service) order_service.attach(analytics_service)
# Place an order - all services get notified automatically! order = Order("ORD-123", ["Laptop", "Mouse"], 999.99) order_service.place_order(order)
# Add a new service - no need to modify OrderService! class NotificationService(OrderObserver): def on_order_placed(self, order_data: Dict[str, Any]) -> None: order_id = order_data["order_id"] print(f"🔔 Push notification sent: Order {order_id} placed")
notification_service = NotificationService() order_service.attach(notification_service)
# Place another order - new service also gets notified! order2 = Order("ORD-124", ["Keyboard"], 49.99) order_service.place_order(order2)
if __name__ == "__main__": main()import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;
// Step 1: Define the Observer interfacepublic interface OrderObserver { /** * Called when an order is placed */ void onOrderPlaced(Map<String, Object> orderData);}
// Step 2: Define the Subject interfacepublic interface OrderSubject { /** * Attach an observer */ void attach(OrderObserver observer);
/** * Detach an observer */ void detach(OrderObserver observer);
/** * Notify all observers about order placement */ void notifyOrderPlaced(Map<String, Object> orderData);}
// Step 3: Create concrete Subjectpublic class Order { private String orderId; private List<String> items; private double total; private String status = "pending";
public Order(String orderId, List<String> items, double total) { this.orderId = orderId; this.items = items; this.total = total; }
// Getters and setters public String getOrderId() { return orderId; } public List<String> getItems() { return items; } public double getTotal() { return total; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; }}
public class OrderService implements OrderSubject { private List<OrderObserver> observers = new ArrayList<>();
@Override public void attach(OrderObserver observer) { if (!observers.contains(observer)) { observers.add(observer); System.out.println("✅ Service subscribed: " + observer.getClass().getSimpleName()); } }
@Override public void detach(OrderObserver observer) { if (observers.remove(observer)) { System.out.println("❌ Service unsubscribed: " + observer.getClass().getSimpleName()); } }
@Override public void notifyOrderPlaced(Map<String, Object> orderData) { for (OrderObserver observer : observers) { observer.onOrderPlaced(orderData); } }
public void placeOrder(Order order) { order.setStatus("placed"); System.out.println("\n🛒 Order " + order.getOrderId() + " placed!");
Map<String, Object> orderData = new HashMap<>(); orderData.put("order_id", order.getOrderId()); orderData.put("items", order.getItems()); orderData.put("total", order.getTotal()); orderData.put("status", order.getStatus());
// Notify all observers automatically! notifyOrderPlaced(orderData); }}
// Step 4: Create concrete Observerspublic class EmailService implements OrderObserver { @Override public void onOrderPlaced(Map<String, Object> orderData) { String orderId = (String) orderData.get("order_id"); System.out.println("📧 Email sent: Order " + orderId + " confirmation"); }}
public class InventoryService implements OrderObserver { @Override public void onOrderPlaced(Map<String, Object> orderData) { @SuppressWarnings("unchecked") List<String> items = (List<String>) orderData.get("items"); System.out.println("📦 Inventory updated for " + items.size() + " items"); }}
public class PaymentService implements OrderObserver { @Override public void onOrderPlaced(Map<String, Object> orderData) { String orderId = (String) orderData.get("order_id"); double amount = (Double) orderData.get("total"); System.out.println("💳 Payment processed: $" + amount + " for order " + orderId); }}
public class WarehouseService implements OrderObserver { @Override public void onOrderPlaced(Map<String, Object> orderData) { String orderId = (String) orderData.get("order_id"); @SuppressWarnings("unchecked") List<String> items = (List<String>) orderData.get("items"); System.out.println("🏭 Warehouse notified: Order " + orderId + " with " + items.size() + " items"); }}
public class AnalyticsService implements OrderObserver { @Override public void onOrderPlaced(Map<String, Object> orderData) { String orderId = (String) orderData.get("order_id"); double amount = (Double) orderData.get("total"); System.out.println("📊 Analytics: Order " + orderId + " recorded, amount: $" + amount); }}
// Step 5: Use the patternpublic class Main { public static void main(String[] args) { // Create order service OrderService orderService = new OrderService();
// Create services (observers) EmailService emailService = new EmailService(); InventoryService inventoryService = new InventoryService(); PaymentService paymentService = new PaymentService(); WarehouseService warehouseService = new WarehouseService(); AnalyticsService analyticsService = new AnalyticsService();
// Subscribe all services orderService.attach(emailService); orderService.attach(inventoryService); orderService.attach(paymentService); orderService.attach(warehouseService); orderService.attach(analyticsService);
// Place an order - all services get notified automatically! Order order = new Order("ORD-123", List.of("Laptop", "Mouse"), 999.99); orderService.placeOrder(order);
// Add a new service - no need to modify OrderService! class NotificationService implements OrderObserver { @Override public void onOrderPlaced(Map<String, Object> orderData) { String orderId = (String) orderData.get("order_id"); System.out.println("🔔 Push notification sent: Order " + orderId + " placed"); } }
NotificationService notificationService = new NotificationService(); orderService.attach(notificationService);
// Place another order - new service also gets notified! Order order2 = new Order("ORD-124", List.of("Keyboard"), 49.99); orderService.placeOrder(order2); }}✅ Service subscribed: EmailService✅ Service subscribed: InventoryService✅ Service subscribed: PaymentService✅ Service subscribed: WarehouseService✅ Service subscribed: AnalyticsService
🛒 Order ORD-123 placed!📧 Email sent: Order ORD-123 confirmation📦 Inventory updated for 2 items💳 Payment processed: $999.99 for order ORD-123🏭 Warehouse notified: Order ORD-123 with 2 items📊 Analytics: Order ORD-123 recorded, amount: $999.99✅ Service subscribed: NotificationService
🛒 Order ORD-124 placed!📧 Email sent: Order ORD-124 confirmation📦 Inventory updated for 1 items💳 Payment processed: $49.99 for order ORD-124🏭 Warehouse notified: Order ORD-124 with 1 items📊 Analytics: Order ORD-124 recorded, amount: $49.99🔔 Push notification sent: Order ORD-124 placedAdding a New Service
Section titled “Adding a New Service”With Observer Pattern, adding a new service is super easy:
# Step 1: Create the new service observerclass LoyaltyPointsService(OrderObserver): """Loyalty points service observer"""
def on_order_placed(self, order_data: Dict[str, Any]) -> None: order_id = order_data["order_id"] amount = order_data["total"] points = int(amount * 0.1) # 10% of order value print(f"🎁 Loyalty points added: {points} points for order {order_id}")
# Step 2: Subscribe it (that's it!)loyalty_service = LoyaltyPointsService()order_service.attach(loyalty_service)
# Now it automatically gets notified for all future orders!# No changes needed to OrderService or place_order() method!// Step 1: Create the new service observerpublic class LoyaltyPointsService implements OrderObserver { /** * Loyalty points service observer */ @Override public void onOrderPlaced(Map<String, Object> orderData) { String orderId = (String) orderData.get("order_id"); double amount = (Double) orderData.get("total"); int points = (int) (amount * 0.1); // 10% of order value System.out.println("🎁 Loyalty points added: " + points + " points for order " + orderId); }}
// Step 2: Subscribe it (that's it!)LoyaltyPointsService loyaltyService = new LoyaltyPointsService();orderService.attach(loyaltyService);
// Now it automatically gets notified for all future orders!// No changes needed to OrderService or placeOrder() method!Observer Pattern Variants
Section titled “Observer Pattern Variants”There are several variations of the Observer Pattern:
1. Push Model (What We Used)
Section titled “1. Push Model (What We Used)”Subject sends all data to observers:
class Observer(ABC): @abstractmethod def update(self, data: Dict[str, Any]) -> None: pass
# Subject pushes all datadef notify(self): for observer in self._observers: observer.update({ "temperature": self._temperature, "humidity": self._humidity, "pressure": self._pressure })public interface Observer { void update(Map<String, Object> data);}
// Subject pushes all datapublic void notifyObservers() { Map<String, Object> data = new HashMap<>(); data.put("temperature", this.temperature); data.put("humidity", this.humidity); data.put("pressure", this.pressure);
for (Observer observer : observers) { observer.update(data); }}Pros: Observers get all data
Cons: Observers might not need all data
2. Pull Model
Section titled “2. Pull Model”Observers pull data they need from subject:
class Observer(ABC): @abstractmethod def update(self, subject: Subject) -> None: # Observer pulls what it needs temp = subject.get_temperature() humidity = subject.get_humidity() pass
# Subject just notifies, observers pull what they needdef notify(self): for observer in self._observers: observer.update(self) # Pass subject referencepublic interface Observer { void update(Subject subject);}
// Observer pulls what it needspublic class ConcreteObserver implements Observer { @Override public void update(Subject subject) { double temp = subject.getTemperature(); double humidity = subject.getHumidity(); // Use the data... }}
// Subject just notifies, observers pull what they needpublic void notifyObservers() { for (Observer observer : observers) { observer.update(this); // Pass subject reference }}Pros: Observers get only what they need
Cons: Observers need to know subject’s interface
3. Event-Based Observer
Section titled “3. Event-Based Observer”Using events instead of direct method calls:
from typing import Callable
class EventObserver: """Event-based observer using callbacks"""
def __init__(self): self._callbacks: List[Callable] = []
def subscribe(self, callback: Callable) -> None: self._callbacks.append(callback)
def notify(self, event_data: Dict[str, Any]) -> None: for callback in self._callbacks: callback(event_data)
# Usagedef on_order_placed(data): print(f"Order {data['order_id']} placed!")
observer = EventObserver()observer.subscribe(on_order_placed)observer.notify({"order_id": "ORD-123"})import java.util.function.Consumer;import java.util.ArrayList;import java.util.List;import java.util.Map;
public class EventObserver { /** * Event-based observer using callbacks */ private List<Consumer<Map<String, Object>>> callbacks = new ArrayList<>();
public void subscribe(Consumer<Map<String, Object>> callback) { callbacks.add(callback); }
public void notify(Map<String, Object> eventData) { for (Consumer<Map<String, Object>> callback : callbacks) { callback.accept(eventData); } }}
// Usagepublic class Main { public static void main(String[] args) { EventObserver observer = new EventObserver(); observer.subscribe(data -> { String orderId = (String) data.get("order_id"); System.out.println("Order " + orderId + " placed!"); });
Map<String, Object> eventData = new HashMap<>(); eventData.put("order_id", "ORD-123"); observer.notify(eventData); }}When to Use Observer Pattern?
Section titled “When to Use Observer Pattern?”Use Observer Pattern when:
✅ One object changes and multiple objects need to be notified
✅ You want loose coupling - Subject shouldn’t know about observers
✅ You need dynamic relationships - Add/remove observers at runtime
✅ You want to avoid polling - No constant checking for changes
✅ You have one-to-many dependency - One subject, many observers
✅ You’re following Open/Closed Principle - Open for extension, closed for modification
When NOT to Use Observer Pattern?
Section titled “When NOT to Use Observer Pattern?”Don’t use Observer Pattern when:
❌ Simple one-to-one communication - Direct method call is simpler
❌ Performance is critical - Observer adds overhead (usually negligible)
❌ Order of notifications matters - Observers might execute in unpredictable order
❌ Observers need to modify subject - Can create circular dependencies
❌ You have few, stable observers - Overhead might not be worth it
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”Mistake 1: Observers Modifying Subject
Section titled “Mistake 1: Observers Modifying Subject”# ❌ Observer modifying subject - can cause infinite loops!class BadObserver(Observer): def update(self, temperature: float): if temperature > 30: self.subject.set_temperature(25) # Modifying subject! # This triggers notify() again, which calls update() again... infinite loop!
# ✅ Better: Observer should only react, not modifyclass GoodObserver(Observer): def update(self, temperature: float): if temperature > 30: self.send_alert() # Just react, don't modify subject// ❌ Observer modifying subject - can cause infinite loops!public class BadObserver implements Observer { private Subject subject;
public BadObserver(Subject subject) { this.subject = subject; }
@Override public void update(double temperature) { if (temperature > 30) { subject.setTemperature(25); // Modifying subject! // This triggers notify() again, which calls update() again... infinite loop! } }}
// ✅ Better: Observer should only react, not modifypublic class GoodObserver implements Observer { @Override public void update(double temperature) { if (temperature > 30) { sendAlert(); // Just react, don't modify subject } }
private void sendAlert() { // Send alert logic }}Mistake 2: Not Handling Observer Errors
Section titled “Mistake 2: Not Handling Observer Errors”# ❌ If one observer fails, others don't get notifieddef notify(self): for observer in self._observers: observer.update(self._temperature) # If this fails, loop stops!
# ✅ Better: Handle errors so all observers get notifieddef notify(self): for observer in self._observers: try: observer.update(self._temperature) except Exception as e: print(f"Error notifying {observer}: {e}") # Continue with other observers// ❌ If one observer fails, others don't get notifiedpublic void notifyObservers() { for (Observer observer : observers) { observer.update(temperature); // If this fails, loop stops! }}
// ✅ Better: Handle errors so all observers get notifiedpublic void notifyObservers() { for (Observer observer : observers) { try { observer.update(temperature); } catch (Exception e) { System.err.println("Error notifying " + observer + ": " + e.getMessage()); // Continue with other observers } }}Mistake 3: Memory Leaks (Not Unsubscribing)
Section titled “Mistake 3: Memory Leaks (Not Unsubscribing)”# ❌ Observer holds reference to subject, subject holds reference to observer# If observer is deleted but not unsubscribed, memory leak!
class BadObserver(Observer): def __init__(self, subject: Subject): self.subject = subject subject.attach(self) # Attached but never detached
# ✅ Better: Always unsubscribe when doneclass GoodObserver(Observer): def __init__(self, subject: Subject): self.subject = subject subject.attach(self)
def cleanup(self): self.subject.detach(self) # Always clean up!// ❌ Observer holds reference to subject, subject holds reference to observer// If observer is deleted but not unsubscribed, memory leak!
public class BadObserver implements Observer { private Subject subject;
public BadObserver(Subject subject) { this.subject = subject; subject.attach(this); // Attached but never detached }}
// ✅ Better: Always unsubscribe when donepublic class GoodObserver implements Observer { private Subject subject;
public GoodObserver(Subject subject) { this.subject = subject; subject.attach(this); }
public void cleanup() { subject.detach(this); // Always clean up! }}Benefits of Observer Pattern
Section titled “Benefits of Observer Pattern”- Loose Coupling - Subject doesn’t depend on concrete observer classes
- Dynamic Relationships - Observers can be added/removed at runtime
- Open/Closed Principle - Easy to add observers without modifying subject
- Broadcast Communication - One notification reaches all observers
- No Polling - Observers don’t need to constantly check for changes
- Follows SOLID Principles - Especially Open/Closed and Dependency Inversion
Revision: Quick Catch-Up
Section titled “Revision: Quick Catch-Up”What is Observer Pattern?
Section titled “What is Observer Pattern?”Observer Pattern is a behavioral design pattern that defines a one-to-many dependency between objects. When one object (subject) changes state, all dependent objects (observers) are notified and updated automatically.
Why Use It?
Section titled “Why Use It?”- ✅ Loose coupling - Subject doesn’t know about specific observers
- ✅ Dynamic subscription - Add/remove observers at runtime
- ✅ Broadcast communication - One change notifies all observers
- ✅ Avoid polling - No need to constantly check for changes
- ✅ Follow Open/Closed Principle
How It Works?
Section titled “How It Works?”- Define Observer interface - What all observers can do
- Define Subject interface - Methods to attach/detach/notify
- Create concrete Subject - Maintains list of observers
- Create concrete Observers - React to subject changes
- Subscribe observers - Attach observers to subject
- Notify on change - Subject notifies all observers when state changes
Key Components
Section titled “Key Components”Subject → attach/detach → ObserversSubject → notify() → Observer.update()- Subject - The object being observed (has state)
- Observer - Objects that watch the subject
- attach() - Subscribe an observer
- detach() - Unsubscribe an observer
- notify() - Notify all observers
- update() - Observer’s reaction method
Simple Example
Section titled “Simple Example”# Observer interfaceclass Observer(ABC): @abstractmethod def update(self, data): pass
# Subjectclass Subject: def __init__(self): self._observers = [] self._state = None
def attach(self, observer): self._observers.append(observer)
def notify(self): for observer in self._observers: observer.update(self._state)
# Concrete Observerclass ConcreteObserver(Observer): def update(self, data): print(f"Updated: {data}")
# Usagesubject = Subject()observer = ConcreteObserver()subject.attach(observer)subject.notify()// Observer interfacepublic interface Observer { void update(Object data);}
// Subjectpublic class Subject { private List<Observer> observers = new ArrayList<>(); private Object state = null;
public void attach(Observer observer) { observers.add(observer); }
public void notifyObservers() { for (Observer observer : observers) { observer.update(state); } }}
// Concrete Observerpublic class ConcreteObserver implements Observer { @Override public void update(Object data) { System.out.println("Updated: " + data); }}
// Usagepublic class Main { public static void main(String[] args) { Subject subject = new Subject(); Observer observer = new ConcreteObserver(); subject.attach(observer); subject.notifyObservers(); }}When to Use?
Section titled “When to Use?”✅ One-to-many dependency
✅ Subject changes and multiple objects need notification
✅ Want loose coupling
✅ Need dynamic add/remove observers
✅ Want to avoid polling
When NOT to Use?
Section titled “When NOT to Use?”❌ Simple one-to-one communication
❌ Performance is critical
❌ Order of notifications matters
❌ Few, stable observers
Key Takeaways
Section titled “Key Takeaways”- Observer Pattern = One subject, many observers
- Subject = Object being watched
- Observer = Objects watching the subject
- Benefit = Loose coupling, dynamic relationships
- Principle = Open for extension, closed for modification
Common Pattern Structure
Section titled “Common Pattern Structure”# 1. Observer Interfaceclass Observer(ABC): @abstractmethod def update(self, data): pass
# 2. Subject Interfaceclass Subject(ABC): @abstractmethod def attach(self, observer): pass
@abstractmethod def detach(self, observer): pass
@abstractmethod def notify(self): pass
# 3. Concrete Subjectclass ConcreteSubject(Subject): def __init__(self): self._observers = [] self._state = None
def attach(self, observer): self._observers.append(observer)
def notify(self): for observer in self._observers: observer.update(self._state)
# 4. Concrete Observerclass ConcreteObserver(Observer): def update(self, data): # React to change pass
# 5. Usagesubject = ConcreteSubject()observer = ConcreteObserver()subject.attach(observer)subject.notify()// 1. Observer Interfacepublic interface Observer { void update(Object data);}
// 2. Subject Interfacepublic interface Subject { void attach(Observer observer); void detach(Observer observer); void notifyObservers();}
// 3. Concrete Subjectpublic class ConcreteSubject implements Subject { private List<Observer> observers = new ArrayList<>(); private Object state = null;
@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(state); } }}
// 4. Concrete Observerpublic class ConcreteObserver implements Observer { @Override public void update(Object data) { // React to change }}
// 5. Usagepublic class Main { public static void main(String[] args) { Subject subject = new ConcreteSubject(); Observer observer = new ConcreteObserver(); subject.attach(observer); subject.notifyObservers(); }}Remember
Section titled “Remember”- Observer Pattern decouples subject from observers
- It enables dynamic relationships - add/remove at runtime
- It follows Open/Closed Principle - easy to extend
- Use it when you need one-to-many notification
- Don’t use it for simple one-to-one cases - avoid over-engineering!
Interview Focus: Observer Pattern
Section titled “Interview Focus: Observer Pattern”Key Points to Remember
Section titled “Key Points to Remember”1. Core Concept
Section titled “1. Core Concept”What to say:
“Observer Pattern is a behavioral design pattern that defines a one-to-many dependency between objects. When the subject (the object being observed) changes state, all its observers are automatically notified and updated.”
Why it matters:
- Shows you understand the fundamental purpose
- Demonstrates knowledge of behavioral patterns category
- Indicates you can explain concepts clearly
2. When to Use Observer Pattern
Section titled “2. When to Use Observer Pattern”Must mention:
- ✅ One-to-many dependency - One subject, many observers
- ✅ Loose coupling - Subject shouldn’t know about specific observers
- ✅ Dynamic relationships - Add/remove observers at runtime
- ✅ Avoid polling - No need to constantly check for changes
- ✅ Event-driven architecture - React to events automatically
Example scenario to give:
“I’d use Observer Pattern when building a stock price monitoring system. When a stock price changes, multiple components need to react - the UI needs to update, alerts need to be sent, analytics need to be recorded. With Observer Pattern, the stock service just notifies all observers, and each handles the update differently.”
3. Structure and Components
Section titled “3. Structure and Components”Must explain:
- Subject (Observable) - The object being observed, maintains list of observers
- Observer - Interface for objects that watch the subject
- Concrete Subject - Specific subject implementation
- Concrete Observer - Specific observer implementations
- attach() - Subscribe an observer
- detach() - Unsubscribe an observer
- notify() - Notify all observers
- update() - Observer’s reaction method
Visual explanation:
Subject → attach(observer) → Observer listSubject → notify() → Observer.update()4. Benefits and Trade-offs
Section titled “4. Benefits and Trade-offs”Benefits to mention:
- Loose Coupling - Subject doesn’t depend on concrete observer classes
- Dynamic Relationships - Observers can be added/removed at runtime
- Open/Closed Principle - Easy to add observers without modifying subject
- Broadcast Communication - One notification reaches all observers
- No Polling - Observers don’t need to constantly check
Trade-offs to acknowledge:
- Performance - Notifying many observers can be slow
- Order of execution - Observers might execute in unpredictable order
- Memory leaks - Need to properly unsubscribe observers
- Debugging difficulty - Hard to trace notification flow
5. Common Interview Questions
Section titled “5. Common Interview Questions”Q: “What’s the difference between Observer Pattern and Pub-Sub Pattern?”
A:
“Observer Pattern has direct communication - the subject knows about observers and calls their update methods directly. Pub-Sub (Publish-Subscribe) Pattern uses a message broker - publishers and subscribers don’t know about each other, they communicate through a broker. Pub-Sub is more decoupled but adds complexity.”
Q: “How do you handle errors in Observer Pattern?”
A:
“I wrap each observer notification in a try-except block. If one observer fails, I log the error and continue notifying other observers. This ensures that one failing observer doesn’t prevent others from being notified. I might also implement a retry mechanism or dead-letter queue for critical observers.”
Q: “How does Observer Pattern relate to SOLID principles?”
A:
“Observer Pattern primarily supports the Open/Closed Principle - you can add new observers without modifying the subject. It also supports Dependency Inversion Principle by making subjects depend on the Observer abstraction rather than concrete observer classes. Additionally, it helps with Single Responsibility Principle by separating the subject’s core logic from notification logic.”
6. Implementation Details
Section titled “6. Implementation Details”Key implementation points:
-
Use Abstract Base Class (ABC) for observer interface
from abc import ABC, abstractmethodclass Observer(ABC):@abstractmethoddef update(self, data): pass -
Maintain observer list - Use a list or set to store observers
def __init__(self):self._observers: List[Observer] = [] -
Handle duplicate subscriptions - Check if observer already exists
def attach(self, observer: Observer):if observer not in self._observers:self._observers.append(observer) -
Error handling - Wrap notifications in try-except
def notify(self):for observer in self._observers:try:observer.update(self._state)except Exception as e:logger.error(f"Error notifying {observer}: {e}")
7. Real-World Examples
Section titled “7. Real-World Examples”Good examples to mention:
- Model-View-Controller (MVC) - Model notifies views when data changes
- Event-driven systems - UI events, button clicks, form submissions
- Stock market - Stock prices notify multiple displays
- Logging systems - Log events notify multiple handlers
- Notification systems - Order placed notifies email, SMS, push services
- GUI frameworks - Widgets notify listeners on events
8. Common Mistakes to Avoid
Section titled “8. Common Mistakes to Avoid”Mistakes interviewers watch for:
-
Memory leaks - Not unsubscribing observers
- ❌ Bad: Attach observer, never detach
- ✅ Good: Always detach when observer is no longer needed
-
Observers modifying subject - Can cause infinite loops
- ❌ Bad: Observer calls subject.set_state() in update()
- ✅ Good: Observer only reacts, doesn’t modify subject
-
No error handling - One failing observer stops all notifications
- ❌ Bad: No try-except in notify()
- ✅ Good: Wrap each notification in try-except
-
Order dependencies - Assuming observers execute in specific order
- ❌ Bad: Observer A depends on Observer B executing first
- ✅ Good: Observers are independent
9. Comparison with Other Patterns
Section titled “9. Comparison with Other Patterns”Observer vs Strategy:
- Observer - One subject notifies many observers about changes
- Strategy - One context uses one strategy at a time (algorithm selection)
Observer vs Mediator:
- Observer - Direct communication, subject knows about observers
- Mediator - Centralized communication through mediator
Observer vs Chain of Responsibility:
- Observer - All observers get notified
- Chain of Responsibility - Request passes through chain until handled
10. Code Quality Points
Section titled “10. Code Quality Points”What interviewers look for:
✅ Clean code - Readable, well-structured
✅ Type hints - Proper type annotations
✅ Error handling - Handle observer failures gracefully
✅ Memory management - Proper cleanup, avoid leaks
✅ SOLID principles - Follows design principles
✅ Testability - Easy to test and mock
Example of good code:
from abc import ABC, abstractmethodfrom typing import List, Dict, Anyimport logging
logger = logging.getLogger(__name__)
class Observer(ABC): """Interface for observers""" @abstractmethod def update(self, data: Dict[str, Any]) -> None: """Called when subject's state changes""" pass
class Subject(ABC): """Interface for subjects""" @abstractmethod def attach(self, observer: Observer) -> None: """Attach an observer""" pass
@abstractmethod def detach(self, observer: Observer) -> None: """Detach an observer""" pass
@abstractmethod def notify(self) -> None: """Notify all observers""" pass
class WeatherStation(Subject): """Weather station subject"""
def __init__(self): self._temperature = 0 self._observers: List[Observer] = []
def attach(self, observer: Observer) -> None: """Subscribe an observer""" if observer not in self._observers: self._observers.append(observer) logger.info(f"Observer attached: {observer.__class__.__name__}")
def detach(self, observer: Observer) -> None: """Unsubscribe an observer""" if observer in self._observers: self._observers.remove(observer) logger.info(f"Observer detached: {observer.__class__.__name__}")
def notify(self) -> None: """Notify all observers with error handling""" data = {"temperature": self._temperature} for observer in self._observers: try: observer.update(data) except Exception as e: logger.error(f"Error notifying {observer}: {e}") # Continue with other observers
def set_temperature(self, temperature: float) -> None: """Set temperature and notify observers""" self._temperature = temperature self.notify()import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.logging.Logger;
public interface Observer { /** * Called when subject's state changes */ void update(Map<String, Object> data);}
public interface Subject { /** * Attach an observer */ void attach(Observer observer);
/** * Detach an observer */ void detach(Observer observer);
/** * Notify all observers */ void notifyObservers();}
public class WeatherStation implements Subject { private static final Logger logger = Logger.getLogger(WeatherStation.class.getName());
private double temperature = 0; private List<Observer> observers = new ArrayList<>();
@Override public void attach(Observer observer) { // Subscribe an observer if (!observers.contains(observer)) { observers.add(observer); logger.info("Observer attached: " + observer.getClass().getSimpleName()); } }
@Override public void detach(Observer observer) { // Unsubscribe an observer if (observers.remove(observer)) { logger.info("Observer detached: " + observer.getClass().getSimpleName()); } }
@Override public void notifyObservers() { // Notify all observers with error handling Map<String, Object> data = new HashMap<>(); data.put("temperature", temperature);
for (Observer observer : observers) { try { observer.update(data); } catch (Exception e) { logger.severe("Error notifying " + observer + ": " + e.getMessage()); // Continue with other observers } } }
public void setTemperature(double temperature) { // Set temperature and notify observers this.temperature = temperature; notifyObservers(); }}Interview Checklist
Section titled “Interview Checklist”Before your interview, make sure you can:
- Define Observer Pattern clearly in one sentence
- Explain when to use it (with examples)
- Describe the structure and components
- List benefits and trade-offs
- Compare with other behavioral patterns
- Implement Observer Pattern from scratch
- Handle errors and memory leaks
- Connect to SOLID principles
- Identify when NOT to use it
- Give 2-3 real-world examples
- Discuss common mistakes and how to avoid them
Remember: Observer Pattern is about keeping objects informed automatically - when one object changes, all observers get notified without tight coupling! 👀