In software engineering, design pattern is a proposed solution to various problems commonly existing (recurring) in software design. — Wikipedia

Recently, I have learned the core Principles and Application Practice of JavaScript Design Pattern by Tao Department’s front-end god Xiuyan, which has benefited me a lot. I have been immersed in the business of repeating the wheel of the switch shrimp, feeling full of work every day, but did not feel that I was making progress. This article has been digested twice and recorded with reference to this article. Help myself and other students in the same situation to deepen their understanding of design patterns and apply them to their daily work

Create – constructor pattern

In JavaScript, we use constructors to initialize objects, which applies the constructor pattern.

// Let's say you're a programmer at Riot and your boss wants you to redevelop LOL using JS

// First create a hero into the system
const Aixi = {
  name: 'athey'.title: 'Ice Shooter'.role: 'striker'
}

// Create second hero
const Zhaoxin = {
  name: 'xenzhao'.title: 'Deppon Steward'.role: 'soldiers'
};

// There are still more than 100 heroes to be created and we have to work late into the night
function Hero(name, title, role) {
  this.name = name;
  this.title = title;
  this.role = role;
};

// Create a hero
const Jiakesi = new Hero('Jaques'.Master of Weapons.'soldiers');
const Yidashi = new Hero(Master Yi.'Master of the Infinite Sword'.'the assassin');
Copy the code

The constructor encapsulates the process of assigning name, title, and role to objects, ensuring that each object has these attributes and the commonality remains unchanged. Meanwhile, the value operation of name, title, and role is open to ensure the flexibility of personality

If we use the constructor pattern, we are essentially abstracting the variation and immutability of each object instance. So with the factory pattern, what we want to do is to abstract away the variation and immutability between different constructors (classes)

Creation – Simple Factory pattern

The product manager mentioned that different hero types have different characteristics, such as 500 yards for shooters and 125 yards for warriors

// In order to solve the above problems, Hero and Longer function Longer(name, title, role) {this.name = name; this.title = title; This. Role = 'archer '; this.attackDistance = 500; } function Shorter(name, title, role) { this.name = name; this.title = title; This. Role = 'soldier '; this.attackDistance = 125; } function handleRole(name, title, role) {switch (role) {case: 'shooter ': return new Longer(name, title, role); break; Case: 'Warrior ': return new Shorter(name, title, role); break; }} // We can create const Jiakesi = new handleRole(' Jakes ', 'Weapon Master ',' warrior '); Const Aixi = new handleRole(' Aixi ', 'handleRole ',' handleRole '); Hero function Hero(name, title, role, attackDistance) {this.name = name; // Hero function Hero(name, title, role, attackDistance) {this.name = name; this.title = title; this.role = role; this.attackDistance = attackDistance; }; Function HeroFactory(name, title, role) {let attackDistance; Switch (role) {case "Archer ": attackDistance = 500; break; Elseif "Assassin ": attackDistance = 125; break; } return new Hero(name, title, role, speciality); } const Jiakesi = new HeroFactory(' Jakes ', 'Weapon Master ',' Warrior '); Const Aixi = new HeroFactory(' Aixi ', 'Frosty Archer ',' Archer ');Copy the code

Creation – Abstract Factory pattern

Abstract factory model is defined as the creation of other factories around a gigafactory. Abstract factory is a good material to support the “open and closed principle”

Function Mode(type, map, mechanism) {this.type = type; function Mode(type, map, mechanism) {this.type = type; this.map = map; this.mechanism = mechanism; } function GameFactory(type) {let map; let mechanism; Switch (type) {case 'map ': map =' map '; Mechanism = 'normal mechanism '; break; Case 'clone Mode ': map =' Clone Battle '; Mechanism = 'clone mechanism '; break; default: map = ''; mechanism = ''; brreak; } return new Mode(type, map, mechanism); } // create a cloneMode const cloneMode = GameFactory(' clone '); // After a while, the product manager decided that the clone mode was too slow and decided to reduce the cooldown of skills in the game mechanics by 40%; // You need to modify the clone mechanism logic in GameFactory; // After a while, a Godzilla was added to the clone mode map to increase the difficulty of the game. // The GameFactory became cluttered, bloated and difficult to maintain due to several changes. Function xiaguMap() {// Summoner canyon map logic} function cloneMap() {// Map factory logic} // map factory function MapFactory(type) {switch (type) {case 'xiaguMap ': return new xiaguMap(); Case 'Clone Battle ': Return cloneMap()} function currentMechanism() {cloneMechanism() {cloneMechanism() {cloneMechanism() {cloneMechanism()} // Function MechanismFactory(type) {switch (type) {case 'normal mode ': return new currentMechanism(); Case 'cloneMechanism ': return new cloneMechanism()}} function GameFactory(type) {let map; let mechanism; Switch (type) {case 'normal match ': map = MapFactory(' call '); Mechanism = MechanismFactory(' normal mode '); break; Case 'clone mode ': map = MapFactory(' clone Mode '); Mechanism = MechanismFactory(' clone '); break; default: map = ''; mechanism = ''; brreak; } return new Mode(type, map, mechanism); } // The good times don't last long, the product manager thinks that there will be a new temporary mode in the New Year, called "Soaring Mode". The map will use the New Year snowman map, and the game mechanism will be completely changed; // We have to modify the GameFactory logic, add the type attribute, and write a new logic. After the New Year, we have to change back; // This modification of the "built factory" violates the open closed principle (classes, modules, functions can be extended, but cannot be modified); // How to avoid directly modifying the logic inside the factory? // When you're out chatting with your colleagues after lunch, you get inspiration from the Java guy's abstract classes. Class GameFactory {createMap() {throw new Error(" The abstract factory method is not allowed to call directly, you need to overwrite me!" ); } createMechanism() {throw new Error(" Abstract factory method is not allowed to call directly, you need to override me!" ); }} class Map {init() {throw new Error(" Abstract factory methods are not allowed to call directly, you need to overwrite me!" ); }} class Mechanism {init() {throw new Error(" The abstract factory method is not allowed to call directly, you need to override me!" ); }} FlyMap extends Map {init() {FlyMechansims extends Map {FlyMechansims}} Init () {FlyModeFactory extends GameFactory {createMap() {return new FlyMap(); } createMechansims() { return new FlyMechansims(); }} // Add a new FlyMode const FlyMode = new FlyModeFactory(); / / summary: // FlyModeFactory: Inherits from the abstract factory and implements the methods declared in the abstract factory, which are used to instantiate the final target product // Map, Mechanism, abstract product: Methods implemented in a concrete factory depend on products, and abstract products declare the commonalities and specifications of these products // FlyMap, FlyMechansims, concrete products: see Abstract ProductsCopy the code

Creation – singleton pattern

What the singleton pattern wants to do is that no matter how many times we try to create it, it will only give you back a single instance of the one created the first time.

// Use 'es6 class' to create singleton mode class GameInfo {}; const player1 = new GameInfo(); const player2 = new GameInfo(); // Players cannot share information console.log(player1 === player2) // false // Get the same object from GameInfo getInstance() { if(! GameInfo.instance) { GameInfo.instance = new GameInfo(); } return GameInfo.instance; }}; const player3 = GameInfo.getInstance(); const player4 = GameInfo.getInstance(); Console. log(player3 === player4) // true // vuex uses singledog. getInstance = (function() {// Let instance = null return function() {// Let instance = null return function() { Instance = new SingleDog()} return instance}})()Copy the code

My understanding of the singleton pattern is to share a block of memory, new an instance does not create new memory.

Interview question: Implement a globally unique popover

// Closure let Modal = (function() {let Modal = null; return function() { if (! modal) { modal = document.createElement("div"); modal.innerHTML = "this is modal!" ; modal.style.display = "none"; modal.id = "modal"; document.body.appendChild(modal); } return modal; }; }) (); document.getElementById("open").addEventListener("click", () => { let modal = new Modal(); modal.style.display = "block"; }); document.getElementById("close").addEventListener("click", () => { let modal = new Modal(); modal.style.display = "none"; }); // Class class Modal {constructor() {this.dom = null; if (! Modal.instance) { this.dom = document.createElement("div"); this.dom.innerHTML = "this is modal"; this.dom.id = "modal"; this.dom.style.display = "none"; this.dom.show = this.show.bind(this); this.dom.hide = this.hide.bind(this); document.body.appendChild(this.dom); Modal.instance = this.dom; } return Modal.instance; } show() { this.dom.style.display = "block"; } hide() { this.dom.style.display = "none"; } } const btnShow = document.getElementById("open"); const btnHide = document.getElementById("close"); btnShow.addEventListener("click", () => { let modal = new Modal(); modal.show(); }); btnHide.addEventListener("click", () => { let modal = new Modal(); modal.hide(); });Copy the code

The create-prototyping pattern

The prototype pattern is not only a design pattern, but also a programming paradigm, which is the foundation of JavaScript object-oriented system implementation.

The core idea of the prototype programming paradigm is to use instances to describe objects and use instances as the basis for defining objects and inheritance. In JavaScript, the prototype programming paradigm is represented by inheritance based on prototype chains. Among them, the understanding of prototype and prototype chain is the key.

The JavaScript classes introduced in ECMAScript 2015 are essentially syntactic sugar for JavaScript’s existing prototype-based inheritance. Class syntax does not introduce a new object-oriented inheritance model to JavaScript. – the MDN

In JavaScript, each constructor has a Prototype attribute, which points to the constructor’s prototype object, which has a Construtor attribute pointing back to the constructor; Each instance has a proto property, and when we use the constructor to create an instance, the instance’s ProTO property points to the constructor’s prototype object.

// create a Dog constructor. Age) {this.name = name this.age = age} dog.prototype. eat = function() {console.log(' bones are yummy ') Const dog = new dog (' fortune ', 3)Copy the code

Implement a deep copy

Function deepClone(obj) {let STR = json.stringify (obj); return JSON.parse(str); } function deepClone(obj) {// Return if(typeof obj! == 'object' || obj === null) { return obj; } // let copy = {}; Constructor === Array) {copy = []; // If the object is an Array, define the resulting Array. If (obj.hasownProperty (key)) {// Recursively call copy[key] = deepClone(obj[key]); } } return copy; }Copy the code

Structural – Decorator mode

Decorator pattern, also known as decorator pattern. It is defined as “wrapping and extending the original object to meet the more complex needs of users without changing it”.

In ES7, it’s easy to decorate a class with an @ syntax sugar

Function classDecorator(target) {target.hasdecorator = true return target} // "install" the decorator on the Button class @classdecorator Button {// Button class logic}Copy the code

Decorator in React: HOC

A higher-order component is a function that takes a component as an argument and returns a new component.

import React, { Component } from 'react'

const BorderHoc = WrappedComponent => class extends Component {
  render() {
    return <div style={{ border: 'solid 1px red' }}>
      <WrappedComponent />
    </div>
  }
}
export default borderHoc
Copy the code

Decorates target components

import React, {Component} from 'react' import BorderHoc from './BorderHoc' // Decorate the target with BorderHoc @borderHoc class TargetComponent Extends React.Component {render() {// business logic of the TargetComponent}} // export is a wrapped component export default TargetComponentCopy the code

Structural – adapter mode

The adapter pattern helps solve incompatibilities by transforming the interface of one class into another that the client expects. Does not affect the existing implementation, compatible with the call to the old interface code

Function oldSpecial(params) {function oldSpecial(params) {// oldSpecial(params) {// oldSpecial(params) {// Function Apatar(params) {newSpecial(params); function Apatar(params) {newSpecial(params); } function oldSpecial(params) { Apatar(params); } // The adapter is similar to a converter, a phone adapter that can charge Android and iPhone systems.Copy the code

Structure-agent mode

Proxy mode, as the name suggests, is a mode in which an object cannot directly access another object due to various considerations/restrictions, and a third party (proxy) is required to indirectly access the object.

The four most common types of proxy are event proxy, virtual proxy, cache proxy, and protection proxy

The event agent

The other three modes are described in detail in the gold digger booklet of The Guru. Temporal proxies are probably the most common use of the proxy pattern, where there are multiple children under a single parent element, like this:

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, Initial scale=1.0"> <meta HTTP-equiv =" X-UA-compatible "content=" IE =edge"> <title> event proxy </title> </head> <body> <div Id = "father" > < a href = "#" > link 1 < / a > < a href = "#" > link 2 < / a > < a href = "#" > link < / a > 3 < a href = "#" > link < / a > 4 < a href = "#" > link < / a > < 5 a Href ="#"> Link 6 </a> </div> </body> </ HTML >Copy the code

What we need now is that when clicking each A label with the mouse, a prompt like “I am XXX” will pop up. Click on the first A TAB, for example, and a prompt like “I’m link number 1” pops up. This means that we need to install at least six listeners for six different elements (we usually use loops, as shown below), and if we increase the number of A tags, the performance cost will be even higher.

// If no proxy mode is used, Const aNodes = document.getelementById ('father').getelementsByTagName ('a') const aLength = anodes.length for(let i=0; i<aLength; i++) { aNodes[i].addEventListener('click', Function (e) {e.preventDefault() alert(' I'm ${aNodes[I].innertext} ')})} function(e) {e.preventDefault() alert(' I'm ${aNodes[I].innertext} ')})} Click events "bubble" to the parent div and are listened on. It can greatly improve the performance of our code. // Const father = document.getelementById ('father') // Father. AddEventListener ('click', Function (e) {if(e.target.tagName === 'A') {e.preventDefault() Alert (' I am ${e.target.innertext} ')}})Copy the code

Behavior-policy pattern

Define a set of algorithms, encapsulate them one by one, and make them interchangeable.

Finally, this is my favorite pattern, which is very practical and often encountered in writing business logic. At the same time, we will remain open and closed to reform, open to expansion and closed to revision

Demand description of the classic case inquiry Strategy given by Xiuyan Daishen: When the price type is “pre-sale price”, the price is 100-20, less than 100, 10% off

When the price type is “big price promotion”, the price is 100-30, less than 100 20% off

When the price type is “return price”, the value is between 200 and 50

When the price type is “early taste price”, it is 50% off

He started by labeling four prices:

Pre-sale price - Pre promotion price - onSale Return price - back test price - freshCopy the code

Use if-else, easy to do

// Method of inquiry, Function askPrice(tag, OriginPrice) {if(tag === 'pre') {if(originPrice >= 100) {return originprice-20} return originPrice * 0.9 If (tag === 'onSale') {if(originPrice >= 100) {return originPrice * 0.8 If (tag === 'back') {if(originPrice >= 200) {return originPrice -50} return originPrice} // Handle the original price if(tag === 'back') 'fresh') {return originPrice * 0.5}}Copy the code

Disadvantages: 1. It violates the “single function” principle. In one function, it handles four chunks of logic. 2. It violates the “open and closed” principle. What if we add a “rookie price” of 100-50? He can only continue if-else, because the inquiry function changed, need to test and regression test.

The workaround: Use object mapping while following the single-function and open-closed principles

Const priceProcessor = {pre(originPrice) {if (originPrice >= 100) {return originPrice -20; } return originPrice * 0.9; }, onSale(originPrice) { if (originPrice >= 100) { return originPrice - 30; } return originPrice * 0.8; }, back(originPrice) { if (originPrice >= 200) { return originPrice - 50; } return originPrice; }, fresh(originPrice) {return originPrice * 0.5; }}; Function askPrice(tag, originPrice) {return priceProcessor[tag](originPrice)}Copy the code

If add a condition new person is full 100 return 50

priceProcessor.newUser = function (originPrice) {
  if (originPrice >= 100) {
    return originPrice - 50;
  }
  return originPrice;
}
Copy the code

Behavior-state patterns

The state pattern is similar to the policy pattern, but the state pattern emphasizes the concept of classes

Class CoffeeMaker {constructor() {this.state = "init"; this.stock = { coffee: 500, milk: 200, }; } changeState(state) { this.state = state; this.changeStateProcessor[state](); } changeStateProcessor = {that: this, coffee() {console.log(" The coffee machine now has coffee :", this.that.stock.coffee); Console. log(" I only spit black coffee "); }, latte() { this.coffee(); Console. log(" The coffee machine now has a stock of coffee :", this.that.stock. Milk); Console. log(" Add milk "); }, vanillaLatte() { this.latte(); Console. log(" Add vanilla syrup "); }, mocha() { this.latte(); Console. log(" add chocolate "); }}; } const coffeeMaker = new coffeeMaker (); coffeeMaker.changeState("latte");Copy the code

Behavior-observer pattern

The observer pattern defines a one-to-many dependency that allows multiple observer objects to listen to a target object at the same time. When the state of the target object changes, all observer objects are notified so that they can update automatically.

Class Publisher {constructor() {this.observers = []; // Observers are observers. } addObserver(observer) { this.observers.push(observer); } removeObserver(observer) { this.observers.forEach((item, index) => { if (item === observer) this.observers.splice(index, 1); }); } notifyObersers() { this.observers.forEach((item) => { item.update(this); }); }} Class Observer {update(publisher) {// Update operations}} // Class DragonPublisher extends Publisher {constructor() {super(); this.state = null; this.observers = []; } getState() { return this.state; } setState(state) { this.state = state; this.notifyObersers(); } } class PlayerObserver extends Observer { constructor(id) { super(); this.id = id; this.state = null; } update(publisher) { this.state = publisher.getState(); this.remind(); } remind() {console.log(' receive message ${this.state}, '); } // Test const Dragon = new DragonPublisher(); const player1 = new PlayerObserver("#01"); const player2 = new PlayerObserver("#02"); Dragon.addObserver(player1); Dragon.addObserver(player2); Dragon.setState(" Tai Long has been killed ");Copy the code

Behavioral publish-subscribe pattern

Observer mode: Publishers contact subscribers directly; Publish-subscribe: Publishers and subscribers interact indirectly through an intermediate platform, with publishers unaware of subscribers

class EventEmitter { constructor() { this.handlers = {}; } // Register event listener on(event, callback) {if (! this.handlers[event]) this.handlers[event] = []; this.handlers[event].push(callback); } / / removing an event callback queue in the specified callback function off (the event callback) {const callbacks = this. Handlers [event]. index = callbacks.indexOf(callback); if (index ! == -1) { callbacks.splice(index, 1); }} // Emit the target event emit(event,... params) { if (this.handlers[event]) { this.handlers[event].forEach((callback) => { callback(... params); }); }} // register a single listener for the event once(event, callback) {// Wrap the callback so that it is automatically removed const wrapper = (... params) => { callback(... params); this.off(event, wrapper); }; this.on(event, wrapper); }}Copy the code

Reference document: Gold Digging volume – “JavaScript design pattern core principles and Application practice” – remarks