DEV Community

Cover image for Design Patterns by Purpose: The Factory Pattern in Frontend Life (Part 2)
Anju Karanji
Anju Karanji

Posted on • Edited on

Design Patterns by Purpose: The Factory Pattern in Frontend Life (Part 2)

🏭 The Factory Pattern – Creating Without Clutter

Before I open a PR, I scan my code like a typical disappointed Asian parent: "Why are there three identical button setups? Who copy-pasted this API client everywhere? And what's with all these validators doing the exact same dance?"

Each one technically works, but asking every file to memorize a 15-step recipe is like teaching the whole family to make rasam from scratch every time — someone’s bound to mess up the masalas.

That’s where the Factory Pattern comes in: it centralizes creation.
Instead of copy-pasting setup code, you just ask by intent — “primary button,” “authenticated client,” “email validator.” The factory knows the house recipe and returns a ready-to-use object.

Best part: when you decide to switch from chat masala to turmeric (or swap an API provider), you update the factory once. Every dish — and every file — automatically gets the upgrade, no family meeting required.


Where the Factory Pattern Quietly Does Its Magic

Think about visual page builders.

// Component Factory for page builder const ComponentFactory = { create: (type, props) => { switch(type) { case 'hero': return new HeroComponent({ ...props, className: 'hero-section', defaultHeight: '500px' }); case 'testimonial': return new TestimonialComponent({ ...props, showAvatar: true, maxLength: 200 }); default: throw new Error(`Unknown component type: ${type}`); } } }; // Usage - clean and simple const hero = ComponentFactory.create('hero', { title: "'Welcome' });" 
Enter fullscreen mode Exit fullscreen mode

It’s flexible, too. These factories are often built using polymorphism, so if your current setup ever starts to feel clunky — or you need something more scalable — you can swap in a different factory without touching the rest of the system.

That kind of design stays clean for years.

Beyond the Basics: Simple, Method, and Abstract

Do you see where I’m going with this?
Imagine you have an Outfit Generator for different kinds of days:

🧰 Simple Factory – kinda simplistic

A Simple Factory is a convenient helper that builds an object based on a parameter — like our Outfit Generator picking “pajamas” or “blouse & skirt.”

⚠️ Note: Purists don’t list this as an official “Gang of Four” pattern because it doesn’t involve inheritance or polymorphism — it’s basically a neat wrapper around new. Still, for small apps it’s practical and keeps creation logic in one spot.

const OutfitFactory = { create(type) { switch (type) { case 'wfh': return 'Mismatched pajama set'; case 'office': return 'Clean blouse & skirt'; default: throw new Error('Unknown vibe'); } } }; 
Enter fullscreen mode Exit fullscreen mode

🏭 Factory Method – stylists with their own rules

The Factory Method pattern delegates creation to specialized “stylists.”
Each subclass knows how to assemble its look.

class Stylist { createOutfit() { throw new Error('Override me'); } } class WFHStylist extends Stylist { createOutfit() { return 'Pajamas + messy bun + cookie crumbs'; } } class OfficeStylist extends Stylist { createOutfit() { return 'Blouse, skirt, neat hair, hint of perfume'; } } 
Enter fullscreen mode Exit fullscreen mode

Use this when different environments need their own construction steps but you want one interface: stylist.createOutfit()

🏗️ Abstract Factory – complete lifestyle bundles

The Abstract Factory builds families of related objects, keeping them consistent.
Think of it as a recipe for the whole vibe:

class WFHBundleFactory { createClothes() { return 'Comfy PJs'; } createHair() { return 'Messy bun'; } createExtras() { return 'Crumbs on top'; } } class OfficeBundleFactory { createClothes() { return 'Pressed blouse & skirt'; } createHair() { return 'Freshly washed'; } createExtras() { return 'Light perfume'; } } 
Enter fullscreen mode Exit fullscreen mode

Perfect when you need coordinated sets — outfit, grooming, accessories — all matching the environment.


đź§Ş Testing & Single Responsibility (Tiny but Mighty)

Factories also shine when it comes to unit testing and Single Responsibility.
By moving setup code into one spot, your classes stay focused on behavior — and tests can inject mocks without wading through construction logic.
(In outfit terms: you test how you wear the clothes, not how the wardrobe picked them.)


A Real-World Moment

Here’s one example from a past project:

I didn’t write the drag-and-drop code myself — but I remember noticing it.

It worked fine in Chrome, but in Firefox, things got a little weird. At the time, most users were on Chrome, so the issues went unnoticed for a while. But when the team finally needed to fix it, they had a choice: scatter browser-specific if-else checks throughout the code… or find a cleaner solution.

 // Before Factory - browser checks scattered everywhere const handleDragStart = (event) => { if (navigator.userAgent.includes('Firefox')) { event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setData('text/plain', ''); } else if (navigator.userAgent.includes('Chrome')) { event.dataTransfer.effectAllowed = 'copyMove'; } // This logic gets duplicated in every drag handler... }; // After Factory - clean separation const DragHandlerFactory = { create: () => { if (navigator.userAgent.includes('Firefox')) { return new FirefoxDragHandler(); } if (navigator.userAgent.includes('Chrome')) { return new ChromeDragHandler(); } return new DefaultDragHandler(); } }; class FirefoxDragHandler { handleDragStart(event) { event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setData('text/plain', ''); } } class ChromeDragHandler { handleDragStart(event) { event.dataTransfer.effectAllowed = 'copyMove'; } } // Usage - no browser detection needed const dragHandler = DragHandlerFactory.create(); dragHandler.handleDragStart(event); 
Enter fullscreen mode Exit fullscreen mode

The main drag-and-drop code stayed clean. Easy to test. Easy to grow. When Safari needed support later, they just added another handler class — no rewrites, no mess scattered throughout the codebase.


When to Use (and Not Use) Factory Pattern

Factory shines when you have:

  • Multiple variations of similar objects that need different configurations
  • Complex setup logic that you don't want scattered everywhere
  • Objects that need environment-specific behavior (dev vs prod, mobile vs desktop)
// Worth using Factory - multiple complex configurations const APIClientFactory = { create: (environment) => { switch(environment) { case 'development': return new APIClient({ baseURL: 'http://localhost:3000', timeout: 30000, debug: true, retries: 1 }); case 'production': return new APIClient({ baseURL: 'https://api.company.com', timeout: 5000, headers: { 'X-Environment': 'prod' }, retries: 3 }); } } }; 
Enter fullscreen mode Exit fullscreen mode

Skip Factory when:

  • You only have one or two simple variations
  • The setup is straightforward and unlikely to change
  • You're just avoiding a few lines of code
// Overkill - simple toggle doesn't need Factory const theme = isDark ? 'dark' : 'light'; // This is fine 
Enter fullscreen mode Exit fullscreen mode

Rule of three: once you’ve set up the same complex object three times, it’s factory time.

Scaling bonus: factories grow sideways, not everywhere.
Need a new API environment? Add a case to the factory — the rest of the codebase stays blissfully unaware.
Compare that to scattered if-else blocks you'd have to hunt down in dozens of files.

Top comments (0)