This folder demonstrates the Observer pattern - a behavioral design pattern that defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
The Observer pattern establishes a subscription mechanism to allow multiple objects to listen and react to events or changes that happen to the object they're observing. It's like a notification system where subscribers get automatically notified when something happens.
Real-world analogy:
- YouTube channel subscriptions
- You subscribe to a channel (you become an observer)
- When the channel uploads a new video (subject changes state)
- You get a notification automatically (observers are notified)
- You can unsubscribe anytime (remove observer)
- The channel doesn't need to know who you are or how many subscribers there are
In programming:
- One object (the Subject) maintains a list of dependents (the Observers)
- When the Subject's state changes, it automatically notifies all Observers
- Observers can subscribe or unsubscribe at runtime
- Loose coupling - Subject and Observers don't need to know much about each other
This shows the inefficient approach without using the Observer pattern.
The base class that all observers must extend.
The base class that all subjects must extend.
A concrete implementation of an observer (a client waiting for products).
A concrete implementation of a subject (a store that notifies clients).
Demonstrates how to properly use the Observer pattern.
Location: src/testWithoutObserver.js
class Store {
constructor() {
this.availableProduct = false;
}
notifyClients() {
if(this.availableProduct) {
console.log("Notifying clients about new product availability.");
console.log("Client A: Product Available!");
console.log("Client B: Product Available!");
}
}
receiveNewProduct() {
console.log("Product has arrived at the store.");
this.availableProduct = true
this.notifyClients()
}
}
const store = new Store();
store.receiveNewProduct();Problems with this approach:
-
Tight coupling:
- ❌ Store class knows exactly which clients to notify (Client A, Client B)
- ❌ Hardcoded client names in the Store class
- ❌ Can't add or remove clients dynamically
- ❌ Store is responsible for knowing about specific clients
-
Violates Open/Closed Principle:
- ❌ Adding a new client requires modifying the Store class
- ❌ Can't extend the notification system without changing existing code
- ❌ Risk of breaking existing functionality
-
No flexibility:
- ❌ Clients can't subscribe or unsubscribe
- ❌ Can't have different types of observers
- ❌ All clients are always notified (no selective notifications)
-
Scaling problems:
- ❌ What if you have 100 clients? Add 100 console.log statements?
- ❌ What if clients need different notification formats?
- ❌ What if some clients are removed?
-
Mixing responsibilities:
- ❌ Store handles both product management AND client notification details
- ❌ Violates Single Responsibility Principle
The Observer pattern separates the notification mechanism from the subject's core logic. The subject maintains a list of observers and notifies them automatically without knowing their details.
observer.js- The Observer interface (base class for all observers)subject.js- The Subject interface (base class for all subjects)client.js- Concrete Observer (a client that gets notified)store.js- Concrete Subject (a store that sends notifications)testWithObserver.js- Client code (demonstrates the pattern)
This is the base class that defines the contract all observers must follow.
class Observer {
update() {
throw new Error("Method update() must be implemented.");
}
}
module.exports = Observer;Key features:
- Defines the interface: All observers must implement
update() - Forces implementation: Throws an error if not overridden
- Common contract: Guarantees all observers respond to notifications the same way
This is the base class that defines how subjects manage their observers.
class Subject {
constructor() {
this.observers = [];
}
addObservers(observer) {
throw new Error("Method addObserver must be implemented.");
}
removeObserver(observer) {
throw new Error("Method removeObserver must be implemented.");
}
notifyObservers() {
throw new Error("Method notifyObservers must be implemented.");
}
}
module.exports = Subject;Key features:
- Manages observers: Maintains a list of subscribed observers
- Subscription methods:
addObservers()andremoveObserver()for managing the list - Notification method:
notifyObservers()to alert all observers - Template: Subclasses implement the specific logic
This class represents a client who wants to be notified about product availability.
const Observer = require("./observer");
class Client extends Observer {
constructor(name) {
super();
this.name = name
}
update() {
console.log(`${this.name}: Product Available!`);
}
}
module.exports = Client;Key features:
- Extends Observer: Implements the
update()method - Personalized response: Each client has a name and responds individually
- Encapsulates behavior: Client knows how to handle notifications
- Independent: Doesn't know about the Store or other Clients
This class represents a store that notifies clients when products arrive.
const Subject = require("./subject");
class Store extends Subject {
constructor() {
super();
}
addObservers(observer) {
this.observers.push(observer);
}
removeObserver(observer) {
this.observers = this.observers.filter(sub => sub !== observer)
}
notifyObservers() {
console.log("Notifying clients...");
this.observers.forEach(observer => observer.update());
}
receiveNewProduct() {
console.log("Product has arrived at the store!");
this.notifyObservers();
}
}
module.exports = Store;Key features:
- Extends Subject: Implements observer management methods
- Dynamic subscription: Can add/remove observers at runtime
- Automatic notification: Calls
update()on all observers - Decoupled: Doesn't know details about observers, just calls their
update()method - Business logic:
receiveNewProduct()triggers the notification
This shows how the client code uses the Observer pattern.
const Client = require("./models/client");
const Store = require("./models/store");
const store = new Store();
const clientA = new Client("John");
const clientB = new Client("Megan");
const clientC = new Client("Anna");
store.addObservers(clientA);
store.addObservers(clientB);
store.addObservers(clientC);
store.removeObserver(clientB);
store.receiveNewProduct();
// Output:
// Product has arrived at the store!
// Notifying clients...
// John: Product Available!
// Anna: Product Available!What's happening:
- Create a
Store(the subject) - Create multiple
Clientinstances (the observers) - Subscribe clients to the store using
addObservers() - Remove one client using
removeObserver() - When
receiveNewProduct()is called, the store automatically notifies all subscribed clients - Each client's
update()method is called automatically
class Store {
notifyClients() {
if(this.availableProduct) {
console.log("Client A: Product Available!");
console.log("Client B: Product Available!");
// Need to hardcode every client!
}
}
receiveNewProduct() {
this.availableProduct = true
this.notifyClients()
}
}
// Can't add clients dynamically
// Can't remove clients
// Store knows about specific clientsProblems:
- Hardcoded client notifications
- Can't add/remove clients
- Tight coupling
- Violates SRP and OCP
// Observer interface
class Observer {
update() { /* must implement */ }
}
// Concrete observer
class Client extends Observer {
constructor(name) {
super();
this.name = name;
}
update() {
console.log(`${this.name}: Product Available!`);
}
}
// Subject manages observers
class Store extends Subject {
addObservers(observer) {
this.observers.push(observer);
}
notifyObservers() {
this.observers.forEach(observer => observer.update());
}
receiveNewProduct() {
console.log("Product has arrived!");
this.notifyObservers(); // Automatically notify all
}
}
// Dynamic subscription
store.addObservers(clientA);
store.addObservers(clientB);
store.removeObserver(clientB);
// Automatic notification
store.receiveNewProduct();Benefits:
- ✅ Dynamic subscription/unsubscription
- ✅ Loose coupling (Store doesn't know Client details)
- ✅ Scalable (handle any number of observers)
- ✅ Flexible (different observer types possible)
- ✅ Follows SRP and OCP
- Observers register with the Subject using
addObservers() - Subject maintains a list of all subscribed observers
- Subject's state changes (e.g., new product arrives)
- Subject calls
notifyObservers() - Subject iterates through all observers and calls their
update()method - Each observer reacts according to its own implementation
- Observers can unsubscribe using
removeObserver()at any time
Subject (Store)
┌──────────────────────┐
│ observers: [] │
│ │
│ addObservers() │
│ removeObserver() │
│ notifyObservers() │
│ receiveNewProduct() │
└──────────┬───────────┘
│
┌──────────┼──────────┐
↓ ↓ ↓
Observer1 Observer2 Observer3
(Client) (Client) (Client)
┌──────┐ ┌──────┐ ┌──────┐
│John │ │Megan │ │Anna │
│ │ │ │ │ │
│update│ │update│ │update│
└──────┘ └──────┘ └──────┘
When receiveNewProduct() is called:
1. Subject.notifyObservers()
2. Calls update() on each observer
3. Each observer responds independently
"Software entities should be open for extension but closed for modification."
✅ With Observer Pattern:
- Want to add a new client? Just create a new
Clientinstance and calladdObservers() - No need to modify
Store,Observer,Subject, or other clients - Extend functionality without touching existing code
❌ Without Observer Pattern:
- Must open
Storeclass and add more hardcoded console.log statements - Risk breaking existing functionality
"A class should have only one reason to change."
✅ With Observer Pattern:
Store- manages products and observer listClient- handles its own notification responseObserver- defines observer interfaceSubject- defines subject interface- Each class has a clear, single responsibility
❌ Without Observer Pattern:
Storeknows about product management AND client notification details- Multiple reasons to change
"Depend on abstractions, not concretions."
✅ With Observer Pattern:
Storedepends onObserverinterface, not specificClientimplementations- Can add any observer type that implements the interface
- Loose coupling through abstraction
❌ Without Observer Pattern:
Storedirectly implements specific client notifications- Tight coupling to concrete implementations
"Minimize dependencies between components."
✅ With Observer Pattern:
- Subject and Observers are loosely coupled
- Subject only knows observers implement
update() - Observers don't know about each other or the subject's internals
- Can change either side without affecting the other
❌ Without Observer Pattern:
- Tight coupling between Store and specific clients
- Changes ripple through the system
- Subject and observers are independent
- Subject doesn't need to know observer details
- Easy to change or extend either side
- Subscribe/unsubscribe at runtime
- Add or remove observers on the fly
- Flexible notification lists
- One-to-many notification automatically
- Single change triggers multiple updates
- Efficient event propagation
- Add new observers without modifying the subject
- Extend functionality through new observer types
- Existing code remains stable
- Same observer can watch multiple subjects
- Same subject can notify different observer types
- Compose complex notification systems
- Subject focuses on its core responsibility
- Observers handle their own responses
- Clear boundaries between components
- Enables reactive programming
- Push-based updates (instead of polling)
- Foundation for event systems
-
Event Listeners (UI):
- Button clicks, mouse movements, keyboard events
- Multiple listeners can respond to the same event
- Add/remove listeners dynamically
-
Model-View Architecture (MVC, MVVM):
- Model changes → Views automatically update
- Multiple views can display the same model
- Foundation of reactive frameworks (React, Vue, Angular)
-
Messaging Systems:
- Pub/Sub pattern (Redis, RabbitMQ, Kafka)
- Publishers send messages → Subscribers receive them
- Decoupled communication between services
-
Stock Market:
- Stock price changes → Notify all subscribers
- Investors, apps, bots watching the same stock
- Real-time updates to multiple parties
-
Social Media:
- New post → Notify all followers
- YouTube subscriptions, Twitter followers, Instagram
- One update, many notifications
-
Newsletter/Email Lists:
- New content published → Email all subscribers
- Subscribers can join/leave anytime
- Broadcasting to a dynamic audience
-
Notification order is uncertain:
⚠️ Observers are notified in array order, but this might not be guaranteed- Can't rely on specific notification sequences
-
Memory leaks:
⚠️ Observers must unsubscribe when no longer needed- Forgotten observers can cause memory leaks
-
Performance overhead:
⚠️ With many observers, notifications can be slow- Every state change triggers all observers
-
Unexpected updates:
⚠️ Cascading updates can be hard to track- Observer's update might trigger other updates
Use it when:
- ✅ Changes to one object require changing others, and you don't know how many
- ✅ An object should be able to notify others without knowing who they are
- ✅ You need a subscription/notification mechanism
- ✅ You want to implement event handling systems
- ✅ You're building reactive or event-driven architectures
Don't use it when:
- ❌ The relationship between subject and observers is always the same
- ❌ You have a simple one-to-one notification
- ❌ Performance is critical and you have many observers
- ❌ The overhead of managing subscriptions isn't worth it
The Observer pattern is about establishing a subscription mechanism where multiple objects can listen to and react to changes in another object. Like YouTube subscriptions, observers get automatically notified when the subject they're watching changes state.
-
One-to-Many Dependency:
- One subject notifies many observers
- Automatic broadcast communication
-
Loose Coupling:
- Subject doesn't know observer details
- Observers don't know about each other
-
Dynamic Subscription:
- Subscribe/unsubscribe at runtime
- Flexible notification lists
-
Event-Driven:
- Push-based updates
- Reactive architecture foundation
-
Design Principles:
- Open/Closed Principle
- Single Responsibility Principle
- Dependency Inversion Principle
The implementation in this folder demonstrates a proper Observer pattern where the Store (subject) maintains a list of Client observers and automatically notifies them when products arrive, compared to the old testWithoutObserver.js approach that hardcoded specific client notifications.