Fundamentals 19 min read

Mastering Design Patterns: Essential Concepts and Real-World Frontend Examples

This article introduces design patterns, explains their origins and core principles, and demonstrates how common patterns such as Publish‑Subscribe, Strategy, Adapter, Proxy, Chain of Responsibility, and Singleton can be applied in frontend development with practical code examples and usage guidelines.

Weimob Technology Center
Weimob Technology Center
Weimob Technology Center
Mastering Design Patterns: Essential Concepts and Real-World Frontend Examples

1. What Is a Design Pattern?

Before discussing design patterns, we need a basic understanding of the concept.

1.1 Definition

Design patterns are a set of reusable, general‑purpose solutions to common problems that arise during software design. They are not directly translatable into source code but represent best‑practice guidelines that developers should follow when architecting a system.

1.2 Origin of Design Patterns

In 1994, four authors (often called the "Gang of Four") published *Design Patterns – Elements of Reusable Object‑Oriented Software*, which introduced the concept of design patterns in software development. Their patterns are based on object‑oriented design principles such as programming to an interface rather than an implementation and preferring object composition over inheritance.

1.3 Design Patterns and Frontend Development

Some may wonder whether design patterns, being rooted in object‑oriented programming, are irrelevant to JavaScript, which is not strictly class‑based. The answer is no—design patterns are fundamentally a programming mindset and can be applied regardless of whether the language is object‑oriented or procedural.

1.4 Design Pattern Principles

There are many ways to describe pattern principles; here we briefly reference the SOLID principles.

2. Common Frontend Design Patterns

2.1 Publish‑Subscribe Pattern

Definition: Publish/Subscribe is a messaging paradigm where the sender (publisher) does not send messages directly to specific receivers (subscribers). Instead, messages are categorized and delivered to all subscribers of the relevant category.

The publish‑subscribe pattern is one of the most frequently mentioned patterns in frontend development; many state‑management tools and component‑communication mechanisms rely on it.

It can be seen as an extension of the Observer pattern, adding an intermediary to decouple the subject from its observers.

Example:

Little Red and Little Ming both want milk.

In the Observer pattern, both call the milk station directly.

In the Publish‑Subscribe pattern, they call a broker; the broker contacts the milk station, allowing other drinks (e.g., juice) to be requested through the same broker without contacting the station again.

Publish‑Subscribe ↑

Observer ↑

Typical Use Cases

Event listening, event bus

When to Use Publish‑Subscribe

Use it when modules are independent, have one‑to‑many dependencies, unstable dependencies, or are developed by different teams.

Things to Watch

Increasing numbers of subscribed events can raise maintenance costs.

2.2 Strategy Pattern

Definition: The strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable.

It can be thought of as an upgraded if‑else structure where the algorithm is selected at runtime.

Example: Rendering operation buttons on a list page based on backend data.

// Assume operators returned from backend: ['edit','add','delete']
    function renderOperators(operators) {
        return operators.map(item => {
            if (item === 'edit') {
                // return ...
            } else if (item === 'add') {
                // return ...
            } else if (item === 'delete') {
                // return ...
            } else {
                return null;
            }
        })
    }

When requirements change, adding new branches makes the function bulky. Using the strategy pattern:

const strategies = {
        edit: function(key) { /* do something */ },
        add: function(key) { /* do something */ },
        delete: function(key) { /* do something */ }
    };
    function renderOperators(operators) {
        return operators.map(item => strategies[item]());
    }

This adheres to the Open/Closed principle.

Typical Use Cases

Multiple login methods, form validation, dynamic UI rendering

When to Use Strategy

When each conditional branch is independent, complex, and may need flexible composition.

Things to Watch

Callers must understand the differences between strategies to choose the appropriate one.

2.3 Adapter Pattern

Definition: The adapter pattern converts one interface into another that a client expects, allowing otherwise incompatible classes to work together.

Example: Reusing a coupon component from a CRM system in a storefront by adapting the backend data format to match the component’s expected input.

Typical Use Cases

Interface data conversion

2.4 Proxy Pattern

Definition: The proxy pattern provides a surrogate object that controls access to another object, while the actual work is performed by the original object.

Example: Email sending with a proxy that filters certain domains before delegating to the real send function.

const emailList = ['qq.com', '163.com', 'weimob.com'];
    const ProxyEmail = function(email) {
        if (emailList.includes(email)) {
            // block
        } else {
            SendEmail.call(this, email);
        }
    };
    const SendEmail = function(email) {
        // send email
    };
    ProxyEmail('cctv.com');
    ProxyEmail('ojbk.com');

Another example: wrapping console.log so that logs only appear when a specific query parameter is present.

function myLog(...args) {
        if (!location.search.includes('xxx')) {
            return;
        }
        console.log(...args);
    }

Typical Use Cases

Image preloading, data caching, request interceptors

When to Use Proxy

Single‑purpose modules that need controlled access

Extending behavior of a method without modifying the original

Enforcing interaction constraints between modules

Things to Watch

Additional indirection can affect performance

Proxy changes the interface indirectly; it does not alter the original API

Unlike decorators, proxies aim to control access rather than add functionality

2.5 Chain of Responsibility Pattern

Definition: The chain of responsibility pattern gives multiple objects a chance to handle a request, decoupling the sender from the receiver.

Example: Device application flow where address selection, verifier selection, and inventory check must occur sequentially, each step passing data to the next.

const Chain = function(fn) {
        this.fn = fn;
        this.next = null;
        this.setNext = function(chain) {
            this.next = chain;
            return this.next;
        };
        this.run = function(...args) {
            const nextData = this.fn(...args);
            this.next?.run?.(nextData);
        };
    };
    const applyDevice = function(data) {};
    const chainApplyDevice = new Chain(applyDevice);
    const selectAddress = function(data) {};
    const chainSelectAddress = new Chain(selectAddress);
    const selectChecker = function(data) {};
    const chainSelectChecker = new Chain(selectChecker);
    chainApplyDevice.setNext(chainSelectAddress).setNext(chainSelectChecker);
    chainApplyDevice.run();

Typical Use Cases

JavaScript event bubbling

Koa’s onion middleware model

Webpack plugins

Things to Watch

Debugging can be harder due to indirect calls

Improper configuration may leave requests unhandled

2.6 Singleton Pattern

Definition: The singleton pattern ensures a class has only one instance and provides a global access point to it.

Two JavaScript implementations are shown: a class‑based static method and a closure‑based function.

// Class version
    class Singleton {
        constructor(name) { this.name = name; }
        static getInstance(name) {
            if (!this.instance) { this.instance = new Singleton(name); }
            return this.instance;
        }
    }
    const a = Singleton.getInstance('a1');
    const b = Singleton.getInstance('b2');
    console.log(a == b); // true
// Closure version
    const Singleton = function(name) { this.name = name; };
    Singleton.getInstance = (function() {
        let instance;
        return function(name) {
            if (!instance) { instance = new Singleton(name); }
            return instance;
        };
    })();
    const a = Singleton.getInstance('a1');
    const b = Singleton.getInstance('b2');
    console.log(a === b); // true

In JavaScript, a plain object can serve as a singleton:

const single = {};

Typical Use Cases

Shared utilities

Global modal dialogs

State‑management stores such as Vuex or Redux

Things to Watch

Avoid polluting the global namespace

Developers unfamiliar with the singleton may find debugging difficult

3. Summary

After learning design patterns, we realize they are ubiquitous. Previously, we relied on experience; many of those heuristics align with established patterns.

Design patterns are language‑agnostic tools that improve code robustness and maintainability, but they should be applied judiciously. Blindly forcing patterns can lead to over‑engineered, hard‑to‑maintain code.

Understanding the context and limitations of each pattern enables better architectural decisions.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

design-patternssoftware architectureFrontend Development
Weimob Technology Center
Written by

Weimob Technology Center

Official platform of the Weimob Technology Center

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.