Summary of practical design patterns of front end

Design pattern is a programming idea, regardless of front-end and back-end. They will have some same design ideas for programs running in any language and environment. By understanding these ideas, we can improve our programming ability, and writing code will become something to enjoy. We are not only completing the work, but also creating a work, a work of art that belongs to you and is pleasing to the heart- My own understanding

Design principles

You need to follow some guidelines to do everything, and design patterns are no exception. When designing some design patterns, we generally follow the following seven basic principles.

  1. Single responsibility principle
  2. Open closed principle
  3. Liskov Substitution Principle
  4. Dependency Inversion Principle
  5. Interface Segregation Principle
  6. The Least Knowledge Principle
  7. Composite / Aggregate Reuse Principle

Among these principles, I think the more practical and important are: the principle of single responsibility, the principle of open-close and the principle of least knowledge. Because these basic principles are used as conventions, our project will develop healthily and have strong expansibility. Explain these three principles in detail. If you are interested in other principles, you can search them yourself and find them easier.

Single responsibility principle

Single Responsibility Principle

  • An object or method does only one thing.
  • If a method assumes too many responsibilities, the more likely it is to rewrite the method in the process of demand change.
  • Objects or methods should be divided into smaller granularity to improve code readability and system maintainability.

Open close principle

Open for extension, close for modification

  • Open to extension: when there are new requirements or changes, you can extend the existing code to adapt to the new situation.
  • Close to modification: once the design is completed, the source code of the module cannot be infringed, and no one is allowed to modify the existing source code.
  • The core idea of opening and closing is to program abstractly, not concretely, because abstractions are relatively stable.
  • Let the class depend on fixed abstraction, so the modification is closed;
  • Through the object-oriented inheritance and polymorphism mechanism, we can inherit the abstract body, change the inherent behavior by overwriting its methods, and realize new extension methods. Therefore, it is open to extension.

Least known principle

The Least Knowledge Principle

  • Or Law of Demeter, which describes a strategy to maintain loose code coupling. Each unit has only limited knowledge of other units and only knows the units closely related to the current unit;
  • Modern object-oriented programming languages usually use "." as the access identifier, and LoD can be simplified to "use only one dot".
  • That is, the code a.b.Method() violates the   LoD, while a.Method() conforms to LoD. For example, people can command a dog to walk, but they should not direct the dog's legs to walk, but the dog should direct its legs to walk.

Design pattern

In JS design pattern, the core idea is encapsulation change.
Separate change from invariance to ensure that the changed part is flexible and the unchanged part is stable.

Constructor mode

Constructor Pattern features:

  • Constructor is a special function used to initialize a new object after its memory is allocated. Almost everything in JavaScript is an object.
  • At the same time, the constructor can use parameters to set the value of the method of the member property when the object is first created.
  • Object definition: status, attribute, behavior

Example code:

function GirlFriend(name, age, cup, height) {
    this.name = name;
    this.age = age;
    this.cup = cup;
    this.height = height;
}

GirlFriend.prototype.myIndentity = function () {
    console.log(`I'm a created girlfriend. My name is ${this.name}`);
};

const aGirl = new GirlFriend('Small A girl friend', 28, '36F', 'LOL');
const bGirl = new GirlFriend('Small B girl friend', 19, '36C', 'DOTA');

Module mode

Module Pattern features:

  • Module mode is the most commonly used mode in the front end, which is especially suitable for maintaining an independent code fragment.
  • Module mode is the basis of almost all JS popular plug-ins and frameworks, such as jQuery, Backbone, Ember, etc.
  • You can create private and public attributes.
  • Module mode uses IIFE (immediate call function expression) and closed packet to form private scope.

Example code:

const Exposer = (function () {
    let privateVariable = 10;

    const privateMethod = function () {
        console.log('Inside a private method!');
        privateVariable++;
    }

    return {
        first: function () {
            console.log('This is a method I want to expose!');
        },
        second: function () {
            privateMethod();
        }
    };
})();

Exposer.first();        // Output: This is a method I want to expose!
Exposer.second();       // Output: Inside a private method!
Exposer.privateMethod;  // undefined

Singleton mode

Singleton Pattern features:

  • The singleton mode limits the number of instances to one. This unique instance is called a singleton.
  • Singleton mode is suitable for handling some logic in a centralized place in a wide range of systems.
  • The use of global variables is reduced, thereby eliminating the risk of namespace pollution and naming conflicts.
  • Example: database connection pool. The connection pool needs to manage the life cycle of many connections so that connections can be reused when requests are made to the database in the future.

Example code:

const Singleton = (function () {
    let instance; // example

    function init() {
        return {
            publicProperty: 'I am a property',
            publicMethod: function () {
                console.log('I am a mehtod');
            },
        };
    }

    return {
        getInstance: function () {
            if (!instance) {
                instance = init();
            }
            return instance;
        }
    };
})();

// Get instance
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();

Adapter mode

Adapter Pattern features:

  • Adapter pattern can convert an interface to a completely different interface, which allows different components of the interface to work together.
  • Example: the newly developed components are completely different from the previous interfaces, and can be compatible with the existing old code through the adapter mode.

Example code:

// Package pricing - old service
function Shipping() {
    this.request = function (zipStart, zipEnd, weight) {
        // ...
        return "$49.75";
    }
}

// Package pricing - new service (with authority verification and logical decoupling)
function AdvancedShipping() {
    this.login = function (credentials) { /* ... */ };
    this.setStart = function (start) { /* ... */ };
    this.setDestination = function (destination) { /* ... */ };
    this.calculate = function (weight) { return "$39.50"; };
}

// Adapter
function ShippingAdapter(credentials) {
    // create a new service
    const shipping = new AdvancedShipping();
    // Verify login permissions
    shipping.login(credentials);

    return {
        // Maintain the old service interface and adapt the content logic to the new service
        request: function (zipStart, zipEnd, weight) {
            shipping.setStart(zipStart);
            shipping.setDestination(zipEnd);
            return shipping.calculate(weight);
        }
    };
}
// Call old service
const shipping = new Shipping();
const cost = shipping.request("78701", "10010", "2 lbs");
// Call new service code
const credentials = { token: "30a8-6ee1" };
const adapter = new ShippingAdapter(credentials);
const cost = adapter.request("78701", "10010", "2 lbs");

Factory mode

Simple Factory Pattern definition:

  • The factory model, as the name suggests, is to create objects.
  • The factory model is similar to the real factory production line, which can produce a large number of similar goods.
  • The advantage of factory mode is that it can solve multiple similar problems and reduce a large amount of redundant code.

Example code:

const FactoryGirlFriend = function () {
    function GirlFriend(name, age, cup, height, nationality) {
        this.name = name;
        this.age = age;
        this.cup = cup;
        this.height = height;
        this.nationality = nationality
    }
    this.create = function (category) {
        switch (category) {
            case 'Imperial sister type':
                return new GirlFriend('Small A girl friend', 28, '36F', 170, 'China')
                break;
            case 'Lori type':
                return new GirlFriend('Small B girl friend', 19, '36C', 160, 'Japan')
                break;
            default:
                throw new Error('Parameter error')
        }
    };
}

const aGirl = FactoryGirlFriend.create('Imperial sister type');
const bGirl = FactoryGirlFriend.create('Lori type');

Strategy mode

Strategy Pattern features:

  • The policy pattern defines a series of algorithms, encapsulates them one by one, and enables them to replace each other.
  • The policy pattern can effectively avoid many if conditional statements.
  • The policy pattern conforms to the open closed principle, making the code easier to understand and expand.
  • The code in the policy pattern can be reused.

Example code:

const express = {
    'Shunfeng': function (package) {
        // Path calculation
        return "¥45.95";
    },
    'JD.COM': function (package) {
        // Path calculation
        return "¥39.40";
    },
    'Yunda': function (package) {
        // Path calculation
        return "¥24.45";
    }
};
const calculateExpense = function (type, package) {
    return express[type] ? express[type](package) : 0;
};
calculateExpense('JD.COM', 1); // ¥39.40
calculateExpense('Yunda', 1); // ¥24.45

Observer mode

Observer Pattern features:

  • The observer mode includes two types of objects: observation target and observer,
  • A target can have any number of dependent observers,
  • Once the state of the observation target changes, all observers will be notified.

Example code:

// Define a host object
class Subject {
    constructor() {
        this.observers = []; // Observer
    }
    add(observer) { // add to
        this.observers.push(observer)
    }
    remove(observer) { // remove
        this.observers = this.observers.filter(item => item !== observer);
    }
    notify() { // notice
        this.observers.forEach(item => {
            item.update();
        })
    }
}
// Define the observed object
class Observer {
    constructor(name) {
        this.name = name;
    }
    update() {
        console.log(`my name is:${this.name}`);
    }
}

// call
const observer1 = new Observer('observer1');
const observer2 = new Observer('observer2');

const subject = new Subject();
subject.add(observer1);
subject.add(observer2);
subject.notify();

Publish subscribe mode

Publish subscribe pattern features:

  • Publish + subscribe = observer mode?
  • In the observer mode, the observer knows the Subject, and the Subject keeps a record of the observer. However, in the publish subscribe mode, publishers and subscribers do not know the existence of each other, and they only communicate through the message broker.
  • In publish subscribe mode, components are loosely coupled, as opposed to observer mode.

Example code:

const publisher = {
    topics: [], // Subscribable topics
    subscribe(key, fn) { // subscribe
        if (!this.topics[key]) {
            this.topics[key] = [];
        }
        this.topics[key].push(fn);
    },
    unSubscribe(key, fn) { // delete
        let fns = this.topics[key];
        for (let i = 0; i < fns.length; ++i) {
            if (fns[i] === fn) {
                fns.splice(i, 1);
            }
        }
    },
    publish(...args) { // release
        let key = args.shift();
        let fns = this.topics[key];
        if (!fns || fns.length <= 0) return;
        for (let i = 0, len = fns.length; i < len; i++) {
            fns[i].apply(this, args);
        }
    }
}

// call
publisher.subscribe('name', (name) => {
    console.log(`your name is ${name}`);
})
publisher.subscribe('gender', (gender) => {
    console.log(`your gender is ${gender}`);
})
publisher.publish('name', 'Ultraman');  // your name is Altman
publisher.publish('gender', 'male');  // your gender is male

Example code: the role of publish subscribe mode in vue

// Traverse the properties of the data object of the incoming instance and set it as the accessor property of the Vue object
function observe(obj, vm) {
    Object.keys(obj).forEach(function (key) {
        defineReactive(vm, key, obj[key]);
    });
}
// Set as accessor property and use subscription publication mode in its getter and setter functions. Listen to each other.
function defineReactive(obj, key, val) {
    // The observer mode is used here. It defines a one to many relationship, allowing multiple observers to listen to a topic object,
    // When the state of the subject object changes, all observer objects will be notified, and the observer object can update its own state.
    // Instantiate a topic object, and there is an empty observer list in the object
    let dep = new Dep();
    // Set each attribute of data as the accessor attribute of Vue object, and the attribute name is the same as that in data
    // Therefore, each time Vue.data is modified, the get and set methods below will be called. Then it will listen for the input event of v-model,
    // When the value of input is changed, the data of Vue.data is changed accordingly, and then the set method here is triggered
    Object.defineProperty(obj, key, {
        get: function () {
            // The Dep.target pointer points to the watcher and adds the subscriber watcher to the subject object Dep
            if (Dep.target) {
                dep.addSub(Dep.target);
            }
            return val;
        },
        set: function (newVal) {
            if (newVal === val) {
                return
            }
            val = newVal;
            // console.log(val);
            // Notify watchers in the subscriber list
            dep.notify();
        }
    });
}

// Subject object Dep constructor
function Dep() {
    this.subs = [];
}
// Dep has two methods, adding subscribers and publishing messages
Dep.prototype = {
    addSub: function (sub) {
        this.subs.push(sub);
    },
    notify: function () {
        this.subs.forEach(function (sub) {
            sub.update();
        });
    }
}

proxy pattern

Proxy Pattern features:

  • Provide a proxy for other objects to control access to this object, and the client can't even perceive the existence of the proxy layer.
  • The proxy object can be instantiated instead of the ontology object. At this time, the ontology object is not really instantiated, and it can be instantiated at an appropriate time.
  • Proxy mode can delay the creation of expensive ontology objects. It will delay the instantiation of ontology until a method is called.

Example code: image loading

// Normal mode
const myImage = (function () {
    const imgNode = document.createElement('img');

    return {
        setSrc: function (src, id) {
            imgNode.src = src;
            document.getElementById(id).appendChild(imgNode);
        }
    }
})();

// Loading pictures in normal mode
myImage.setSrc('https://tengmed.com/a.png', "avatar");

// proxy pattern
const ProxyImage = (function () {
    const img = new Image();
    img.onload = function () {
        myImage.setSrc(this.src, this.id);
    };
    return {
        setSrc: function (src, id) {
            myImage.setSrc('http://loading.gif', id); //  Add default loading picture
            img.src = src;
            img.id = id;
        }
    }
})();

// Loading pictures in proxy mode
ProxyImage.setSrc('https://tengmed.com/a.png', "avatar");

Example code: cache agent

let mult = function () {
    let a = 1;
    for (let i = 0, ilen = arguments.length; i < ilen; i += 1) {
        a = a * arguments[i];
    }
    return a;
};
// Computational addition
let plus = function () {
    let a = 0;
    for (let i = 0, ilen = arguments.length; i < ilen; i += 1) {
        a += arguments[i];
    }
    return a;
}
// Surrogate function
let proxyFunc = function (fn) {
    let cache = {};  // Cache object
    return function () {
        let args = Array.prototype.join.call(arguments, ',');
        if (args in cache) {
            return cache[args];   // Using cache proxy
        }
        return cache[args] = fn.apply(this, arguments);
    }
};
let proxyMult = proxyFunc(mult);
console.log(proxyMult(1, 2, 3, 4)); // 24
console.log(proxyMult(1, 2, 3, 4)); // Cache fetch 24

let proxyPlus = proxyFunc(plus);
console.log(proxyPlus(1, 2, 3, 4));  // 10
console.log(proxyPlus(1, 2, 3, 4));  // Cache fetch 10

Keywords: Javascript Design Pattern

Added by MajorMo on Fri, 19 Nov 2021 11:37:23 +0200