Writing code is easy, writing elegant code is hard, writing code that is easy to maintain, easy to extend, and clear structure should be the goal of every developer, and learning design patterns, rational use can take us one step closer to this goal. I recently read the book Javascript Design Patterns and Development Practices, which is a great book in a word. Here I summarize the main design patterns that we can use in Javascript introduced in the book. The ideas of design patterns are worth pondering over and over again, and should be used reasonably in the future business implementation

The singleton pattern

The singleton pattern guarantees that a class has only one instance and provides a global access point to it

Js implemented

  function getSingle(fn){
    let result

    return function (){
        return result || (result=fn.apply(this,arguments))
    }

  }Copy the code

The strategy pattern

Multiple approaches to a problem, each individually packaged and interchangeable

A program based on policy pattern consists of at least two parts: a set of policy classes, which encapsulate specific algorithms and are responsible for specific computation, and an environment class, which accepts customer requests and then delegates the requests to a policy class

One use scenario for the policy pattern: form validation, which encapsulates different validation rules into a set of policies and avoids multiple conditional statements

A classic saying: in languages where functions are first-class objects, the policy pattern is implicit, and a policy is a variable whose value is a function

Example:

const S = (salary)=>{
        return salary * 4
    }
    const A = (salary)=>{
        return salary * 3
    }
    const B = (salary)=>{
        return salary * 2
    }

    const calculate = (fun,salary)=>{
        return fun(salary)
    }
    calculate(S,1000)Copy the code

The proxy pattern

The proxy pattern provides a proxy or placeholder for an object to control access to it

Instead of interacting directly with the ontology, a layer of agents is added in the middle to handle operations that are not required by the ontology

var myImage=function(){ var imgNode=document.createElement('img') document.body.appendChild(imgNode) return { setImg(src){ imgNode.src=src } } } var proxyImg=function(){ var img =new Image() img.onload=function(){ Myimage.setsrc (this.src)} return {setImg(SRC){myimage.setsrc (' load.png ') img. SRC = SRC}}}Copy the code

Meaning of agency

A manifestation of the single responsibility principle, which states that a function or class should only do one thing, how does a function have so many responsibilities that they are coupled together, and when one part needs to be changed, it is likely to affect other parts of the function

Observer and publish subscribe patterns

The observer and publish and subscribe patterns allow the two parts of the application to communicate through notification rather than being tightly coupled

  • Observer model

An object maintains a series of objects that depend on it and actively notifies those dependent objects when their state changes

Note that the object directly manages the dependency list, which is the main difference between the observer and publish/subscribe modes

class Subject{
        constructor(){
            this.observers=[]
        }
        add(observer){
            this.observers.push(observer)
        }
        notify(data){
            for(let observer of this.observers){
                observer.update(data)
            }
        }
    }

    class Observer{
        update(){

        }
    }Copy the code

The disadvantage of the observer mode is that the object must maintain a list of observers by itself, and when the object state is updated, the methods of other objects are directly called. Therefore, in use, we generally adopt a deformation mode, namely publish and subscribe mode

  • Publish and subscribe model

This pattern adds a layer of pipe between the topic and the observer, so that the topic and the observer do not interact directly. Publishers publish content to the pipe, and subscribers subscribe to the content in the pipe, in order to avoid the dependency between the subscriber and the publisher

class Pubsub{ constuctor(){ this.pubsub={} this.subId=-1 } publish(topic,data){ if(! this.pubsub[topic]) return const subs=this.pubsub[topic] const len=subs.length while(len--){ subs[len].update(topic,data) } } /** * topic {string} * update {function} */ subscribe(topic,update){ ! this.pubsub[topic] && (this.pubsub[topic]=[]) this.subId++ this.pubsub[topic].push({ token:this.subId, update }) } unsubscribe(token){ for(let topic in this.pubsub){ if(this.pubsub.hasOwnProperty(topic)){ const current=this.pubsub[topic] for(let i=0,j=current.length; i<j; i++){ if(current[i].token==token){ current.splice(i,1) return token } } } } return this } }Copy the code

Publish-subscribe is a common design pattern used in framework design, from custom events in AngularJS to Rxjs to redux for state management

Command mode

A command in command mode is an instruction to do something specific

The command pattern is most commonly used when you need to send a request to some object without knowing who the recipient of the request is or what action is being requested. The hope is to design the program in a loosely coupled manner so that the sender and receiver of the request are decouple from each other

The origin of the command pattern is an object-oriented substitute for the callback function

In short, the command pattern wraps an implementation in a function that defines an execute method to invoke the implementation, and the requester only needs to communicate with the command function

The flyweight pattern

The share element pattern, as the name suggests, shares units to optimize code that is repetitive, slow, and inefficient at sharing data

Application: one is used for data layer, processing the shared data of a large number of similar objects stored in memory, and the other is used for DOM layer, event proxy

In the meta pattern, there is a concept of two states – internal and external

Internal state is stored within objects and can be shared by objects, independent of the specific scenario, and usually does not change

The external state varies according to the scenario

Objects stripped of external state become shared objects, and external state is passed into the shared object as necessary to form a complete object

There are several steps to using the share mode:

Use the example of file uploading in the book

  1. Stripping external state

class Upload{ constructor(type){ this.uploadType=type } delFile(id){ uploadManager.setExternalState(id,this) If (this.filesize <3000){return} // popup ask confirm delete? }}Copy the code

2. Instantiate objects using factories

var UploadFactory=(function(){ const flyWeightObjs={} return { create(uploadType){ if(flyWeightObjs[uploadType]){ return  flyWeightObjs[uploadType] } return flyWeightObjs[uploadType]=new Upload(uoloadType) } } })()Copy the code

3. Use the manager to encapsulate external state

var uploadManager=(function(){ var uploadDatabase={} return { add(id,uploadType,fileSize,fileName){ var FlyWeightObj = uploadFactory. create(uploadType) flyWeightObj= uploadFactory. create(uploadType) // Delete dom.onclick=function(){flyweightobj.delfile (id) // This share will be combined in Step 1. } uploadDatabase[id]={fileName, fileSize, dom} return flyWeightObj},  setExternalState(id,flyWeight){ var externalState=uploadDatabase[id] Object.assign(flyWeight,externalState) } } })()Copy the code

Chain of Responsibility model

To pass a request to multiple functions in this way, the current function processes the request if it meets the requirements of the current function, otherwise, the next function

Very good and powerful

Chain of responsibility is a good way to avoid a lot of if,else if,else

if (Function.prototype.chainAfter) { throw new Error('the chainAfter method already exist') } else { Function.prototype.chainAfter = function (fn) { return (... args) => { const ret = this.apply(this, [...args, () => { return fn && fn.apply(this, args) }]) if (ret === 'NEXT') { return fn && fn.apply(this, args) } return ret } } } /** * example * class Test{ * * test(... args){ * alert('test') * return 'NEXT' * } * * test1(... args){ * * setTimeout(()=>{ * alert('test1') * args.pop()() * }) * } * * test2(... args){ * alert('test2') * } * * $onInit(){ * const chain = this.test.bind(this) * .chainAfter(this.test1.bind(this)) * ChainAfter (enclosing test2. Bind (this)) * chain}} (1, 2, 3) * * * * /Copy the code


Decorator pattern

To add functionality to an existing function or object without changing its functionality

Decorate functions with AOP

if (Function.prototype.before) { throw new Error('the before method already exist') } else { Function.prototype.before =  function (beforefn) { return () => { if (beforefn.apply(this, arguments)) { this.apply(this, arguments) } } } } if (Function.prototype.after) { throw new Error('the after method already exist') } else { Function.prototype.after = function (afterfn) { return () => { this.apply(this, arguments) afterfn.apply(this, arguments) } } }Copy the code


The state pattern

Allows an object to change its behavior when its internal state changes, and the object appears to modify its class

Main point: Encapsulating state as a separate function and delegating requests to the current state object causes different behavior changes when the internal state of the object changes

An example of electric light:

A button controls the light switch, pressing one to turn it on, pressing another to turn it off

Initial implementation:

const S = (salary)=>{
        return salary * 4
    }
    const A = (salary)=>{
        return salary * 3
    }
    const B = (salary)=>{
        return salary * 2
    }

    const calculate = (fun,salary)=>{
        return fun(salary)
    }
    calculate(S,1000)Copy the code

The downside of this code is that it is not easily extensible, and when you want to add a flashing state, you have to modify the code in btnPressed

Use state mode overwrite

const S = (salary)=>{
        return salary * 4
    }
    const A = (salary)=>{
        return salary * 3
    }
    const B = (salary)=>{
        return salary * 2
    }

    const calculate = (fun,salary)=>{
        return fun(salary)
    }
    calculate(S,1000)Copy the code