100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Software Design

Design Patterns in Modern Frameworks

How classic GoF design patterns show up, sometimes disguised under different names, inside React, Spring, Angular, and other modern frameworks.

PracticeIntermediate11 min readJul 10, 2026
Analogies

Patterns Didn't Disappear — They Got Baked In

Modern frameworks did not eliminate the need for classic design patterns; they industrialized them, wiring the pattern's machinery into the framework so application developers rarely write the plumbing by hand. React's hooks encode Observer and Strategy, Spring's ApplicationContext is a sophisticated Factory plus Dependency Injection container, and Angular's dependency injection hierarchy is a textbook Composite of injectors. Recognizing the underlying pattern in a framework feature helps you use it correctly, because you inherit the pattern's known trade-offs — such as an Observer's risk of update storms — even when the framework hides the wiring.

🏏

Cricket analogy: The DRS (Decision Review System) didn't remove the umpire's decision-making pattern, it industrialized it — technology now automates ball-tracking and edge detection, but the underlying pattern of 'appeal, review, decide' from decades of cricket remains structurally the same.

React: Observer, Strategy, and Composite in Everyday Hooks

React's useState and useEffect implement the Observer pattern: a component subscribes to state, and React re-renders (notifies) it whenever that state changes, without the component polling for updates. Custom hooks are Strategy objects in disguise — swapping useFetchWithCache for useFetchWithRetry changes the data-loading algorithm while the component's rendering logic stays untouched. The component tree itself is a Composite: a <Layout> composed of <Header>, <Sidebar>, and <Content> treats leaf components and composite components through the same props.children interface, letting you nest arbitrarily deep without the parent caring whether a child is a leaf or another composite.

🏏

Cricket analogy: useState acting like an Observer is like a scoreboard operator who doesn't poll the umpire every ball but is notified the instant a run is scored or a wicket falls, updating the display automatically rather than checking on a timer.

Spring and Angular: Factory, Singleton, and Dependency Injection at Scale

Spring's ApplicationContext is a Factory that also enforces Singleton scope by default: when you @Autowired a bean, Spring's container looks up or lazily constructs the instance and hands you a reference to the same shared object every time, unless you explicitly declare prototype scope. Angular's hierarchical injector system is a Composite of Factories — each injector can resolve a dependency itself or delegate up the tree to a parent injector, exactly like a Composite's operation() delegating to children. Both frameworks turn what used to be manually written Factory and Singleton boilerplate into declarative annotations (@Component, @Injectable), but the underlying pattern responsibilities — object creation and lifecycle management — are unchanged.

🏏

Cricket analogy: Spring's default Singleton-scoped beans are like a franchise's single designated wicketkeeper for the season — every match 'autowires' the same person into that role rather than auditioning a new keeper each game.

typescript
// Angular: hierarchical DI is a Composite of Factories
@Injectable({ providedIn: 'root' })
export class AnalyticsService {
  track(event: string) { console.log('tracked:', event); }
}

// A feature module can override the singleton with its own factory
@NgModule({
  providers: [
    { provide: AnalyticsService, useFactory: () => new AnalyticsService() }
  ]
})
export class CheckoutModule {}

// React: a custom hook as a Strategy object
function useFetchWithRetry(url: string, retries = 3) {
  const [data, setData] = useState(null);
  useEffect(() => {
    let attempt = 0;
    const load = async () => {
      try {
        const res = await fetch(url);
        setData(await res.json());
      } catch (err) {
        if (attempt++ < retries) load();
      }
    };
    load();
  }, [url]);
  return data;
}

When debugging 'why is my Angular service state shared across unrelated components,' the answer is almost always Singleton scope from providedIn: 'root'. Recognizing this as the Singleton pattern immediately tells you the fix: provide the service at the component level instead of root.

Why Recognizing the Pattern Still Matters

Even though frameworks hide the pattern's boilerplate, they cannot hide its trade-offs. Observer-based reactivity (React state, Angular's RxJS-driven change detection, Vue's reactive refs) inherits Observer's classic risk: a deeply nested subscriber graph can cause cascading re-renders or 'update storms' if you're not careful about subscription granularity. Factory-based DI containers inherit Factory's classic trade-off: convenience at construction time in exchange for a layer of indirection that can make tracing 'where does this object actually come from' harder during debugging. Naming the pattern gives you the pattern's known vocabulary of failure modes and mitigations, which is faster than re-deriving them from scratch under framework-specific jargon.

🏏

Cricket analogy: A DRS 'update storm' is like a team reviewing every single close call obsessively, exhausting their limited review count — knowing the Observer pattern's cascading-notification risk tells a captain when to hold back a review, just as a developer learns to throttle re-renders.

Framework magic can hide a Singleton scope decision you didn't consciously make. Always check a service's provider scope (Spring's @Scope, Angular's providedIn) before assuming state is instance-local — shared mutable singleton state is a common source of hard-to-reproduce bugs in multi-tenant or multi-request systems.

  • Frameworks don't remove design patterns — they industrialize them behind declarative APIs and annotations.
  • React's useState/useEffect implement Observer; custom hooks implement Strategy; the component tree is a Composite.
  • Spring's ApplicationContext is a Factory that defaults to Singleton scope for autowired beans.
  • Angular's hierarchical injector system is a Composite of Factories that can delegate up the tree.
  • Recognizing the underlying pattern predicts the framework feature's known trade-offs and failure modes.
  • Observer-heavy reactivity risks cascading update storms if subscriptions aren't scoped or memoized carefully.
  • Always verify a DI container's scope annotation before assuming state isn't shared across consumers.

Practice what you learned

Was this page helpful?

Topics covered

#SoftwareDesign#DesignPatternsStudyNotes#SoftwareEngineering#DesignPatternsInModernFrameworks#Design#Patterns#Modern#Frameworks#StudyNotes#SkillVeris