banner
无关风月

无关风月

当我与世界初相见
email
douban
bilibili

JavaScript Design Patterns

Explore the JavaScript implementations of some classic and modern design patterns.

There is no best, only the most suitable.

There are a total of 23 design patterns divided into 3 categories.

Pattern TypeDesign Patterns
CreationalSingleton, Factory, Builder
StructuralAdapter, Decorator, Proxy
BehavioralStrategy, Observer, Publisher-Subscriber, Chain of Responsibility, Mediator

Singleton Pattern#

A class that has only one instance and provides a global access point to it.

image-20230319225821142

  • Singleton: Specific class that we need to access, and visitors need to get its instance.

  • instance: Singleton, an instance of the specific class. The specific class usually provides the getInstance method to obtain the singleton.

  • getInstance: Method to get the singleton.

class Singleton {
    let _instance = null;
    static getInstance() {
        if (!Singleton._instance) {
          Singleton.instance = new Singleton()
        }
        // If the unique instance already exists, return it directly
        return Singleton._instance
    }
}

const s1 = Singleton.getInstance()
const s2 = Singleton.getInstance()

console.log(s1 === s2)  // true

Example#

Vuex implements a global store to store all application states. The implementation of this store is a typical application of the singleton pattern.

Use Cases#

  1. If instantiating a class consumes a lot of resources, singleton can be used to avoid performance waste.

  2. Singleton can be used to ensure access consistency when there is a need for shared state.

Factory Pattern#

Factory pattern: Returns instances of different classes based on different parameters. Separates the creation of objects from their implementation. Implementation is complex, but usage is simple. Just use the methods provided by the factory.

Advantages:

  1. Good encapsulation, visitors do not need to know the creation process, and the code structure is clear.

  2. Good extensibility, the factory method isolates users and the creation process, conforming to the open-closed principle.

Disadvantages:

It adds abstraction to the system, resulting in additional system complexity, and should not be overused.

Example#

document.createElement creates DOM elements. This method uses the factory pattern. The internal implementation is complex, but the external usage is simple.

Use Cases#

  • Object creation is complex and visitors do not need to know the creation process.

  • Need to handle a large number of small objects with similar/same attributes.

Adapter Pattern#

Used to solve compatibility issues when interfaces/methods/data are incompatible. Converts them into the format expected by visitors.

image-20230319230730502

Characteristics:

  1. Integrating third-party SDKs.
  2. Encapsulating old interfaces.

Decorator Pattern#

  • Dynamically adds additional responsibilities to an object, serving as an alternative to inheritance.
  • Extends the original object through wrapping, allowing the original object to meet more complex requirements without affecting other objects derived from the same class.

image-20230319230958645

Has a taste of prototype chain.

Proxy Pattern#

Provides a substitute or placeholder for an object to control access to it.

Use Cases#

  • ES6 Proxy
  • jQuery.proxy() method

Difference Between Decorator and Proxy Patterns#

  • Decorator Pattern: Extends functionality, keeps the original functionality unchanged and can be used directly.
  • Proxy Pattern: Displays the original functionality, but with restrictions.

Strategy Pattern#

Defines a series of algorithms and selects which algorithm to use based on input parameters.

image-20230319231627454

Example#

Scenario: Discounts for Singles' Day. Discounts include $20 off for orders over $200, $50 off for orders over $300, and $100 off for orders over $500. How would you implement this requirement?

// if-else: Bulky and difficult to modify
function priceCalculate(discountType,price){
    if(discountType === 'discount200-20'){
        return price - Math.floor(price/200) * 20;
    }else if(discountType === 'discount300-50'){
        return price - Math.floor(price/300) * 50;
    }else if(userType === 'discount500-100'){
        return price - Math.floor(price/500) * 100;
    }
}
// Rewrite using the strategy pattern, hiding the algorithm, and reserving an entry point for adding strategies for easy expansion

const priceCalculate = (function(){
    const discountMap = {
        'discount200-20': function(price) {
            return price - Math.floor(price / 200) * 20;
        },
        'discount300-50': function(price) {
            return price - Math.floor(price/300) * 50;
        },
        'discount500-100': function(price) {
            return price - Math.floor(price/500) * 100;
        },
    };
    return {
        addStategy(stategyName,fn){
            if(discountMap[stategyName]) return;
            discountMap[stategyName] = fn;
        },
        priceCal(discountType,price){
            return discountMap[discountType] && discountMap[discountType](price);
        }
    }
})()

Advantages:

  1. Strategies are independent of each other and can be switched. This improves flexibility and reusability.
  2. No need to use if-else for strategy selection, which improves maintainability.
  3. Good scalability, satisfying the open-closed principle.

Disadvantages:

  1. Strategies are independent of each other, so some complex algorithm logic cannot be shared, resulting in resource waste.
  2. Users need to understand the specific strategy implementation when using strategies, which does not meet the principle of least knowledge and increases usage costs.

Use Cases#

  1. Scenarios where algorithms need to be freely switched.
  2. Multiple algorithms have slight differences in behavior, and the strategy pattern can be considered for dynamic selection of algorithms.
  3. Multiple conditional judgments can be avoided by using the strategy pattern.

Observer Pattern#

An object (called the subject) maintains a list of objects (called observers) that depend on it and automatically notifies them of any changes in state.

Pros and Cons#

Advantages: The biggest advantage of the observer pattern is that observers are notified when the target changes.

Disadvantages: The subject and observers are coupled together. To implement the observer pattern, both the subject and observers are required to achieve a responsive effect.

Use Cases#

Assume that Bilibili users are observers and Bilibili updaters are subjects. Many Bilibili users follow a specific updater, and when this updater uploads a new video, these subscribed Bilibili users will be notified.

Publisher-Subscriber Pattern#

Based on a topic, objects that want to receive notifications (called subscribers) subscribe to the topic through custom events, and objects that are activated by events (called publishers) notify subscribers by publishing topic events.

Use Cases#

WeChat follows many official accounts, and when an official account publishes a new article, we receive a message notification that the article has been updated.

In this case, the official account is the publisher, and the users are subscribers. Users register the subscription of official accounts' events with the event dispatcher. When the publisher publishes a new article, it publishes the event to the event dispatcher, and the dispatcher sends a message to the subscribers.

Publisher-Subscriber Pattern in Vue's Two-Way Binding#

image-20230319232727395

Vue's two-way binding is implemented through data interception and the publish-subscribe pattern.

  • By using DefineProperty to intercept the setter and getter of each data, and adding a subscriber list to each data, the list records all components that depend on this data.

    Reactive data is the publisher.

  • Each component corresponds to a Watcher subscriber. When the component's rendering function is executed, its Watcher is added to the subscriber list of the reactive data it depends on.

    This process is called "dependency collection".

  • When reactive data changes, the setter is triggered, and the setter is responsible for notifying the Watcher in the subscriber list of the data. The Watcher triggers component re-rendering to update the view.

    The view layer is the subscriber.

Difference Between Observer and Publisher-Subscriber Patterns#

The observer pattern is one of the classic software design patterns, while the publish-subscribe pattern is just a message paradigm in software architecture.

Observer PatternPublisher-Subscriber Pattern
2 roles3 roles
Focus on subjectFocus on event dispatcher

The relationship between observer and subject is established by the subject actively, and the subject needs at least three methods: add observer, remove observer, and notify observer.

The publish-subscribe pattern is based on a central event dispatcher to establish the entire system. The publisher and subscriber do not communicate directly, but the publisher hands over the message to the dispatcher for management, and the subscriber subscribes to the messages in the dispatcher according to its own situation.

The implementation of the publish-subscribe pattern internally utilizes the observer pattern, but due to the introduction of the publish-subscribe center, the communication management between the producer and consumer becomes more manageable and extensible.

Understanding the Difference Between Observer Pattern and Publish-Subscribe Pattern - 掘金 (juejin.cn)

JS 常用的六种设计模式介绍 - 掘金 (juejin.cn)

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.