Strategy, Factory & Observer
The Gang of Four patterns you actually reach for daily — swapping algorithms, abstracting creation, and broadcasting change, all built on programming to an interface.
Design patterns are named, reusable solutions to recurring design problems. Three of the most useful in everyday code are Strategy (interchangeable algorithms), Factory (abstracted object creation), and Observer (one-to-many change notification). They all share one foundation — depend on interfaces, not implementations — which is what makes code extensible and loosely coupled.
Define a family of algorithms behind a common interface and make them swappable at runtime. The context holds a reference to a strategy and delegates to it, never knowing which concrete one it has. Adding a new algorithm is a new class — no edits to the context. It's the object-oriented replacement for a behavioral switch statement.
A factory encapsulates the decision of which concrete class to instantiate. Callers ask for what they want (by type or parameters) and receive an object that satisfies an interface, without coupling themselves to concrete constructors. This isolates creation logic and makes it easy to add or vary the products.
A subject maintains a list of observers and notifies them whenever its state changes. Observers subscribe and unsubscribe dynamically and react independently. It models one-to-many dependencies with loose coupling — the subject doesn't know what its observers do, only that they implement update().
All three (and most GoF patterns) rest on the same principle: program to an interface, not an implementation. By depending on abstractions, you let concrete types vary without changing the code that uses them — the practical embodiment of polymorphism and the Open/Closed principle.
Key Concepts
Encapsulate interchangeable algorithms behind an interface and select one at runtime. Replaces behavioral switches.
Centralize and abstract object creation so callers depend on an interface, not concrete constructors.
A subject notifies a dynamic list of subscribers on state change — one-to-many, loosely coupled.
Depend on abstractions so implementations can vary without touching client code. The core GoF principle.
Strategy/Observer are behavioral (how objects interact); Factory is creational (how objects are made).
These patterns inject collaborators rather than subclass — favoring flexible composition.
1// STRATEGY — swap an algorithm behind one interface.2interface ShippingStrategy { cost(weight: number): number; }3class Express implements ShippingStrategy { cost(w) { return 15 + w; } }4class Drone implements ShippingStrategy { cost(w) { return 40 + w * 2; } }5checkout.setStrategy(new Drone()); // chosen at runtime6checkout.total(); // uses whatever strategy is set78// FACTORY — centralize creation; callers don't 'new' concrete types.9class ExporterFactory {10 static create(kind: string): Exporter {11 switch (kind) {12 case "pdf": return new PdfExporter();13 case "csv": return new CsvExporter();14 default: throw new Error("unknown format");15 }16 }17}1819// OBSERVER — one subject notifies many subscribers on change.20class StockPrice {21 private observers: Observer[] = [];22 subscribe(o: Observer) { this.observers.push(o); }23 set(price: number) { this.observers.forEach(o => o.update(price)); }24}
GoF patterns are a shared vocabulary among developers and a constant interview topic — saying 'use a strategy here' communicates a whole design instantly. More importantly, recognizing when a problem fits a known pattern (and when it doesn't) lets you write extensible code on purpose rather than rediscovering these solutions the hard way.
Common Pitfalls
1The Pricing Engine Full of If-Else
A checkout calculates discounts with a sprawling if/else over customer tier, promo code, season, and region. Every new promotion edits this method, and bugs in one branch break unrelated pricing.
All pricing variations live in one growing conditional — a maintenance nightmare and an Open/Closed violation. Testing one rule means navigating the whole tangle.
Apply Strategy: each pricing rule becomes a DiscountStrategy implementing calculate(cart). The engine composes the applicable strategies at runtime. New promotions are new classes, independently testable, with no edits to existing rules.
Takeaway: When behavior varies along an axis and keeps growing, Strategy turns a fragile conditional into a set of small, swappable, testable classes — and makes the engine closed for modification.
2Reacting to an Event Without Coupling
When an order is placed, the system must update inventory, send a confirmation email, notify analytics, and award loyalty points. Cramming all of that into placeOrder() couples it to four subsystems.
Direct calls make the order code depend on every subsystem, so it can't change or be tested in isolation, and adding a fifth reaction means editing core order logic again.
Use Observer (in-process pub/sub / domain events): placeOrder() publishes an OrderPlaced event; inventory, email, analytics, and loyalty each subscribe and react independently. The order code knows nothing about them, and new reactions just subscribe.
Takeaway: Observer decouples 'something happened' from 'who cares'. The subject broadcasts; subscribers react on their own — letting you add or remove reactions without touching the source of the event.
Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.