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
-
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