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); }}// ❌ Without Observer Pattern - Tight coupling
class WeatherStation { private temperature: number = 0; // Problem: Subject knows about all observers! private phoneApp: PhoneApp | null = null; private website: Website | null = null; private billboard: Billboard | null = null;
setTemperature(temp: number): void { this.temperature = temp; // Problem: Need to manually notify each observer if (this.phoneApp) { this.phoneApp.update(temp); } if (this.website) { this.website.update(temp); } if (this.billboard) { this.billboard.update(temp); } // Every time you add a new display, you need to modify this! }
setPhoneApp(phoneApp: PhoneApp): void { this.phoneApp = phoneApp; }
setWebsite(website: Website): void { this.website = website; }
setBillboard(billboard: Billboard): void { this.billboard = billboard; }}
class PhoneApp { update(temperature: number): void { console.log(`📱 Phone: Temperature is ${temperature}°C`); }}
class Website { update(temperature: number): void { console.log(`🌐 Website: Temperature is ${temperature}°C`); }}
class Billboard { update(temperature: number): void { console.log(`📺 Billboard: Temperature is ${temperature}°C`); }}
// Usageconst station = new WeatherStation();station.setPhoneApp(new PhoneApp());station.setWebsite(new Website());station.setBillboard(new Billboard());
station.setTemperature(25.0);// ❌ Without Observer Pattern - Tight coupling
#include <iostream>#include <memory>
class PhoneApp {public: void update(double temperature) { std::cout << "📱 Phone: Temperature is " << temperature << "°C" << std::endl; }};
class Website {public: void update(double temperature) { std::cout << "🌐 Website: Temperature is " << temperature << "°C" << std::endl; }};
class Billboard {public: void update(double temperature) { std::cout << "📺 Billboard: Temperature is " << temperature << "°C" << std::endl; }};
class WeatherStation {private: double temperature = 0; // Problem: Subject knows about all observers! PhoneApp* phoneApp = nullptr; Website* website = nullptr; Billboard* billboard = nullptr;
public: void setTemperature(double temp) { this->temperature = temp; // Problem: Need to manually notify each observer if (phoneApp) { phoneApp->update(temp); } if (website) { website->update(temp); } if (billboard) { billboard->update(temp); } // Every time you add a new display, you need to modify this! }
void setPhoneApp(PhoneApp* app) { this->phoneApp = app; }
void setWebsite(Website* site) { this->website = site; }
void setBillboard(Billboard* board) { this->billboard = board; }};
// Usageint main() { WeatherStation station; PhoneApp phoneApp; Website website; Billboard billboard;
station.setPhoneApp(&phoneApp); station.setWebsite(&website); station.setBillboard(&billboard);
station.setTemperature(25.0);
return 0;}// ❌ Without Observer Pattern - Tight coupling
using System;
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! }
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) { Console.WriteLine($"📱 Phone: Temperature is {temperature}°C"); }}
public class Website{ public void Update(double temperature) { Console.WriteLine($"🌐 Website: Temperature is {temperature}°C"); }}
public class Billboard{ public void Update(double temperature) { Console.WriteLine($"📺 Billboard: Temperature is {temperature}°C"); }}
// Usageclass Program{ static void Main() { WeatherStation station = new WeatherStation(); station.SetPhoneApp(new PhoneApp()); station.SetWebsite(new Website()); station.SetBillboard(new Billboard());
station.SetTemperature(25.0); }}package main
import "fmt"
// ❌ Without Observer Pattern - Tight coupling
type PhoneApp struct{}type Website struct{}type Billboard struct{}
func (p *PhoneApp) Update(temp float64) { fmt.Printf("📱 Phone: Temperature is %.1f°C\n", temp) }func (w *Website) Update(temp float64) { fmt.Printf("🌐 Website: Temperature is %.1f°C\n", temp) }func (b *Billboard) Update(temp float64) { fmt.Printf("📺 Billboard: Temperature is %.1f°C\n", temp) }
type WeatherStation struct { temperature float64 phoneApp *PhoneApp website *Website billboard *Billboard}
func (ws *WeatherStation) SetTemperature(temp float64) { ws.temperature = temp // Problem: Need to manually notify each observer if ws.phoneApp != nil { ws.phoneApp.Update(temp) } if ws.website != nil { ws.website.Update(temp) } if ws.billboard != nil { ws.billboard.Update(temp) }}
func main() { station := &WeatherStation{} station.phoneApp = &PhoneApp{} station.website = &Website{} station.billboard = &Billboard{} station.SetTemperature(25.0)}struct OrderService;
impl OrderService { fn place_order(&self, order_id: u32) { println!("Order {} placed", order_id); println!("Sending email"); println!("Updating analytics"); println!("Notifying warehouse"); }}📱 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”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); }}// Step 1: Define the Observer interfaceinterface Observer { /** Called when subject's state changes */ update(temperature: number): void;}
// Step 2: Define the Subject interfaceinterface Subject { /** Attach an observer */ attach(observer: Observer): void; /** Detach an observer */ detach(observer: Observer): void; /** Notify all observers */ notify(): void;}
// Step 3: Create concrete Subjectclass WeatherStation implements Subject { /** Weather station - the subject being observed */ private temperature: number = 0; private observers: Observer[] = []; // List of observers
attach(observer: Observer): void { /** Subscribe an observer */ if (!this.observers.includes(observer)) { this.observers.push(observer); console.log(`✅ Observer attached: ${observer.constructor.name}`); } }
detach(observer: Observer): void { /** Unsubscribe an observer */ const index = this.observers.indexOf(observer); if (index > -1) { this.observers.splice(index, 1); console.log(`❌ Observer detached: ${observer.constructor.name}`); } }
notify(): void { /** Notify all observers about temperature change */ for (const observer of this.observers) { observer.update(this.temperature); } }
setTemperature(temperature: number): void { /** Set temperature and notify all observers */ this.temperature = temperature; console.log(`\n🌡️ Temperature changed to ${temperature}°C`); this.notify(); // Notify all observers! }
getTemperature(): number { /** Get current temperature */ return this.temperature; }}
// Step 4: Create concrete Observersclass PhoneApp implements Observer { /** Phone app observer */ update(temperature: number): void { console.log(`📱 Phone App: Temperature is ${temperature}°C`); }}
class Website implements Observer { /** Website observer */ update(temperature: number): void { console.log(`🌐 Website: Temperature is ${temperature}°C`); }}
class Billboard implements Observer { /** Billboard observer */ update(temperature: number): void { console.log(`📺 Billboard: Temperature is ${temperature}°C`); }}
// Step 5: Use the patternfunction main(): void { // Create subject const weatherStation = new WeatherStation();
// Create observers const phoneApp = new PhoneApp(); const website = new Website(); const 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 { update(temperature: number): void { console.log(`⌚ Smart Watch: Temperature is ${temperature}°C`); } }
const smartWatch = new SmartWatch(); weatherStation.attach(smartWatch); weatherStation.setTemperature(22.0);}
main();#include <iostream>#include <vector>#include <algorithm>#include <string>
// Step 1: Define the Observer interfaceclass Observer {public: virtual ~Observer() = default; /** Called when subject's state changes */ virtual void update(double temperature) = 0; virtual std::string getName() const = 0;};
// Step 2: Define the Subject interfaceclass Subject {public: virtual ~Subject() = default; /** Attach an observer */ virtual void attach(Observer* observer) = 0; /** Detach an observer */ virtual void detach(Observer* observer) = 0; /** Notify all observers */ virtual void notify() = 0;};
// Step 3: Create concrete Subjectclass WeatherStation : public Subject {private: double temperature = 0; std::vector<Observer*> observers; // List of observers
public: void attach(Observer* observer) override { /** Subscribe an observer */ if (std::find(observers.begin(), observers.end(), observer) == observers.end()) { observers.push_back(observer); std::cout << "✅ Observer attached: " << observer->getName() << std::endl; } }
void detach(Observer* observer) override { /** Unsubscribe an observer */ auto it = std::find(observers.begin(), observers.end(), observer); if (it != observers.end()) { observers.erase(it); std::cout << "❌ Observer detached: " << observer->getName() << std::endl; } }
void notify() override { /** Notify all observers about temperature change */ for (Observer* observer : observers) { observer->update(temperature); } }
void setTemperature(double temp) { /** Set temperature and notify all observers */ temperature = temp; std::cout << "\n🌡️ Temperature changed to " << temp << "°C" << std::endl; notify(); // Notify all observers! }
double getTemperature() const { /** Get current temperature */ return temperature; }};
// Step 4: Create concrete Observersclass PhoneApp : public Observer {public: void update(double temperature) override { std::cout << "📱 Phone App: Temperature is " << temperature << "°C" << std::endl; }
std::string getName() const override { return "PhoneApp"; }};
class Website : public Observer {public: void update(double temperature) override { std::cout << "🌐 Website: Temperature is " << temperature << "°C" << std::endl; }
std::string getName() const override { return "Website"; }};
class Billboard : public Observer {public: void update(double temperature) override { std::cout << "📺 Billboard: Temperature is " << temperature << "°C" << std::endl; }
std::string getName() const override { return "Billboard"; }};
class SmartWatch : public Observer {public: void update(double temperature) override { std::cout << "⌚ Smart Watch: Temperature is " << temperature << "°C" << std::endl; }
std::string getName() const override { return "SmartWatch"; }};
// Step 5: Use the patternint main() { // Create subject WeatherStation weatherStation;
// Create observers PhoneApp phoneApp; Website website; Billboard 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! SmartWatch smartWatch; weatherStation.attach(&smartWatch); weatherStation.setTemperature(22.0);
return 0;}using System;using System.Collections.Generic;
// Step 1: Define the Observer interfacepublic interface IObserver{ /** Called when subject's state changes */ void Update(double temperature);}
// Step 2: Define the Subject interfacepublic interface ISubject{ /** Attach an observer */ void Attach(IObserver observer); /** Detach an observer */ void Detach(IObserver observer); /** Notify all observers */ void Notify();}
// Step 3: Create concrete Subjectpublic class WeatherStation : ISubject{ /** Weather station - the subject being observed */ private double temperature = 0; private List<IObserver> observers = new List<IObserver>(); // List of observers
public void Attach(IObserver observer) { /** Subscribe an observer */ if (!observers.Contains(observer)) { observers.Add(observer); Console.WriteLine($"✅ Observer attached: {observer.GetType().Name}"); } }
public void Detach(IObserver observer) { /** Unsubscribe an observer */ if (observers.Remove(observer)) { Console.WriteLine($"❌ Observer detached: {observer.GetType().Name}"); } }
public void Notify() { /** Notify all observers about temperature change */ foreach (var observer in observers) { observer.Update(temperature); } }
public void SetTemperature(double temp) { /** Set temperature and notify all observers */ temperature = temp; Console.WriteLine($"\n🌡️ Temperature changed to {temp}°C"); Notify(); // Notify all observers! }
public double GetTemperature() { /** Get current temperature */ return temperature; }}
// Step 4: Create concrete Observerspublic class PhoneApp : IObserver{ /** Phone app observer */ public void Update(double temperature) { Console.WriteLine($"📱 Phone App: Temperature is {temperature}°C"); }}
public class Website : IObserver{ /** Website observer */ public void Update(double temperature) { Console.WriteLine($"🌐 Website: Temperature is {temperature}°C"); }}
public class Billboard : IObserver{ /** Billboard observer */ public void Update(double temperature) { Console.WriteLine($"📺 Billboard: Temperature is {temperature}°C"); }}
public class SmartWatch : IObserver{ public void Update(double temperature) { Console.WriteLine($"⌚ Smart Watch: Temperature is {temperature}°C"); }}
// Step 5: Use the patternclass Program{ static void Main() { // 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! SmartWatch smartWatch = new SmartWatch(); weatherStation.Attach(smartWatch); weatherStation.SetTemperature(22.0); }}package main
import "fmt"
// Step 1: Observer interfacetype Observer interface { Update(temperature float64)}
// Step 2: Subject interfacetype Subject interface { Attach(Observer) Detach(Observer) Notify()}
// Step 3: Concrete Subjecttype WeatherStation struct { observers []Observer temperature float64}
func (ws *WeatherStation) Attach(o Observer) { ws.observers = append(ws.observers, o) fmt.Printf("✅ Observer attached\n")}
func (ws *WeatherStation) Detach(o Observer) { for i, obs := range ws.observers { if obs == o { ws.observers = append(ws.observers[:i], ws.observers[i+1:]...) break } }}
func (ws *WeatherStation) Notify() { for _, o := range ws.observers { o.Update(ws.temperature) }}
func (ws *WeatherStation) SetTemperature(temp float64) { ws.temperature = temp fmt.Printf("\n🌡️ Temperature changed to %.1f°C\n", temp) ws.Notify()}
// Step 4: Concrete Observerstype PhoneApp struct{}type Website struct{}type Billboard struct{}type SmartWatch struct{}
func (p *PhoneApp) Update(t float64) { fmt.Printf("📱 Phone App: Temperature is %.1f°C\n", t) }func (w *Website) Update(t float64) { fmt.Printf("🌐 Website: Temperature is %.1f°C\n", t) }func (b *Billboard) Update(t float64) { fmt.Printf("📺 Billboard: Temperature is %.1f°C\n", t) }func (s *SmartWatch) Update(t float64) { fmt.Printf("⌚ Smart Watch: Temperature is %.1f°C\n", t) }
func main() { ws := &WeatherStation{} phoneApp := &PhoneApp{} website := &Website{} billboard := &Billboard{}
ws.Attach(phoneApp) ws.Attach(website) ws.Attach(billboard) ws.SetTemperature(25.0)
ws.Detach(website) ws.SetTemperature(30.0)
ws.Attach(&SmartWatch{}) ws.SetTemperature(22.0)}trait Observer { fn update(&self, event: &str);}
struct EmailObserver;impl Observer for EmailObserver { fn update(&self, event: &str) { println!("Email: {}", event); }}
struct Subject { observers: Vec<Box<dyn Observer>>,}
impl Subject { fn attach(&mut self, observer: Box<dyn Observer>) { self.observers.push(observer); } fn notify(&self, event: &str) { for observer in &self.observers { observer.update(event); } }}✅ Observer attached✅ 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:
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(); }}// ❌ Without Observer Pattern
class Order { private orderId: string; private items: string[]; private total: number; private status: string = "pending"; // Problem: Order knows about all services! private emailService: EmailService | null = null; private inventoryService: InventoryService | null = null; private paymentService: PaymentService | null = null; private warehouseService: WarehouseService | null = null; private analyticsService: AnalyticsService | null = null;
constructor(orderId: string, items: string[], total: number) { this.orderId = orderId; this.items = items; this.total = total; }
placeOrder(): void { /** Place order and notify all services */ this.status = "placed";
// Problem: Manual notification to each service if (this.emailService) { this.emailService.sendConfirmation(this.orderId); }
if (this.inventoryService) { this.inventoryService.updateInventory(this.items); }
if (this.paymentService) { this.paymentService.processPayment(this.orderId, this.total); }
if (this.warehouseService) { this.warehouseService.notifyWarehouse(this.orderId, this.items); }
if (this.analyticsService) { this.analyticsService.recordOrder(this.orderId, this.total); } }
setEmailService(service: EmailService): void { this.emailService = service; }
setInventoryService(service: InventoryService): void { this.inventoryService = service; }
setPaymentService(service: PaymentService): void { this.paymentService = service; }
setWarehouseService(service: WarehouseService): void { this.warehouseService = service; }
setAnalyticsService(service: AnalyticsService): void { this.analyticsService = service; }}
class EmailService { sendConfirmation(orderId: string): void { console.log(`📧 Email sent: Order ${orderId} confirmation`); }}
class InventoryService { updateInventory(items: string[]): void { console.log(`📦 Inventory updated for ${items.length} items`); }}
class PaymentService { processPayment(orderId: string, amount: number): void { console.log(`💳 Payment processed: $${amount} for order ${orderId}`); }}
class WarehouseService { notifyWarehouse(orderId: string, items: string[]): void { console.log(`🏭 Warehouse notified: Order ${orderId} with ${items.length} items`); }}
class AnalyticsService { recordOrder(orderId: string, amount: number): void { console.log(`📊 Analytics: Order ${orderId} recorded, amount: $${amount}`); }}
// Usageconst order = new Order("ORD-123", ["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();// ❌ Without Observer Pattern
#include <iostream>#include <string>#include <vector>
class EmailService;class InventoryService;class PaymentService;class WarehouseService;class AnalyticsService;
class Order {private: std::string orderId; std::vector<std::string> items; double total; std::string status = "pending"; // Problem: Order knows about all services! EmailService* emailService = nullptr; InventoryService* inventoryService = nullptr; PaymentService* paymentService = nullptr; WarehouseService* warehouseService = nullptr; AnalyticsService* analyticsService = nullptr;
public: Order(const std::string& orderId, const std::vector<std::string>& items, double total) : orderId(orderId), items(items), total(total) {}
void placeOrder();
void setEmailService(EmailService* service) { emailService = service; } void setInventoryService(InventoryService* service) { inventoryService = service; } void setPaymentService(PaymentService* service) { paymentService = service; } void setWarehouseService(WarehouseService* service) { warehouseService = service; } void setAnalyticsService(AnalyticsService* service) { analyticsService = service; }
const std::string& getOrderId() const { return orderId; } const std::vector<std::string>& getItems() const { return items; } double getTotal() const { return total; }};
class EmailService {public: void sendConfirmation(const std::string& orderId) { std::cout << "📧 Email sent: Order " << orderId << " confirmation" << std::endl; }};
class InventoryService {public: void updateInventory(const std::vector<std::string>& items) { std::cout << "📦 Inventory updated for " << items.size() << " items" << std::endl; }};
class PaymentService {public: void processPayment(const std::string& orderId, double amount) { std::cout << "💳 Payment processed: $" << amount << " for order " << orderId << std::endl; }};
class WarehouseService {public: void notifyWarehouse(const std::string& orderId, const std::vector<std::string>& items) { std::cout << "🏭 Warehouse notified: Order " << orderId << " with " << items.size() << " items" << std::endl; }};
class AnalyticsService {public: void recordOrder(const std::string& orderId, double amount) { std::cout << "📊 Analytics: Order " << orderId << " recorded, amount: $" << amount << std::endl; }};
void Order::placeOrder() { status = "placed";
// Problem: Manual notification to each service if (emailService) { emailService->sendConfirmation(orderId); }
if (inventoryService) { inventoryService->updateInventory(items); }
if (paymentService) { paymentService->processPayment(orderId, total); }
if (warehouseService) { warehouseService->notifyWarehouse(orderId, items); }
if (analyticsService) { analyticsService->recordOrder(orderId, total); }}
// Usageint main() { Order order("ORD-123", {"item1", "item2"}, 99.99);
EmailService emailService; InventoryService inventoryService; PaymentService paymentService; WarehouseService warehouseService; AnalyticsService analyticsService;
order.setEmailService(&emailService); order.setInventoryService(&inventoryService); order.setPaymentService(&paymentService); order.setWarehouseService(&warehouseService); order.setAnalyticsService(&analyticsService);
order.placeOrder();
return 0;}// ❌ Without Observer Pattern
using System;using System.Collections.Generic;
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 */ 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); } }
public void SetEmailService(EmailService service) { emailService = service; } public void SetInventoryService(InventoryService service) { inventoryService = service; } public void SetPaymentService(PaymentService service) { paymentService = service; } public void SetWarehouseService(WarehouseService service) { warehouseService = service; } public void SetAnalyticsService(AnalyticsService service) { analyticsService = service; }}
public class EmailService{ public void SendConfirmation(string orderId) { Console.WriteLine($"📧 Email sent: Order {orderId} confirmation"); }}
public class InventoryService{ public void UpdateInventory(List<string> items) { Console.WriteLine($"📦 Inventory updated for {items.Count} items"); }}
public class PaymentService{ public void ProcessPayment(string orderId, double amount) { Console.WriteLine($"💳 Payment processed: ${amount} for order {orderId}"); }}
public class WarehouseService{ public void NotifyWarehouse(string orderId, List<string> items) { Console.WriteLine($"🏭 Warehouse notified: Order {orderId} with {items.Count} items"); }}
public class AnalyticsService{ public void RecordOrder(string orderId, double amount) { Console.WriteLine($"📊 Analytics: Order {orderId} recorded, amount: ${amount}"); }}
// Usageclass Program{ static void Main() { Order order = new Order("ORD-123", new List<string> { "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(); }}package main
import "fmt"
// ❌ Without Observer Pattern - Order knows about all services!
type EmailService struct{}type InventoryService struct{}type PaymentService struct{}
func (e *EmailService) SendConfirmation(id string) { fmt.Printf("📧 Email sent: Order %s confirmation\n", id) }func (i *InventoryService) UpdateInventory(items int) { fmt.Printf("📦 Inventory updated for %d items\n", items) }func (p *PaymentService) ProcessPayment(id string, amt float64) { fmt.Printf("💳 Payment processed: $%.2f for order %s\n", amt, id) }
type Order struct { orderId string items []string total float64 email *EmailService inventory *InventoryService payment *PaymentService}
func (o *Order) PlaceOrder() { if o.email != nil { o.email.SendConfirmation(o.orderId) } if o.inventory != nil { o.inventory.UpdateInventory(len(o.items)) } if o.payment != nil { o.payment.ProcessPayment(o.orderId, o.total) }}
func main() { order := &Order{orderId: "ORD-123", items: []string{"item1", "item2"}, total: 99.99} order.email = &EmailService{} order.inventory = &InventoryService{} order.payment = &PaymentService{} order.PlaceOrder()}struct OrderService;
impl OrderService { fn place_order(&self, order_id: u32) { println!("Order {} placed", order_id); println!("Sending email"); println!("Updating analytics"); println!("Notifying warehouse"); }}📧 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”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); }}// Complete TypeScript implementation matches Python/Java structure// Due to length, showing key parts with all 5 services
interface OrderObserver { onOrderPlaced(orderData: Record<string, any>): void;}
interface OrderSubject { attach(observer: OrderObserver): void; detach(observer: OrderObserver): void; notifyOrderPlaced(orderData: Record<string, any>): void;}
class Order { constructor( public orderId: string, public items: string[], public total: number, public status: string = "pending" ) {}}
class OrderService implements OrderSubject { private observers: OrderObserver[] = [];
attach(observer: OrderObserver): void { if (!this.observers.includes(observer)) { this.observers.push(observer); console.log(`✅ Service subscribed: ${observer.constructor.name}`); } }
detach(observer: OrderObserver): void { const index = this.observers.indexOf(observer); if (index > -1) { this.observers.splice(index, 1); console.log(`❌ Service unsubscribed: ${observer.constructor.name}`); } }
notifyOrderPlaced(orderData: Record<string, any>): void { for (const observer of this.observers) { observer.onOrderPlaced(orderData); } }
placeOrder(order: Order): void { order.status = "placed"; console.log(`\n🛒 Order ${order.orderId} placed!`);
const orderData = { order_id: order.orderId, items: order.items, total: order.total, status: order.status };
this.notifyOrderPlaced(orderData); }}
// Concrete Observersclass EmailService implements OrderObserver { onOrderPlaced(orderData: Record<string, any>): void { console.log(`📧 Email sent: Order ${orderData.order_id} confirmation`); }}
class InventoryService implements OrderObserver { onOrderPlaced(orderData: Record<string, any>): void { console.log(`📦 Inventory updated for ${orderData.items.length} items`); }}
class PaymentService implements OrderObserver { onOrderPlaced(orderData: Record<string, any>): void { console.log(`💳 Payment processed: $${orderData.total} for order ${orderData.order_id}`); }}
class WarehouseService implements OrderObserver { onOrderPlaced(orderData: Record<string, any>): void { console.log(`🏭 Warehouse notified: Order ${orderData.order_id} with ${orderData.items.length} items`); }}
class AnalyticsService implements OrderObserver { onOrderPlaced(orderData: Record<string, any>): void { console.log(`📊 Analytics: Order ${orderData.order_id} recorded, amount: $${orderData.total}`); }}
class NotificationService implements OrderObserver { onOrderPlaced(orderData: Record<string, any>): void { console.log(`🔔 Push notification sent: Order ${orderData.order_id} placed`); }}
// Usageconst orderService = new OrderService();const emailService = new EmailService();const inventoryService = new InventoryService();const paymentService = new PaymentService();const warehouseService = new WarehouseService();const analyticsService = new AnalyticsService();
orderService.attach(emailService);orderService.attach(inventoryService);orderService.attach(paymentService);orderService.attach(warehouseService);orderService.attach(analyticsService);
const order = new Order("ORD-123", ["Laptop", "Mouse"], 999.99);orderService.placeOrder(order);
const notificationService = new NotificationService();orderService.attach(notificationService);
const order2 = new Order("ORD-124", ["Keyboard"], 49.99);orderService.placeOrder(order2);// Complete C++ implementation with all 5 services#include <iostream>#include <vector>#include <string>#include <map>#include <algorithm>
using OrderData = std::map<std::string, std::string>;
class OrderObserver {public: virtual ~OrderObserver() = default; virtual void onOrderPlaced(const OrderData& orderData) = 0; virtual std::string getName() const = 0;};
class OrderSubject {public: virtual ~OrderSubject() = default; virtual void attach(OrderObserver* observer) = 0; virtual void detach(OrderObserver* observer) = 0; virtual void notifyOrderPlaced(const OrderData& orderData) = 0;};
struct Order { std::string orderId; std::vector<std::string> items; double total; std::string status = "pending";
Order(const std::string& id, const std::vector<std::string>& itms, double tot) : orderId(id), items(itms), total(tot) {}};
class OrderService : public OrderSubject {private: std::vector<OrderObserver*> observers;
public: void attach(OrderObserver* observer) override { if (std::find(observers.begin(), observers.end(), observer) == observers.end()) { observers.push_back(observer); std::cout << "✅ Service subscribed: " << observer->getName() << std::endl; } }
void detach(OrderObserver* observer) override { auto it = std::find(observers.begin(), observers.end(), observer); if (it != observers.end()) { observers.erase(it); std::cout << "❌ Service unsubscribed: " << observer->getName() << std::endl; } }
void notifyOrderPlaced(const OrderData& orderData) override { for (auto observer : observers) { observer->onOrderPlaced(orderData); } }
void placeOrder(Order& order) { order.status = "placed"; std::cout << "\n🛒 Order " << order.orderId << " placed!" << std::endl;
OrderData orderData; orderData["order_id"] = order.orderId; orderData["total"] = std::to_string(order.total); orderData["items_count"] = std::to_string(order.items.size());
notifyOrderPlaced(orderData); }};
// Concrete Observers (showing all 5 services)class EmailService : public OrderObserver {public: void onOrderPlaced(const OrderData& orderData) override { std::cout << "📧 Email sent: Order " << orderData.at("order_id") << " confirmation" << std::endl; } std::string getName() const override { return "EmailService"; }};
class InventoryService : public OrderObserver {public: void onOrderPlaced(const OrderData& orderData) override { std::cout << "📦 Inventory updated for " << orderData.at("items_count") << " items" << std::endl; } std::string getName() const override { return "InventoryService"; }};
class PaymentService : public OrderObserver {public: void onOrderPlaced(const OrderData& orderData) override { std::cout << "💳 Payment processed: $" << orderData.at("total") << " for order " << orderData.at("order_id") << std::endl; } std::string getName() const override { return "PaymentService"; }};
class WarehouseService : public OrderObserver {public: void onOrderPlaced(const OrderData& orderData) override { std::cout << "🏭 Warehouse notified: Order " << orderData.at("order_id") << " with " << orderData.at("items_count") << " items" << std::endl; } std::string getName() const override { return "WarehouseService"; }};
class AnalyticsService : public OrderObserver {public: void onOrderPlaced(const OrderData& orderData) override { std::cout << "📊 Analytics: Order " << orderData.at("order_id") << " recorded, amount: $" << orderData.at("total") << std::endl; } std::string getName() const override { return "AnalyticsService"; }};
class NotificationService : public OrderObserver {public: void onOrderPlaced(const OrderData& orderData) override { std::cout << "🔔 Push notification sent: Order " << orderData.at("order_id") << " placed" << std::endl; } std::string getName() const override { return "NotificationService"; }};
// Usageint main() { OrderService orderService; EmailService emailService; InventoryService inventoryService; PaymentService paymentService; WarehouseService warehouseService; AnalyticsService analyticsService;
orderService.attach(&emailService); orderService.attach(&inventoryService); orderService.attach(&paymentService); orderService.attach(&warehouseService); orderService.attach(&analyticsService);
Order order("ORD-123", {"Laptop", "Mouse"}, 999.99); orderService.placeOrder(order);
NotificationService notificationService; orderService.attach(¬ificationService);
Order order2("ORD-124", {"Keyboard"}, 49.99); orderService.placeOrder(order2);
return 0;}// Complete C# implementation with all 5 servicesusing System;using System.Collections.Generic;using System.Linq;
public interface IOrderObserver{ void OnOrderPlaced(Dictionary<string, object> orderData);}
public interface IOrderSubject{ void Attach(IOrderObserver observer); void Detach(IOrderObserver observer); void NotifyOrderPlaced(Dictionary<string, object> orderData);}
public class Order{ public string OrderId { get; set; } public List<string> Items { get; set; } public double Total { get; set; } public string Status { get; set; } = "pending";
public Order(string orderId, List<string> items, double total) { OrderId = orderId; Items = items; Total = total; }}
public class OrderService : IOrderSubject{ private List<IOrderObserver> observers = new List<IOrderObserver>();
public void Attach(IOrderObserver observer) { if (!observers.Contains(observer)) { observers.Add(observer); Console.WriteLine($"✅ Service subscribed: {observer.GetType().Name}"); } }
public void Detach(IOrderObserver observer) { if (observers.Remove(observer)) { Console.WriteLine($"❌ Service unsubscribed: {observer.GetType().Name}"); } }
public void NotifyOrderPlaced(Dictionary<string, object> orderData) { foreach (var observer in observers) { observer.OnOrderPlaced(orderData); } }
public void PlaceOrder(Order order) { order.Status = "placed"; Console.WriteLine($"\n🛒 Order {order.OrderId} placed!");
var orderData = new Dictionary<string, object> { ["order_id"] = order.OrderId, ["items"] = order.Items, ["total"] = order.Total, ["status"] = order.Status };
NotifyOrderPlaced(orderData); }}
// Concrete Observers (all 5 services)public class EmailService : IOrderObserver{ public void OnOrderPlaced(Dictionary<string, object> orderData) { Console.WriteLine($"📧 Email sent: Order {orderData["order_id"]} confirmation"); }}
public class InventoryService : IOrderObserver{ public void OnOrderPlaced(Dictionary<string, object> orderData) { var items = (List<string>)orderData["items"]; Console.WriteLine($"📦 Inventory updated for {items.Count} items"); }}
public class PaymentService : IOrderObserver{ public void OnOrderPlaced(Dictionary<string, object> orderData) { Console.WriteLine($"💳 Payment processed: ${orderData["total"]} for order {orderData["order_id"]}"); }}
public class WarehouseService : IOrderObserver{ public void OnOrderPlaced(Dictionary<string, object> orderData) { var items = (List<string>)orderData["items"]; Console.WriteLine($"🏭 Warehouse notified: Order {orderData["order_id"]} with {items.Count} items"); }}
public class AnalyticsService : IOrderObserver{ public void OnOrderPlaced(Dictionary<string, object> orderData) { Console.WriteLine($"📊 Analytics: Order {orderData["order_id"]} recorded, amount: ${orderData["total"]}"); }}
public class NotificationService : IOrderObserver{ public void OnOrderPlaced(Dictionary<string, object> orderData) { Console.WriteLine($"🔔 Push notification sent: Order {orderData["order_id"]} placed"); }}
// Usageclass Program{ static void Main() { var orderService = new OrderService(); var emailService = new EmailService(); var inventoryService = new InventoryService(); var paymentService = new PaymentService(); var warehouseService = new WarehouseService(); var analyticsService = new AnalyticsService();
orderService.Attach(emailService); orderService.Attach(inventoryService); orderService.Attach(paymentService); orderService.Attach(warehouseService); orderService.Attach(analyticsService);
var order = new Order("ORD-123", new List<string> { "Laptop", "Mouse" }, 999.99); orderService.PlaceOrder(order);
var notificationService = new NotificationService(); orderService.Attach(notificationService);
var order2 = new Order("ORD-124", new List<string> { "Keyboard" }, 49.99); orderService.PlaceOrder(order2); }}package main
import "fmt"
// Observer interface for order eventstype OrderObserver interface { OnOrderPlaced(data map[string]any)}
// Order structtype Order struct { orderId string items []string total float64}
// Subject: OrderServicetype OrderService struct { observers []OrderObserver}
func (os *OrderService) Attach(o OrderObserver) { os.observers = append(os.observers, o) fmt.Printf("✅ Service subscribed\n")}
func (os *OrderService) Notify(data map[string]any) { for _, o := range os.observers { o.OnOrderPlaced(data) }}
func (os *OrderService) PlaceOrder(order *Order) { fmt.Printf("\n🛒 Order %s placed!\n", order.orderId) data := map[string]any{ "order_id": order.orderId, "items": order.items, "total": order.total, } os.Notify(data)}
// Concrete observerstype EmailSvc struct{}type InventorySvc struct{}type PaymentSvc struct{}
func (e *EmailSvc) OnOrderPlaced(d map[string]any) { fmt.Printf("📧 Email sent: Order %s confirmation\n", d["order_id"])}func (i *InventorySvc) OnOrderPlaced(d map[string]any) { items := d["items"].([]string) fmt.Printf("📦 Inventory updated for %d items\n", len(items))}func (p *PaymentSvc) OnOrderPlaced(d map[string]any) { fmt.Printf("💳 Payment processed: $%.2f for order %s\n", d["total"], d["order_id"])}
func main() { svc := &OrderService{} svc.Attach(&EmailSvc{}) svc.Attach(&InventorySvc{}) svc.Attach(&PaymentSvc{})
svc.PlaceOrder(&Order{"ORD-123", []string{"Laptop", "Mouse"}, 999.99}) svc.PlaceOrder(&Order{"ORD-124", []string{"Keyboard"}, 49.99})}trait Observer { fn update(&self, event: &str);}
struct EmailObserver;impl Observer for EmailObserver { fn update(&self, event: &str) { println!("Email: {}", event); }}
struct Subject { observers: Vec<Box<dyn Observer>>,}
impl Subject { fn attach(&mut self, observer: Box<dyn Observer>) { self.observers.push(observer); } fn notify(&self, event: &str) { for observer in &self.observers { observer.update(event); } }}✅ 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!// Step 1: Create the new service observerclass LoyaltyPointsService implements OrderObserver { /** Loyalty points service observer */ onOrderPlaced(orderData: Record<string, any>): void { const orderId = orderData.order_id; const amount = orderData.total; const points = Math.floor(amount * 0.1); // 10% of order value console.log(`🎁 Loyalty points added: ${points} points for order ${orderId}`); }}
// Step 2: Subscribe it (that's it!)const loyaltyService = new LoyaltyPointsService();orderService.attach(loyaltyService);
// Now it automatically gets notified for all future orders!// No changes needed to OrderService or placeOrder() method!// Step 1: Create the new service observerclass LoyaltyPointsService : public OrderObserver {public: void onOrderPlaced(const OrderData& orderData) override { std::string orderId = orderData.at("order_id"); double amount = std::stod(orderData.at("total")); int points = static_cast<int>(amount * 0.1); // 10% of order value std::cout << "🎁 Loyalty points added: " << points << " points for order " << orderId << std::endl; }
std::string getName() const override { return "LoyaltyPointsService"; }};
// Step 2: Subscribe it (that's it!)LoyaltyPointsService loyaltyService;orderService.attach(&loyaltyService);
// Now it automatically gets notified for all future orders!// No changes needed to OrderService or placeOrder() method!// Step 1: Create the new service observerpublic class LoyaltyPointsService : IOrderObserver{ /** Loyalty points service observer */ public void OnOrderPlaced(Dictionary<string, object> orderData) { string orderId = (string)orderData["order_id"]; double amount = (double)orderData["total"]; int points = (int)(amount * 0.1); // 10% of order value Console.WriteLine($"🎁 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!// Step 1: Create the new service observertype LoyaltyPointsService struct{}
func (l *LoyaltyPointsService) OnOrderPlaced(data map[string]any) { orderId := data["order_id"].(string) amount := data["total"].(float64) points := int(amount * 0.1) fmt.Printf("🎁 Loyalty points added: %d points for order %s\n", points, orderId)}
// Step 2: Subscribe it (that's it!)// orderService.Attach(&LoyaltyPointsService{})// No changes needed to OrderService!// Adding a New Servicetrait Observer { fn update(&self, event: &str);}struct EmailObserver;impl Observer for EmailObserver { fn update(&self, event: &str) { println!("Email: {}", event); }}
struct Subject { observers: Vec<Box<dyn Observer>>,}impl Subject { fn attach(&mut self, observer: Box<dyn Observer>) { self.observers.push(observer); } fn notify(&self, event: &str) { for observer in &self.observers { observer.update(event); } }}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); }}interface Observer { update(data: Record<string, any>): void;}
// Subject pushes all dataclass Subject { private observers: Observer[] = []; private temperature: number; private humidity: number; private pressure: number;
notify(): void { for (const observer of this.observers) { observer.update({ temperature: this.temperature, humidity: this.humidity, pressure: this.pressure }); } }}#include <map>#include <vector>#include <string>
class Observer {public: virtual ~Observer() = default; virtual void update(const std::map<std::string, double>& data) = 0;};
// Subject pushes all dataclass Subject {private: std::vector<Observer*> observers; double temperature; double humidity; double pressure;
public: void notify() { std::map<std::string, double> data; data["temperature"] = temperature; data["humidity"] = humidity; data["pressure"] = pressure;
for (Observer* observer : observers) { observer->update(data); } }};using System.Collections.Generic;
public interface IObserver{ void Update(Dictionary<string, object> data);}
// Subject pushes all datapublic class Subject{ private List<IObserver> observers = new List<IObserver>(); private double temperature; private double humidity; private double pressure;
public void Notify() { var data = new Dictionary<string, object> { ["temperature"] = temperature, ["humidity"] = humidity, ["pressure"] = pressure };
foreach (var observer in observers) { observer.Update(data); } }}// Push Model - subject pushes all data to observerstype Observer interface { Update(data map[string]any)}
type Subject struct { observers []Observer temperature float64 humidity float64 pressure float64}
func (s *Subject) Notify() { data := map[string]any{ "temperature": s.temperature, "humidity": s.humidity, "pressure": s.pressure, } for _, o := range s.observers { o.Update(data) }}// 1. Push Model What We Usedtrait Observer { fn update(&self, event: &str);}struct EmailObserver;impl Observer for EmailObserver { fn update(&self, event: &str) { println!("Email: {}", event); }}
struct Subject { observers: Vec<Box<dyn Observer>>,}impl Subject { fn attach(&mut self, observer: Box<dyn Observer>) { self.observers.push(observer); } fn notify(&self, event: &str) { for observer in &self.observers { observer.update(event); } }}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 }}interface Observer { update(subject: Subject): void;}
abstract class Subject { abstract getTemperature(): number; abstract getHumidity(): number;}
class ConcreteObserver implements Observer { update(subject: Subject): void { // Observer pulls what it needs const temp = subject.getTemperature(); const humidity = subject.getHumidity(); // Use the data... }}
// Subject just notifies, observers pull what they needclass ConcreteSubject extends Subject { private observers: Observer[] = [];
notify(): void { for (const observer of this.observers) { observer.update(this); // Pass subject reference } }
getTemperature(): number { return 0; } getHumidity(): number { return 0; }}class Subject;
class Observer {public: virtual ~Observer() = default; virtual void update(Subject* subject) = 0;};
class Subject {protected: std::vector<Observer*> observers;
public: virtual ~Subject() = default; virtual double getTemperature() const = 0; virtual double getHumidity() const = 0;
void notify() { for (Observer* observer : observers) { observer->update(this); // Pass subject reference } }};
class ConcreteObserver : public Observer {public: void update(Subject* subject) override { // Observer pulls what it needs double temp = subject->getTemperature(); double humidity = subject->getHumidity(); // Use the data... }};using System.Collections.Generic;
public interface IObserver{ void Update(ISubject subject);}
public interface ISubject{ double GetTemperature(); double GetHumidity();}
public class ConcreteObserver : IObserver{ public void Update(ISubject subject) { // Observer pulls what it needs double temp = subject.GetTemperature(); double humidity = subject.GetHumidity(); // Use the data... }}
// Subject just notifies, observers pull what they needpublic class ConcreteSubject : ISubject{ private List<IObserver> observers = new List<IObserver>();
public void Notify() { foreach (var observer in observers) { observer.Update(this); // Pass subject reference } }
public double GetTemperature() { return 0; } public double GetHumidity() { return 0; }}// Pull Model - observer pulls data it needs from subject
type SubjectI interface { GetTemperature() float64 GetHumidity() float64}
type ObserverI interface { Update(s SubjectI)}
type ConcreteObserver struct{}
func (o *ConcreteObserver) Update(s SubjectI) { // Observer pulls only what it needs temp := s.GetTemperature() humidity := s.GetHumidity() fmt.Printf("Temp: %.1f, Humidity: %.1f\n", temp, humidity)}
type ConcreteSubject struct { observers []ObserverI temperature float64 humidity float64}
func (s *ConcreteSubject) GetTemperature() float64 { return s.temperature }func (s *ConcreteSubject) GetHumidity() float64 { return s.humidity }func (s *ConcreteSubject) Notify() { for _, o := range s.observers { o.Update(s) // Pass subject reference }}// 2. Pull Modeltrait Observer { fn update(&self, event: &str);}struct EmailObserver;impl Observer for EmailObserver { fn update(&self, event: &str) { println!("Email: {}", event); }}
struct Subject { observers: Vec<Box<dyn Observer>>,}impl Subject { fn attach(&mut self, observer: Box<dyn Observer>) { self.observers.push(observer); } fn notify(&self, event: &str) { for observer in &self.observers { observer.update(event); } }}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); }}type EventCallback = (data: Record<string, any>) => void;
class EventObserver { /** Event-based observer using callbacks */ private callbacks: EventCallback[] = [];
subscribe(callback: EventCallback): void { this.callbacks.push(callback); }
notify(eventData: Record<string, any>): void { for (const callback of this.callbacks) { callback(eventData); } }}
// Usageconst observer = new EventObserver();observer.subscribe((data) => { console.log(`Order ${data.order_id} placed!`);});
observer.notify({ order_id: "ORD-123" });#include <functional>#include <vector>#include <map>#include <string>
using EventCallback = std::function<void(const std::map<std::string, std::string>&)>;
class EventObserver {private: std::vector<EventCallback> callbacks;
public: void subscribe(EventCallback callback) { callbacks.push_back(callback); }
void notify(const std::map<std::string, std::string>& eventData) { for (const auto& callback : callbacks) { callback(eventData); } }};
// Usage// EventObserver observer;// observer.subscribe([](const auto& data) {// std::cout << "Order " << data.at("order_id") << " placed!" << std::endl;// });// observer.notify({{"order_id", "ORD-123"}});using System;using System.Collections.Generic;
public class EventObserver{ /** Event-based observer using callbacks */ private List<Action<Dictionary<string, object>>> callbacks = new List<Action<Dictionary<string, object>>>();
public void Subscribe(Action<Dictionary<string, object>> callback) { callbacks.Add(callback); }
public void Notify(Dictionary<string, object> eventData) { foreach (var callback in callbacks) { callback(eventData); } }}
// Usageclass Program{ static void Main() { EventObserver observer = new EventObserver(); observer.Subscribe(data => { Console.WriteLine($"Order {data["order_id"]} placed!"); });
var eventData = new Dictionary<string, object> { ["order_id"] = "ORD-123" }; observer.Notify(eventData); }}package main
import "fmt"
// Event-based observer using function callbackstype EventCallback func(data map[string]any)
type EventObserver struct { callbacks []EventCallback}
func (e *EventObserver) Subscribe(cb EventCallback) { e.callbacks = append(e.callbacks, cb)}
func (e *EventObserver) Notify(eventData map[string]any) { for _, cb := range e.callbacks { cb(eventData) }}
func main() { observer := &EventObserver{} observer.Subscribe(func(data map[string]any) { fmt.Printf("Order %s placed!\n", data["order_id"]) }) observer.Notify(map[string]any{"order_id": "ORD-123"})}// 3. Event-Based Observertrait Observer { fn update(&self, event: &str);}struct EmailObserver;impl Observer for EmailObserver { fn update(&self, event: &str) { println!("Email: {}", event); }}
struct Subject { observers: Vec<Box<dyn Observer>>,}impl Subject { fn attach(&mut self, observer: Box<dyn Observer>) { self.observers.push(observer); } fn notify(&self, event: &str) { for observer in &self.observers { observer.update(event); } }}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 }}// ❌ Observer modifying subject - can cause infinite loops!class BadObserver implements Observer { private subject: Subject;
constructor(subject: Subject) { this.subject = subject; }
update(temperature: number): void { if (temperature > 30) { this.subject.setTemperature(25); // Modifying subject! // This triggers notify() again, which calls update() again... infinite loop! } }}
// ✅ Better: Observer should only react, not modifyclass GoodObserver implements Observer { update(temperature: number): void { if (temperature > 30) { this.sendAlert(); // Just react, don't modify subject } }
private sendAlert(): void { // Send alert logic }}// ❌ Observer modifying subject - can cause infinite loops!class BadObserver : public Observer {private: Subject* subject;
public: BadObserver(Subject* subject) : subject(subject) {}
void update(double temperature) override { if (temperature > 30) { subject->setTemperature(25); // Modifying subject! // This triggers notify() again, which calls update() again... infinite loop! } }
std::string getName() const override { return "BadObserver"; }};
// ✅ Better: Observer should only react, not modifyclass GoodObserver : public Observer {public: void update(double temperature) override { if (temperature > 30) { sendAlert(); // Just react, don't modify subject } }
private: void sendAlert() { // Send alert logic }
std::string getName() const override { return "GoodObserver"; }};// ❌ Observer modifying subject - can cause infinite loops!public class BadObserver : IObserver{ private ISubject subject;
public BadObserver(ISubject subject) { this.subject = subject; }
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 : IObserver{ public void Update(double temperature) { if (temperature > 30) { SendAlert(); // Just react, don't modify subject } }
private void SendAlert() { // Send alert logic }}// ❌ Bad: Observer modifying subject - infinite loop risktype BadObserver struct{ subject *WeatherStation }
func (b *BadObserver) Update(temp float64) { if temp > 30 { b.subject.SetTemperature(25) // Modifying subject - causes infinite loop! }}
// ✅ Good: Observer only reacts, does not modify subjecttype GoodObserver struct{}
func (g *GoodObserver) Update(temp float64) { if temp > 30 { sendAlert() // Just react, don't modify subject }}
func sendAlert() { fmt.Println("Alert: High temperature!") }// Mistake 1: Observers Modifying Subjectstruct OrderService;impl OrderService { fn place_order(&self, id: u32) { println!("Order {}", id); println!("Email"); println!("Analytics"); }}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 } }}// ❌ If one observer fails, others don't get notifiedclass Subject { private observers: Observer[] = []; private temperature: number;
notify(): void { for (const observer of this.observers) { observer.update(this.temperature); // If this fails, loop stops! } }}
// ✅ Better: Handle errors so all observers get notifiedclass BetterSubject { private observers: Observer[] = []; private temperature: number;
notify(): void { for (const observer of this.observers) { try { observer.update(this.temperature); } catch (e) { console.error(`Error notifying ${observer.constructor.name}: ${e}`); // Continue with other observers } } }}#include <iostream>#include <vector>
// ❌ If one observer fails, others don't get notifiedclass Subject {private: std::vector<Observer*> observers; double temperature;
public: void notify() { for (Observer* observer : observers) { observer->update(temperature); // If this throws, loop stops! } }};
// ✅ Better: Handle errors so all observers get notifiedclass BetterSubject {private: std::vector<Observer*> observers; double temperature;
public: void notify() { for (Observer* observer : observers) { try { observer->update(temperature); } catch (const std::exception& e) { std::cerr << "Error notifying observer: " << e.what() << std::endl; // Continue with other observers } } }};using System;using System.Collections.Generic;
// ❌ If one observer fails, others don't get notifiedpublic class Subject{ private List<IObserver> observers = new List<IObserver>(); private double temperature;
public void Notify() { foreach (var observer in observers) { observer.Update(temperature); // If this fails, loop stops! } }}
// ✅ Better: Handle errors so all observers get notifiedpublic class BetterSubject{ private List<IObserver> observers = new List<IObserver>(); private double temperature;
public void Notify() { foreach (var observer in observers) { try { observer.Update(temperature); } catch (Exception e) { Console.Error.WriteLine($"Error notifying observer: {e.Message}"); // Continue with other observers } } }}// ❌ Bad: If one observer panics, others don't get notifiedfunc (s *Subject) BadNotify() { for _, o := range s.observers { o.Update(s.temperature) // If this panics, loop stops! }}
// ✅ Good: Handle errors so all observers get notifiedfunc (s *Subject) Notify() { for _, o := range s.observers { func() { defer func() { if r := recover(); r != nil { fmt.Fprintf(os.Stderr, "Error notifying observer: %v\n", r) // Continue with other observers } }() o.Update(s.temperature) }() }}// Mistake 2: Not Handling Observer Errorsstruct OrderService;impl OrderService { fn place_order(&self, id: u32) { println!("Order {}", id); println!("Email"); println!("Analytics"); }}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! }}// ❌ Observer holds reference to subject, subject holds reference to observer// If observer is deleted but not unsubscribed, memory leak!
class BadObserver implements Observer { private subject: Subject;
constructor(subject: Subject) { this.subject = subject; subject.attach(this); // Attached but never detached }
update(data: any): void { // Update logic }}
// ✅ Better: Always unsubscribe when doneclass GoodObserver implements Observer { private subject: Subject;
constructor(subject: Subject) { this.subject = subject; subject.attach(this); }
update(data: any): void { // Update logic }
cleanup(): void { this.subject.detach(this); // Always clean up! }}// ❌ Observer holds reference to subject, subject holds reference to observer// If observer is deleted but not unsubscribed, memory leak!
class BadObserver : public Observer {private: Subject* subject;
public: BadObserver(Subject* subject) : subject(subject) { subject->attach(this); // Attached but never detached }
void update(double data) override { // Update logic }
std::string getName() const override { return "BadObserver"; }};
// ✅ Better: Always unsubscribe when doneclass GoodObserver : public Observer {private: Subject* subject;
public: GoodObserver(Subject* subject) : subject(subject) { subject->attach(this); }
~GoodObserver() { cleanup(); }
void update(double data) override { // Update logic }
void cleanup() { subject->detach(this); // Always clean up! }
std::string getName() const override { return "GoodObserver"; }};// ❌ Observer holds reference to subject, subject holds reference to observer// If observer is deleted but not unsubscribed, memory leak!
public class BadObserver : IObserver{ private ISubject subject;
public BadObserver(ISubject subject) { this.subject = subject; subject.Attach(this); // Attached but never detached }
public void Update(double data) { // Update logic }}
// ✅ Better: Always unsubscribe when donepublic class GoodObserver : IObserver, IDisposable{ private ISubject subject;
public GoodObserver(ISubject subject) { this.subject = subject; subject.Attach(this); }
public void Update(double data) { // Update logic }
public void Dispose() { subject.Detach(this); // Always clean up! }}// ❌ Bad: Observer attached but never detachedtype BadObserver struct{ subject *WeatherStation }
func NewBadObserver(s *WeatherStation) *BadObserver { o := &BadObserver{subject: s} s.Attach(o) // Attached but never detached return o}func (b *BadObserver) Update(temp float64) {}
// ✅ Good: Provide cleanup methodtype GoodObserver struct{ subject *WeatherStation }
func NewGoodObserver(s *WeatherStation) *GoodObserver { o := &GoodObserver{subject: s} s.Attach(o) return o}func (g *GoodObserver) Update(temp float64) {}func (g *GoodObserver) Close() { g.subject.Detach(g) // Always clean up!}// Mistake 3: Memory Leaks Not Unsubscribingstruct OrderService;impl OrderService { fn place_order(&self, id: u32) { println!("Order {}", id); println!("Email"); println!("Analytics"); }}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(); }}// Observer interfaceinterface Observer { update(data: any): void;}
// Subjectclass Subject { private observers: Observer[] = []; private state: any = null;
attach(observer: Observer): void { this.observers.push(observer); }
notify(): void { for (const observer of this.observers) { observer.update(this.state); } }}
// Concrete Observerclass ConcreteObserver implements Observer { update(data: any): void { console.log(`Updated: ${data}`); }}
// Usageconst subject = new Subject();const observer = new ConcreteObserver();subject.attach(observer);subject.notify();// Observer interfaceclass Observer {public: virtual ~Observer() = default; virtual void update(void* data) = 0;};
// Subjectclass Subject {private: std::vector<Observer*> observers; void* state = nullptr;
public: void attach(Observer* observer) { observers.push_back(observer); }
void notify() { for (Observer* observer : observers) { observer->update(state); } }};
// Concrete Observerclass ConcreteObserver : public Observer {public: void update(void* data) override { // Update logic }};
// Usage// Subject subject;// ConcreteObserver observer;// subject.attach(&observer);// subject.notify();// Observer interfacepublic interface IObserver{ void Update(object data);}
// Subjectpublic class Subject{ private List<IObserver> observers = new List<IObserver>(); private object state = null;
public void Attach(IObserver observer) { observers.Add(observer); }
public void Notify() { foreach (var observer in observers) { observer.Update(state); } }}
// Concrete Observerpublic class ConcreteObserver : IObserver{ public void Update(object data) { Console.WriteLine($"Updated: {data}"); }}
// Usageclass Program{ static void Main() { Subject subject = new Subject(); IObserver observer = new ConcreteObserver(); subject.Attach(observer); subject.Notify(); }}// Observer interfacetype Observer interface { Update(data any)}
// Subjecttype Subject struct { observers []Observer state any}
func (s *Subject) Attach(o Observer) { s.observers = append(s.observers, o) }func (s *Subject) Notify() { for _, o := range s.observers { o.Update(s.state) }}
// Concrete Observertype ConcreteObserver struct{}
func (c *ConcreteObserver) Update(data any) { fmt.Printf("Updated: %v\n", data) }
func main() { subject := &Subject{} subject.Attach(&ConcreteObserver{}) subject.Notify()}// Simple Exampletrait Observer { fn update(&self, event: &str);}struct EmailObserver;impl Observer for EmailObserver { fn update(&self, event: &str) { println!("Email: {}", event); }}
struct Subject { observers: Vec<Box<dyn Observer>>,}impl Subject { fn attach(&mut self, observer: Box<dyn Observer>) { self.observers.push(observer); } fn notify(&self, event: &str) { for observer in &self.observers { observer.update(event); } }}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(); }}// 1. Observer Interfaceinterface Observer { update(data: any): void;}
// 2. Subject Interfaceinterface Subject { attach(observer: Observer): void; detach(observer: Observer): void; notify(): void;}
// 3. Concrete Subjectclass ConcreteSubject implements Subject { private observers: Observer[] = []; private state: any = null;
attach(observer: Observer): void { this.observers.push(observer); }
detach(observer: Observer): void { const index = this.observers.indexOf(observer); if (index > -1) { this.observers.splice(index, 1); } }
notify(): void { for (const observer of this.observers) { observer.update(this.state); } }}
// 4. Concrete Observerclass ConcreteObserver implements Observer { update(data: any): void { // React to change }}
// 5. Usageconst subject: Subject = new ConcreteSubject();const observer: Observer = new ConcreteObserver();subject.attach(observer);subject.notify();// 1. Observer Interfaceclass Observer {public: virtual ~Observer() = default; virtual void update(void* data) = 0;};
// 2. Subject Interfaceclass Subject {public: virtual ~Subject() = default; virtual void attach(Observer* observer) = 0; virtual void detach(Observer* observer) = 0; virtual void notify() = 0;};
// 3. Concrete Subjectclass ConcreteSubject : public Subject {private: std::vector<Observer*> observers; void* state = nullptr;
public: void attach(Observer* observer) override { observers.push_back(observer); }
void detach(Observer* observer) override { auto it = std::find(observers.begin(), observers.end(), observer); if (it != observers.end()) { observers.erase(it); } }
void notify() override { for (Observer* observer : observers) { observer->update(state); } }};
// 4. Concrete Observerclass ConcreteObserver : public Observer {public: void update(void* data) override { // React to change }};
// 5. Usage// ConcreteSubject subject;// ConcreteObserver observer;// subject.attach(&observer);// subject.notify();// 1. Observer Interfacepublic interface IObserver{ void Update(object data);}
// 2. Subject Interfacepublic interface ISubject{ void Attach(IObserver observer); void Detach(IObserver observer); void Notify();}
// 3. Concrete Subjectpublic class ConcreteSubject : ISubject{ private List<IObserver> observers = new List<IObserver>(); private object state = null;
public void Attach(IObserver observer) { observers.Add(observer); }
public void Detach(IObserver observer) { observers.Remove(observer); }
public void Notify() { foreach (var observer in observers) { observer.Update(state); } }}
// 4. Concrete Observerpublic class ConcreteObserver : IObserver{ public void Update(object data) { // React to change }}
// 5. Usageclass Program{ static void Main() { ISubject subject = new ConcreteSubject(); IObserver observer = new ConcreteObserver(); subject.Attach(observer); subject.Notify(); }}// 1. Observer interfacetype Observer interface{ Update(data any) }
// 2. Subject interfacetype Subject interface { Attach(Observer) Detach(Observer) Notify()}
// 3. Concrete Subjecttype ConcreteSubject struct { observers []Observer state any}
func (s *ConcreteSubject) Attach(o Observer) { s.observers = append(s.observers, o) }func (s *ConcreteSubject) Detach(o Observer) { for i, obs := range s.observers { if obs == o { s.observers = append(s.observers[:i], s.observers[i+1:]...); return } }}func (s *ConcreteSubject) Notify() { for _, o := range s.observers { o.Update(s.state) }}
// 4. Concrete Observertype ConcreteObserver struct{}
func (c *ConcreteObserver) Update(data any) {} // React to change
// 5. Usage// subject := &ConcreteSubject{}// subject.Attach(&ConcreteObserver{})// subject.Notify()// Common Pattern Structuretrait Observer { fn update(&self, event: &str);}struct EmailObserver;impl Observer for EmailObserver { fn update(&self, event: &str) { println!("Email: {}", event); }}
struct Subject { observers: Vec<Box<dyn Observer>>,}impl Subject { fn attach(&mut self, observer: Box<dyn Observer>) { self.observers.push(observer); } fn notify(&self, event: &str) { for observer in &self.observers { observer.update(event); } }}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, abstractmethod
class Observer(ABC): @abstractmethod def update(self, data): passinterface Observer { void update(Object data);}interface Observer { update(data: any): void;}class Observer {public: virtual void update(void* data) = 0;};interface IObserver { void Update(object data);}type Observer interface { Update(data any)}// 6. Implementation Detailstrait Observer { fn update(&self, event: &str);}struct EmailObserver;impl Observer for EmailObserver { fn update(&self, event: &str) { println!("Email: {}", event); }}
struct Subject { observers: Vec<Box<dyn Observer>>,}impl Subject { fn attach(&mut self, observer: Box<dyn Observer>) { self.observers.push(observer); } fn notify(&self, event: &str) { for observer in &self.observers { observer.update(event); } }}- Maintain observer list - Use a list or set to store observers
def __init__(self): self._observers: List[Observer] = []private List<Observer> observers = new ArrayList<>();private observers: Observer[] = [];std::vector<Observer*> observers;private List<IObserver> observers = new List<IObserver>();observers []Observer // slice of observers// 6. Implementation Detailstrait Observer { fn update(&self, event: &str);}struct EmailObserver;impl Observer for EmailObserver { fn update(&self, event: &str) { println!("Email: {}", event); }}
struct Subject { observers: Vec<Box<dyn Observer>>,}impl Subject { fn attach(&mut self, observer: Box<dyn Observer>) { self.observers.push(observer); } fn notify(&self, event: &str) { for observer in &self.observers { observer.update(event); } }}- Handle duplicate subscriptions - Check if observer already exists
def attach(self, observer: Observer): if observer not in self._observers: self._observers.append(observer)void attach(Observer o) { if (!observers.contains(o)) observers.add(o);}attach(observer: Observer): void { if (!this.observers.includes(observer)) this.observers.push(observer);}void attach(Observer* o) { if (std::find(observers.begin(), observers.end(), o) == observers.end()) observers.push_back(o);}public void Attach(IObserver o) { if (!observers.Contains(o)) observers.Add(o);}func (s *Subject) Attach(o Observer) { for _, existing := range s.observers { if existing == o { return } // already subscribed } s.observers = append(s.observers, o)}// 6. Implementation Detailstrait Observer { fn update(&self, event: &str);}struct EmailObserver;impl Observer for EmailObserver { fn update(&self, event: &str) { println!("Email: {}", event); }}
struct Subject { observers: Vec<Box<dyn Observer>>,}impl Subject { fn attach(&mut self, observer: Box<dyn Observer>) { self.observers.push(observer); } fn notify(&self, event: &str) { for observer in &self.observers { observer.update(event); } }}- 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}")void notifyObservers() { for (Observer o : observers) { try { o.update(state); } catch (Exception e) { logger.error("Error notifying", e); } }}notify(): void { for (const o of this.observers) { try { o.update(this.state); } catch (e) { console.error("Error notifying", e); } }}void notify() { for (auto* o : observers) { try { o->update(&state); } catch (const std::exception& e) { /* log error */ } }}public void Notify() { foreach (var o in observers) { try { o.Update(state); } catch (Exception e) { logger.Error("Error notifying", e); } }}func (s *Subject) Notify() { for _, o := range s.observers { func() { defer func() { if r := recover(); r != nil { log.Printf("Error notifying observer: %v", r) } }() o.Update(s.state) }() }}// 6. Implementation Detailstrait Observer { fn update(&self, event: &str);}struct EmailObserver;impl Observer for EmailObserver { fn update(&self, event: &str) { println!("Email: {}", event); }}
struct Subject { observers: Vec<Box<dyn Observer>>,}impl Subject { fn attach(&mut self, observer: Box<dyn Observer>) { self.observers.push(observer); } fn notify(&self, event: &str) { for observer in &self.observers { observer.update(event); } }}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(); }}import { Logger } from './logger';
const logger = new Logger();
interface Observer { /** Called when subject's state changes */ update(data: Record<string, any>): void;}
interface Subject { /** Attach an observer */ attach(observer: Observer): void; /** Detach an observer */ detach(observer: Observer): void; /** Notify all observers */ notify(): void;}
class WeatherStation implements Subject { /** Weather station subject */ private temperature: number = 0; private observers: Observer[] = [];
attach(observer: Observer): void { /** Subscribe an observer */ if (!this.observers.includes(observer)) { this.observers.push(observer); logger.info(`Observer attached: ${observer.constructor.name}`); } }
detach(observer: Observer): void { /** Unsubscribe an observer */ const index = this.observers.indexOf(observer); if (index > -1) { this.observers.splice(index, 1); logger.info(`Observer detached: ${observer.constructor.name}`); } }
notify(): void { /** Notify all observers with error handling */ const data = { temperature: this.temperature }; for (const observer of this.observers) { try { observer.update(data); } catch (e) { logger.error(`Error notifying ${observer.constructor.name}: ${e}`); // Continue with other observers } } }
setTemperature(temperature: number): void { /** Set temperature and notify observers */ this.temperature = temperature; this.notify(); }}#include <vector>#include <map>#include <string>#include <algorithm>#include <iostream>
class Observer {public: virtual ~Observer() = default; virtual void update(const std::map<std::string, double>& data) = 0; virtual std::string getName() const = 0;};
class Subject {public: virtual ~Subject() = default; virtual void attach(Observer* observer) = 0; virtual void detach(Observer* observer) = 0; virtual void notify() = 0;};
class WeatherStation : public Subject {private: double temperature = 0; std::vector<Observer*> observers;
public: void attach(Observer* observer) override { // Subscribe an observer if (std::find(observers.begin(), observers.end(), observer) == observers.end()) { observers.push_back(observer); std::cout << "Observer attached: " << observer->getName() << std::endl; } }
void detach(Observer* observer) override { // Unsubscribe an observer auto it = std::find(observers.begin(), observers.end(), observer); if (it != observers.end()) { observers.erase(it); std::cout << "Observer detached: " << observer->getName() << std::endl; } }
void notify() override { // Notify all observers with error handling std::map<std::string, double> data; data["temperature"] = temperature;
for (Observer* observer : observers) { try { observer->update(data); } catch (const std::exception& e) { std::cerr << "Error notifying observer: " << e.what() << std::endl; // Continue with other observers } } }
void setTemperature(double temp) { // Set temperature and notify observers temperature = temp; notify(); }};using System;using System.Collections.Generic;
public interface IObserver{ /** Called when subject's state changes */ void Update(Dictionary<string, object> data);}
public interface ISubject{ /** Attach an observer */ void Attach(IObserver observer); /** Detach an observer */ void Detach(IObserver observer); /** Notify all observers */ void Notify();}
public class WeatherStation : ISubject{ /** Weather station subject */ private double temperature = 0; private List<IObserver> observers = new List<IObserver>();
public void Attach(IObserver observer) { /** Subscribe an observer */ if (!observers.Contains(observer)) { observers.Add(observer); Console.WriteLine($"Observer attached: {observer.GetType().Name}"); } }
public void Detach(IObserver observer) { /** Unsubscribe an observer */ if (observers.Remove(observer)) { Console.WriteLine($"Observer detached: {observer.GetType().Name}"); } }
public void Notify() { /** Notify all observers with error handling */ var data = new Dictionary<string, object> { ["temperature"] = temperature }; foreach (var observer in observers) { try { observer.Update(data); } catch (Exception e) { Console.Error.WriteLine($"Error notifying observer: {e.Message}"); // Continue with other observers } } }
public void SetTemperature(double temp) { /** Set temperature and notify observers */ temperature = temp; Notify(); }}package main
import ( "fmt" "log")
// Observer interfacetype Observer interface { Update(data map[string]any)}
// Subject interfacetype Subject interface { Attach(Observer) Detach(Observer) Notify()}
// WeatherStation - production-ready subjecttype WeatherStation struct { observers []Observer temperature float64}
func (ws *WeatherStation) Attach(o Observer) { for _, existing := range ws.observers { if existing == o { return } } ws.observers = append(ws.observers, o) fmt.Printf("Observer attached\n")}
func (ws *WeatherStation) Detach(o Observer) { for i, obs := range ws.observers { if obs == o { ws.observers = append(ws.observers[:i], ws.observers[i+1:]...) fmt.Printf("Observer detached\n") return } }}
func (ws *WeatherStation) Notify() { data := map[string]any{"temperature": ws.temperature} for _, o := range ws.observers { func() { defer func() { if r := recover(); r != nil { log.Printf("Error notifying observer: %v", r) } }() o.Update(data) }() }}
func (ws *WeatherStation) SetTemperature(temp float64) { ws.temperature = temp ws.Notify()}// 10. Code Quality Pointstrait Observer { fn update(&self, event: &str);}struct EmailObserver;impl Observer for EmailObserver { fn update(&self, event: &str) { println!("Email: {}", event); }}
struct Subject { observers: Vec<Box<dyn Observer>>,}impl Subject { fn attach(&mut self, observer: Box<dyn Observer>) { self.observers.push(observer); } fn notify(&self, event: &str) { for observer in &self.observers { observer.update(event); } }}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! 👀