Design Patterns Overview
Step 4 in the OO & Low-Level Design path · 8 concepts · 0 problems
📘 Learn Design Patterns Overview from zero
What design patterns are. A design pattern is a named, reusable solution to a recurring object-oriented design problem — not a finished library or a snippet you paste, but a template for how classes and objects collaborate. The canonical source is the Gang of Four (GoF) book, Design Patterns: Elements of Reusable Object-Oriented Software, which documents 23 patterns. Each is described by four essentials: name (shared vocabulary), problem/intent (when to apply it), solution (the participating classes and their responsibilities), and consequences (the trade-offs you accept).
Why they matter at FAANG level. Patterns are a compression of design intent. Saying "use a Strategy here" conveys structure, extension points, and trade-offs in one word. In low-level design interviews they signal that you reach for vetted structure instead of reinventing — and, just as important, that you know when not to (premature patterning is a red flag).
Classification of design patterns. GoF classifies along two dimensions. By purpose: Creational (object creation — Factory Method, Abstract Factory, Builder, Prototype, Singleton), Structural (composition of classes/objects — Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy), and Behavioral (responsibility and communication — Strategy, State, Observer, Command, Template Method, Iterator, and others). By scope: class patterns (fixed at compile time via inheritance) versus object patterns (composed and changeable at runtime). The two principles underlying nearly all of them: program to an interface, not an implementation, and favor object composition over class inheritance.
✨ Added by the guide to build intuition — not from the source course.
🎯 Guided practice
- Easy — classify, and correctly distinguish Simple Factory from GoF Factory Method. Problem: a logging library must produce a
Loggerthat could beFileLoggerorConsoleLogger, chosen by config. Reasoning: (1) Spot the smell — callers donew FileLogger(), so adding a logger type touches every caller. This is an object-creation problem, so look in the creational family. (2) The straightforward fix is aLoggerFactory.create(config)with aswitchreturning theLoggerinterface. Name it correctly: a single method that branches on a parameter is the Simple Factory idiom (not a GoF pattern). (3) The true GoF Factory Method is different: a base class declares an abstractcreateLogger()and subclasses decide the concrete product — e.g.FileLogManageroverrides it to return aFileLogger. The decision moves to polymorphism, not a branch. (4) Either way callers depend only onLogger, so addingNetworkLoggeris localized. Takeaway: Simple Factory = one method picks the product by parameter; Factory Method = an overridable method lets a subclass pick. Interviewers probe this exact confusion. - Medium — Strategy vs State (pick the right behavioral pattern). Problem: a media player has Play, Pause, Stop. Button behavior depends on the current mode, and pressing a button moves the player to a new mode (Playing then Paused). Reasoning: (1) Behavior varies by a "mode" field with a big
switch— behavioral family. (2) Ask the discriminating question: does the caller pick the behavior, or does the object transition itself? Pressing Play in Stopped mode moves the player to Playing — the object drives its own transitions. That is State, not Strategy. (3) Model each mode as a class implementingPlayerStatewithplay()/pause()/stop(); each method performs the action and sets the next state viaplayer.setState(...). (4) Contrast: if the user instead freely chose a compression algorithm with no transition rules, that is Strategy — caller selects, object does not self-mutate. Takeaway: Strategy = interchangeable algorithms chosen externally; State = an encapsulated mode that manages its own transitions. Near-identical structure (interface plus concrete classes), opposite intent. - Hard — Decorator vs subclassing (structural, runtime composition). Problem: a coffee-shop ordering system has a
Beveragewith a base cost, and any drink can add milk, soy, mocha, or whip in any combination, each adding cost. Reasoning: (1) The naive fix is a subclass per combination (DarkRoastWithMochaAndWhip), which explodes combinatorially — a class-inheritance smell, so reach for a structural pattern. (2) Apply favor composition over inheritance: aCondimentDecoratorimplements the sameBeverageinterface and wraps aBeverage, delegatingcost()to the wrapped object and adding its own. (3)new Whip(new Mocha(new DarkRoast()))composes behavior at runtime; each decorator preserves theBeverageinterface so the client never knows it is holding a wrapped object. (4) Why not Adapter? Adapter changes an interface to make a fixed class usable; Decorator keeps the same interface and adds responsibility. Why not Strategy? Strategy swaps one pluggable algorithm; Decorator stacks many layers. Takeaway: Decorator = open for extension, closed for modification — add behavior by wrapping at runtime instead of subclassing every combination. This is the canonical Head First example of the Open/Closed Principle.
✨ Added by the guide — work these before the full problem set.