Instiq
Chapter 3 · Software design·v1.0.0·Updated 7/11/2026·~17 min

What's changed: Initial version

3.3Design patterns

Key points

Covers the intent of representative examples from the three GoF design-pattern categories—creational (Singleton, Factory Method), structural (Adapter, Facade), and behavioral (Observer, Strategy)—and building the judgment to select the right pattern for a design problem at hand.

Design patterns are named, proven solutions that help avoid reinventing the wheel. But what a system architect needs is not memorizing pattern names—it is judging which pattern's intent matches the design problem at hand (what needs to vary, and what should stay fixed). Borrowing a pattern name while ignoring its intent only introduces unnecessary complexity.

3.3.1Creational patterns

  • Singleton guarantees that a class has only one instance across the entire application and provides a global access point to it. It suits resources where "having more than one instance at once would create inconsistency"—centralized configuration, a shared log destination, and so on. Overuse creates hidden global state that is hard to test, effectively becoming common coupling—a side effect to watch for.
  • Factory Method delegates the choice of concrete class to instantiate to a subclass (or factory object), so the caller need not be aware of the concrete type being created. Adding a new kind of object to create requires no change to the client's creation call, making it a classic mechanism for realizing the open/closed principle.

3.3.2Structural patterns

  • Adapter converts an existing class whose interface is incompatible (often an external library you cannot modify) into the interface the caller expects, bridging the two. Use it when you want to reuse an existing asset that "works correctly but whose call convention doesn't match."
  • Facade wraps and hides the complex call sequence across multiple subsystems (many classes) behind a single, simplified front-facing interface. It lets the caller use the subsystem without knowing its internal details, lowering coupling between the caller and the subsystem.

3.3.3Behavioral patterns

  • Observer defines a one-to-many dependency in which a change in one object's state (the Subject) is automatically broadcast to a set of registered observers. The subject need not know the concrete number or type of observers, and adding a new observer requires no change to the subject's code.
  • Strategy encapsulates an algorithm (a behavior) as an interchangeable object that can be swapped at run time, separating it from the class that uses it. Instead of branching behavior with if-else, it uses polymorphism to make the entire behavior swappable.
Exam point

Most-tested: "Adapter bridges by converting an existing incompatible interface," "Facade simplifies complex calls across multiple subsystems behind a single front," "Observer automatically notifies multiple observers of a state change," and "Strategy encapsulates an algorithm as a swappable object." The key pitfall is confusing Adapter with Facade—Adapter's core is "interface conversion," while Facade's core is "hiding and simplifying complexity."

Suppose a system architect is designing the stock-update feature of an e-commerce site. The requirement: "when stock quantity changes, perform three actions—(1) update the search index, (2) notify the recommendation engine, (3) send a low-stock alert email—and more monitoring actions are expected to be added over time." If the three actions were written directly inside InventoryManager's updateStock() method, every new monitoring action (say, notifying a stock-prediction AI) would require rewriting InventoryManager itself, violating the open/closed principle. The right fit here is the Observer pattern. Making InventoryManager the Subject and registering "search index update," "recommendation notification," and "alert email" each as an Observer means InventoryManager need only publish a "stock changed" notification; adding a new monitoring action is done by registering one more Observer, and InventoryManager's code never needs to change. Meanwhile, suppose the internals of the alert-email sending logic involve a tangle of low-level library calls—establishing an SMTP connection, authentication, MIME encoding, and so on. Hiding that complexity behind a single class, AlertMailer, with a send() method means the caller only needs to be aware of the simple front, AlertMailer.send(...). This is an application of the Facade pattern (simplifying calls across a complex set of subsystems into a single front). Further, suppose there are two ways to notify the recommendation engine—an immediate web-API call or enqueueing onto a batch message queue—and a requirement emerges that this notification method itself should be swappable while the system is running. Extracting the notification algorithm into a NotificationStrategy interface, with two implementations, ImmediateApiStrategy and QueueBatchStrategy, is the right fit for the Strategy pattern. Discerning "what needs to vary and what should stay fixed"—whether the notification recipients grow and shrink (Observer), a complex call sequence needs hiding (Facade), or the algorithm itself needs to be swappable (Strategy)—and choosing the pattern accordingly is the system architect's role.

PatternCategoryIntent
SingletonCreationalGuarantee a single instance across the application
Factory MethodCreationalDelegate the choice of concrete class to instantiate to a subclass/factory
AdapterStructuralConvert an incompatible interface to bridge two components
FacadeStructuralSimplify complex calls across multiple subsystems behind a single front
ObserverBehavioralAutomatically notify registered observers of a state change
StrategyBehavioralEncapsulate an algorithm as a swappable object
Warning

Trap: "Adapter and Facade are the same in intent because both simplify complex processing" is wrong—Adapter's core is "converting an incompatible interface," while Facade's core is "simplifying and hiding complex calls across multiple subsystems"—their intents differ. Also wrong: "Singleton is always safe to use"—overuse creates hidden global state that becomes a breeding ground for bugs in testing and concurrent processing, so its application should be judged carefully.

GoF creational/structural/behavioral.
Refining with proven solutions

3.3.4Section summary

  • Use Observer when the set of notification recipients (observers) grows and shrinks, Facade when you want to hide a complex call sequence behind a single front, and Adapter when you need to bridge an incompatible interface
  • Use Strategy when the algorithm itself needs to be swappable at run time, and Factory Method when you want to delegate the choice of concrete class and realize the open/closed principle
  • Singleton is effective for guaranteeing uniqueness, but overuse creates hidden global state (effectively common coupling), so its application should be judged carefully

Sign in to track progress — Log in.

Quick check

(just a quick review)

Q1. An e-commerce site performs three actions on stock change—search-index update, recommendation notification, alert email—and more monitoring actions are expected. Which pattern is most appropriate for adding new monitoring actions without changing InventoryManager's code?

Q2. The internals of alert-email sending involve a tangle of low-level library calls—SMTP connection setup, authentication, MIME encoding. The goal is to hide this complexity behind a single class's send() method and lower the caller's coupling. Which pattern is most appropriate?

Q3. There are two ways to notify the recommendation engine—an immediate web-API call or enqueueing to a batch message queue—and a requirement emerges to swap the notification method itself while the system is running. Which pattern is most appropriate?

Check your understandingPractice questions for Chapter 3: Software design