This is the second blog I wrote, and I felt a little regretful before I started writing. It was just as big as the topic I chose in my second blog, so it took a lot of time and effort to choose tens of thousands of words. Finally, I persevered. I look forward to your support. If you have any questions, please discuss them together. Your support is my biggest motivation.

I. Introduction of design mode

What are design patterns

  • Design patterns are an idea of problem solving, independent of language. In object-oriented software design engineering, a simple and elegant solution to a specific problem. Generally speaking, design pattern is a solution to a certain problem in a certain scenario. Through design pattern, the code can be increased in reusability, extensibility and maintainability, and ultimately make our code highly cohesive and low coupling.

Five design principles of Design Patterns

  • Single responsibility: A program only needs to do one thing well. If the functionality is too complex, break it down to keep each part separate
  • Open closed principle: open to expansion, closed to modification. When adding requirements, extend new code, not modify the source code. This is the ultimate goal of software design.
  • Lee’s substitution principle: a subclass can override its parent, and a subclass can appear where its parent can appear.
  • Interface independence principle: Keep interfaces single and independent to avoid fat interfaces. This applies to TS today.
  • Principle of dependency: Interface oriented programming relies on abstraction rather than concrete. The user only focuses on the interface, not the implementation of the concrete class. Commonly known as “Duck type”

There are three broad categories of design patterns

  • Creatives: Factory, Abstract Factory, Builder, singleton, Prototype
  • Structure: Adapter mode, decorator mode, agent mode, appearance mode, bridge mode, combination mode, share mode
  • Behavior: Policy pattern, template method pattern, publish/subscribe pattern, iterator pattern, chain of responsibility pattern, command pattern, memo pattern, state pattern, visitor pattern, mediator pattern, interpreter pattern.

Second, design mode

1. Factory mode

  • The factory pattern is a common design pattern used to create objects. It can be called a factory if the logic is encapsulated rather than exposed. The factory pattern, also known as the static factory pattern, allows a factory object to create an instance of a class.

advantages

  1. The caller only needs to know the name of the object when creating it
  2. High scalability, if you want to add a product, directly extend a factory class.
  3. Hide the implementation of the product and only care about the interface of the product.

disadvantages

  • Every time you add a product, you need to add a concrete class, which invisibly increases the stress on system memory and complexity, as well as the dependency on concrete classes

example

  • A garment factory can produce different types of clothing, and we use a factory method class to determine the output

class DownJacket {
  production(){
    console.log('Making down jackets')}}class Underwear{
  production(){
    console.log('Making underwear')}}class TShirt{
  production(){
    console.log('Produce t-shirts')}}/ / the factory class
class clothingFactory {
  constructor(){
    this.downJacket = DownJacket
    this.underwear = Underwear
    this.t_shirt = TShirt
  }
  getFactory(clothingType){
    const _production = new this[clothingType]
    return _production.production()
  }
}
const clothing = new clothingFactory()
console.log(clothing.getFactory('t_shirt'))// Make t-shirts
Copy the code

2. Abstract factory pattern

  • The abstract factory pattern uses class abstraction to make the business applicable to the creation of a product class cluster rather than an instance of a product class. Abstract factories can be regarded as the upgraded version of ordinary factories, which are mainly production instances, while abstract factories are production factories.

advantages

  • When multiple objects in a product family are designed to work together, it ensures that clients always use only objects in the same product family.

disadvantages

  • Product family extension is very difficult, and to add a product to a family requires both code in the abstract Creator and code in the concrete one.

example

  • Also based on the above example, an abstract class is simulated, and the method implementation of the inherited subclass is constrained. Finally, the factory function returns the specified class cluster
/* Abstract is a reserved word in abstract class JS. Implementation of abstract classes can only be verified by new.target, preventing the abstract class from being directly instantiated
class ProductionFlow {
  constructor(){
    if(new.target === ProductionFlow){
      throw new Error('Abstract classes cannot be instantiated')}}production(){
    throw new Error('Production to be overwritten')}materials(){
    throw new Error('Materials to be rewritten')}}class DownJacket extends ProductionFlow{
  production(){
    console.log(` materials:The ${this.materials()}, the production of down jackets)}materials(){
    return 'duck feather'}}class Underwear extends ProductionFlow{
  production(){
    console.log(` materials:The ${this.materials()}, production of underwear)}materials(){
    return 'Mercerized cotton'}}class TShirt extends ProductionFlow{
  production(){
    console.log(` materials:The ${this.materials()}, producing t-shirts)}materials(){
    return 'cotton'}}function getAbstractProductionFactory(clothingType){
  const clothingObj = {
    downJacket:DownJacket,
    underwear:Underwear,
    t_shirt:TShirt,
  }
  if(clothingObj[clothingType]){
    return clothingObj[clothingType]
  }
  throw new Error('The factory does not support production of this for the time being${clothingType}Type of clothing ')}const downJacketClass = getAbstractProductionFactory('downJacket')
const underwearClass = getAbstractProductionFactory('underwear')

const downJacket = new downJacketClass()
const underwear = new underwearClass()
console.log(downJacket.production()) // Material: duck hair, down jacket
console.log(underwear.production()) // Material: mercerized cotton, underwear production
Copy the code

3. Builder mode

  • The Builder pattern is a more complex and less frequently used creative design pattern. The Builder pattern returns not a simple product for the client, but a complex product composed of multiple parts. Primarily used to separate the construction of a complex object from its representation, so that the same construction process can create different representations.

advantages

  1. The builder is independent and easy to expand
  2. Easy to control risk details

disadvantages

  1. Products must have something in common and be limited in scope
  2. When internal changes are complex, there are many builder classes

example

/ / abstract classes
class Clothing {
  constructor() {
    this.clothingType = ' '
    this.price
  }
}

class Underwear extends Clothing {
  constructor() {
    super(a)this.clothingType = 'underwear'
    this.price = 10}}class TShirt extends Clothing {
  constructor() {
    super(a)this.clothingType = 't_shirt'
    this.price = 50}}class DownCoat extends Clothing {
  constructor() {
    super(a)this.clothingType = 'DownCoat'
    this.price = 500}}/ / product
class Purchase {
  constructor() {
    this.clothings = []
  }
  addClothing(clothing) {
    this.clothings.push(clothing)
  }
  countPrice() {
    return this.clothings.reduce((prev, cur) = >cur.price + prev,0)}}/ / the factory director
class FactoryManager {
  createUnderwear() {
    throw new Error(The subclass must override createUnderwear)}createTShirt() {
    throw new Error(Subclasses must override createTShirt)}createDownCoat() {
    throw new Error(Subclasses must override DownCoat)}}/ / workers
class Worker extends FactoryManager {
  constructor() {
    super(a)this.purchase = new Purchase()
  }
  createUnderwear(num) {
    for (let i = 0; i < num; i++) {
      this.purchase.addClothing(new Underwear())
    }
  }
  createTShirt(num) {
    for (let i = 0; i < num; i++) {
      this.purchase.addClothing(new TShirt())
    }
  }
  createDownCoat(num) {
    for (let i = 0; i < num; i++) {
      this.purchase.addClothing(new DownCoat())
    }
  }
}

/ / sales
class Salesman {
  constructor() {
    this.worker = null
  }
  setWorker(worker) {
    this.worker = worker
  }
  reserve(clothing) {
    clothing.forEach((item) = > {
      if (item.type === 'underwear') {
        this.worker.createUnderwear(item.num)
      } else if (item.type === 't_shirt') {
        this.worker.createTShirt(item.num)
      } else if (item.type === 'DownCoat') {
        this.worker.createDownCoat(item.num)
      } else {
        try {
          throw new Error('The company does not produce or does not currently exist this type of commodity')}catch (error) {
          console.log(error)
        }
      }
    });

    const purchase = this.worker.purchase
    return purchase.countPrice()
  }
}

const salesman = new Salesman()
const worker = new Worker()
salesman.setWorker(worker)
const order = [
  {
    type: 'underwear'.num: 10
  },
  {
    type: 't_shirt'.num: 4
  },
  {
    type: 'DownCoat'.num: 1}]console.log('Amount required for this order:${salesman.reserve(order)}`)
Copy the code

4. Singleton mode

  • The idea of the singleton pattern is to ensure that a class can be instantiated only once. Each time, if the class has already been instantiated, the instance is returned directly. Otherwise, an instance is created and saved and returned.
  • The core of the singleton pattern is to create a unique object, and creating a unique object in javascript is so simple that creating a class to retrieve an object is a bit redundant. Such asconst obj = {}.objA singleton is a unique object that can be accessed from anywhere under a global scope declaration, which satisfies the singleton condition.

advantages

  1. There is only one instance in memory, reducing memory overhead.
  2. It avoids multiple occupation of resources.

disadvantages

  • In violation of the single responsibility, a class should only care about internal logic, not external implementation

example

  • The login popover that we see all the time, it either shows or hides, you can’t have two popovers at the same time
class LoginFrame {
    constructor(state){
        this.state = state
    }
    show(){
        if(this.state === 'show') {console.log('Login box is displayed')
            return
        }
        this.state = 'show'
        console.log('Login displayed successfully')}hide(){
        if(this.state === 'hide') {console.log('Login box is hidden')
            return
        }
        this.state = 'hide'
        console.log('Login box hidden successfully')}// Use a static method to get whether an instance exists on instance. If you do not create one and return it, return the existing instance
    static getInstance(start){
        if(!this.instance){
            this.instance = new LoginFrame(state)
        }
        return this.instance
    }
}
const p1 = LoginFrame.getInstance('show')
const p2 = LoginFrame.getInstance('hide')
console.log(p1 === p2) // true
Copy the code

5. Adapter mode

  • The purpose of the adapter pattern is to solve the problem of incompatible interfaces between objects by making two incompatible objects work when called without changing the source code.

advantages

  • Allows any two unrelated classes to run efficiently at the same time, and improves reusability, transparency, and flexibility

disadvantages

  • Too much use of the adapter mode will make the system become cluttered and difficult to control as a whole. It is recommended to use adapters when refactoring is not possible.

example

  • Take a real example, Jack can only speak English, Xiao Ming can only speak Chinese, they have obstacles in communication, Xiao Hong can speak Both Chinese and English, xiao Hong translates Jack’s English into Chinese, so that Xiao Ming and Jack can communicate barrier-free, here Xiao Hong plays the role of adapter.
class Jack {
  english() {
    return 'I speak English'}}class Xiaoming {
  chinese() {
    return 'I only speak Chinese'}}/ / adapter
class XiaoHong {
  constructor(person) {
    this.person = person
  }
  chinese() {
    return `The ${this.person.english()}"I can speak English"}}class Communication {
  speak(language) {
    console.log(language.chinese())
  }
}

const xiaoming = new Xiaoming()
const xiaoHong = new XiaoHong(new Jack())
const communication = new Communication()
communication.speak(xiaoming)
communication.speak(xiaoHong)
Copy the code

6. Decorator mode

  • Decorator pattern allows you to add responsibilities to the source code without changing it itself. It’s lighter than inheriting decorators. Generally speaking, we put film, hangar and stickers on our beloved mobile phones, which are the decoration of mobile phones.

advantages

  • Decorator class and decorator class can develop independently of each other without coupling. Decorator pattern is an alternative to inheritance, which can dynamically extend the functionality of an implementation class.

disadvantages

  • Multiple layers of decoration can add complexity

example

  • In the preparation of aircraft war game, aircraft object attack only ordinary bullets attack, how to change the original code under the circumstances, for it other attacks, such as laser weapons, missile weapons?
class Aircraft {
    ordinary(){
        console.log('Fire ordinary bullets')}}class AircraftDecorator {
    constructor(aircraft){
        this.aircraft = aircraft
    }
    laser(){
        console.log('Fire the laser')}guidedMissile(){
        console.log('Launch a missile')},ordinary(){
        this.aircraft.ordinary()
    }
}
const aircraft = new Aircraft()
const aircraftDecorator = new AircraftDecorator(aircraft)
console.log(aircraftDecorator.ordinary) // Fire normal bullets
console.log(aircraftDecorator.laser) // Fire the laser
console.log(aircraftDecorator.guidedMissile) // Launch the missile
// You can see that it has been decoratively extended without changing the source code
Copy the code

7. Proxy mode

  • The key to the proxy pattern is to provide a proxy object to control access to an object when it is inconvenient or not necessary for the client to directly access it. After the proxy object does some processing of the request, it passes the request to the ontology object.
  • The proxy and ontology interfaces require consistency, and the relationship between the proxy and ontology is a duck relationship, regardless of how it is implemented, as long as the methods exposed between them are consistent.

advantages

  • Clear responsibility, high scalability, intelligent

disadvantages

  • Adding proxies between objects can affect the speed of processing.
  • Implementing agents requires additional work, and some can be quite complex.

example

  • Leadership, as we all know, with the company’s highest authority, assuming that the company has 100 employees, if everyone is to find the leadership to handle affairs, the leadership will collapse, so leaders hired a secretary to help him collect transaction, the secretary will at the appropriate time will need to deal with processing business to the boss, the secretary is to lead an agent here.
/ / employee
class Staff {
  constructor(affairType){
    this.affairType = affairType
  }
  applyFor(target){
    target.receiveApplyFor(this.affairType)
  }
}
/ / secretary
class Secretary {
  constructor(){
    this.leader = new Leader()
  }
  receiveApplyFor(affair){
    this.leader.receiveapplyfor (affair)}} leaderclass Leader {
  receiveApplyFor(affair){
    console.log(` approval:${affair}`)}}const staff = new Staff('Promotion and raise')
staff.applyFor(new Secretary()) // Approval: promotion and salary increase

Copy the code

8. Appearance mode

  • The facade pattern is essentially an encapsulation of interaction, consisting of a high-level interface that integrates a set of interfaces from a subsystem to provide a consistent facade that reduces direct interactions between the outside world and multiple subsystems, thereby facilitating the use of subsystems.

advantages

  • Reduce system interdependence, as well as security and flexibility

disadvantages

  • In violation of the open closed principle, changes are cumbersome when there are changes, even inheritance refactoring is not feasible.

example

  • Appearance mode is often used to handle compatibility processing for some interfaces of advanced and low-level browsers
function addEvent(el,type,fn){
    if(el.addEventlistener){// Advanced viewer adds event DOM API
        el.addEventlistener(type,fn,false)}else if(el.attachEvent){// Add event API for lower version explorer
        el.attachEvent(`on${type}`,fn)
    }else {/ / other
        el[type] = fn
    }
}
Copy the code
  • Another scenario is to use function overloading to make parameter passing more flexible when a parameter in a function may or may not be passed.
function bindEvent(el,type,selector,fn){
    if(! fn){ fn = selector }// Other code
    console.log(el,type,fn)
}
bindEvent(document.body,'click'.'#root'.() = >{})
bindEvent(document.body,'click'.() = >{})

Copy the code

9. Publish and subscribe

  • Publish subscription, also known as the observer pattern, defines a 1-to-N dependency between objects, and when one of them changes, all dependent objects are notified.
  • The publish-and-subscribe model is often used in our work scenarios, such as when you bind an event to the DOM, by subscribing to click events on the DOM that publish messages to subscribers when clicked.

advantages

  • The observer and the observed are abstractly coupled. And set up the trigger mechanism.

disadvantages

  • Notifying all subscribers at the same time can cause performance problems when there are a large number of subscribers.
  • A circular reference execution between the subscriber and the subscription target can cause a crash.
  • The publish-subscribe model has no way of providing the subscriber with information about how the target to which it is subscribed has changed, only that it has changed.

example

  • This is a metaphor for the Recent Winter Olympics. We can book before the event starts. When the event is about to start, the APP will send us the notification of the upcoming event in advance, and we will not be notified if the event is not due. Let’s write an example based on this requirement
class Subject {
  constructor(){
    this.observers = {}
    this.key = ' '
  }
  add(observer){
    const key = observer.project
    if (!this.observers[key]) {
      this.observers[key] = []
    }
    this.observers[key].push(observer)
  }
  remove(observer){
    const _observers = this.observers[observer.project]
    console.log(_observers,11)
    if(_observers.length){
      _observers.forEach((item,index) = >{
        if(item === observer){
          _observers.splice(index,1)}})}}setObserver(subject){
    this.key = subject
    this.notifyAllObservers()
  }
  notifyAllObservers(){
    this.observers[this.key].forEach((item,index) = >{
      item.update()
    })
  }
}

class Observer {
  constructor(project,name) {
    this.project = project
    this.name = name
  }
  update() {
    console.log(Dear:The ${this.name}The item you booked: [The ${this.project}【 Begin at once)}}const subject = new Subject()
const xiaoming = new Observer('skiing'.'xiaoming')
const A = new Observer('Big jump'.'A')
const B = new Observer('Big jump'.'B')
const C = new Observer('Big jump'.'C')
subject.add(xiaoming)
subject.add(A)
subject.add(B)
subject.add(C)
subject.remove(B) // Unsubscribe
subject.setObserver('Big jump')
/** Execution result * Dear :A Your scheduled event: [big platform] will start soon */ dear :C Your scheduled event: [big platform] will start soon */
Copy the code

10. Iterator pattern

  • The iterator pattern provides a way to access each element of an aggregate object sequentially without exposing the object’s interior.
  • Iterators are divided into internal iterators and external iterators, which have their own application scenarios.

advantages

  • It supports traversing an aggregate object in different ways.
  • Iterators simplify aggregate classes. You can have multiple traversals on the same aggregate.
  • In the iterator pattern, it is easy to add new aggregate classes and iterator classes without modifying the existing code.

disadvantages

  • Since the iterator pattern separates the responsibility of storing data and traversing data, adding new aggregation classes needs to correspond to adding new iterator classes, and the number of classes increases in pairs, which increases the complexity of the system to some extent.

example

  1. Inner iterator
// An inner iterator means that the iteration rules have been defined internally. It accepts the entire iteration process completely, with only one initial call from the outside.
Array.prototype.MyEach = function(fn){
    for(let i = 0; i<this.length; i++){ fn(this[i],i,this)}} [1.2.3.4].MyEach((item,index) = >{
    console.log(item,index)
})
Copy the code
  1. External iterator
// The external iterator must display the iteration of the next element. It increases the complexity of the call, but it also increases the flexibility of the iterator to manually control the iteration process.
class Iterator{
    constructor(arr){
        this.current = 0
        this.length = arr.length
        this.arr = arr
    }
    next(){
        return getCurrItem
    }
    isDone(){
        return this.current>=this.length
    }
    getCurrItem(){
        return {
            done:this.isDone(),
            value:this.arr[this.current++]
        }
    }
}
let iterator =new Iterator([1.2.3])
while(! (item=iterator.next()).done) {console.log(item) 
}
iterator.next()
{done: false, value: 1} {done: false, value: 2} {done: false, value: 3} {done: true, value: 3} {done: true, value: 1} undefined} */
Copy the code

11. State mode

  • Allow an object to change its behavior when its internal state changes. The object appears to modify its class. A more mundane way of doing this would be to record a set of states, with an implementation for each state, and run the implementation according to that state.

advantages

  • Put all the behavior associated with a state into a single class, and you can easily add new states by simply changing the object’s state to change its behavior.
  • Allows state transition logic to be integrated with state objects, rather than some giant block of conditional statements.
  • You can reduce the number of objects in the system by having multiple environment objects share a state object.

disadvantages

  • The use of state patterns inevitably increases the number of system classes and objects.
  • The structure and implementation of state pattern are complicated, and it will lead to confusion of program structure and code if used improperly.
  • State modes do not support the “on/off principle” very well. Adding a new state class to a toggled state mode requires modifying the source code responsible for the transition to the new state, and modifying the behavior of a state class requires modifying the source code of the corresponding class.

example

  • Raven’s Q in LOL has three attacks. The same button has different attack behaviors in different states. Normally, we use if… Else can also be implemented, but this is obviously not extensible and violates the open and closed principle. This scenario is described in code.
class State {
  constructor(attack){
    this.attack = attack
  }
  handle(context){
    console.log(this.attack)
    context.setState(this)}}class Context {
  constructor(){
    this.state = null
  }
  getState(){
    return this.state
  }
  setState(state){
    this.state = state
  }
}
const q1 = new State('Q1 Strike 1'),
      q2 = new State('Q2 Strike 2'),
      q3 = new State('Q3 Strike 3'),
      context = new Context()
      
q1.handle(context)1 / / q1
q2.handle(context)/ / q2. 2
q3.handle(context)/ / q3. 3
Copy the code

12. Strategic mode

  • The strategy pattern refers to defining a set of algorithms and encapsulating them one by one in order to separate the use of the algorithm from its implementation. It can also be used to encapsulate a set of rules, such as common form validation rules, as long as they point to a consistent goal and can be used interchangeably.

advantages

  • The algorithm can be switched freely, avoiding the use of multi-level conditional judgment and increasing scalability

disadvantages

  • The number of policy classes increases, and all policy classes need to be exposed.

example

  • When I first started in the industry, writing form validation was often an endless if… Else, I realized that this was not going to work, so I put the validation rule in an object, controlled it in a function, separated the rule from the implementation, and only needed to change the configuration in the encapsulated rule each time. This approach is used in a variety of later scenarios to resolve the frequent use of if… Else question, when you first encounter inverted policy mode you know that this is also policy mode.
const rules = {
    cover_img: {
        must: false.msg: 'Please upload cover image'.val: ' '
    },
    name: {
        must: true.msg: 'Name cannot be empty'.val: ' '
    },
    sex: {
        must: true.msg: 'Please specify gender'.val: ' '
    },
    birthday: {
        must: false.msg: 'Please choose a birthday'.val: ' '}},function verify(){
    for(const key in rules){
        if(rules[key].must&&! rules[key].val){console.log(rules[key].msg)
        }
    }
}
Copy the code
  • The above example is written in JS. In a language where javascript uses functions as first-class citizens, the policy pattern is invisible. It is integrated into the javascript language, so the strategy pattern in javascript is straightforward. However, we still need to understand the traditional strategy pattern. Let’s look at an example of the traditional strategy pattern.
//html-----------------
<form action="http:// xxx.com/register" id="registerForm" method="post"> Please enter the user name: <input type="text" name="userName"/> Please enter the password: <input type="text" name="password"/> Please enter mobile phone number: <input type="text" name="phoneNumber" />
    <button>submit</button>
</form>
// js------------------
class Strategies {
  constructor() {
    this.rules = {}
  }
  add(key, rule) {
    this.rules[key] = rule
    return this}}class Validator {
  constructor(strategies) {
    this.cache = [] // Save the validation rule
    this.strategies = strategies
  }
  add(dom, rules) {
    rules.forEach((rule) = > {
      const strategyAry = rule.strategy.split(':')
      this.cache.push(() = > {
        const strategy = strategyAry.shift()
        strategyAry.unshift(dom.value)
        strategyAry.push(rule.errorMsg)
        console.log(this.strategies[strategy])
        return this.strategies[strategy].apply(dom, strategyAry)
      })
    });
  }
  start() {
    for (let i = 0,validatorFunc; validatorFunc =this.cache[i++]; ) {
      const msg = validatorFunc()
      if (msg) {
        return msg
      }
    }
  }
}
const registerForm = document.getElementById('registerForm') // Get the formDom node
const strategies = new Strategies()
strategies.add('isNonEmpty'.function(value, errorMsg) {
  if(! value) {return errorMsg
  }
}).add('minLength'.function(value, length, errorMsg) {
  if (value.length < length) {
    return errorMsg
  }
}).add('isMobile'.function(value, errorMsg) {
  if (!/ (a ^ 1 [3 | | 5 8] [0-9] {9} $) /.test(value)) {
    return errorMsg
  }
})
function validataFunc() {
  const validator = new Validator(strategies.rules)
  // Multiple verification rules
  validator.add(registerForm.userName, [
    {
      strategy: 'isNonEmpty'.errorMsg: 'User name cannot be empty'
    }, {
      strategy: 'minLength:10'.errorMsg: 'Username must be at least 10 characters long'
    }
  ])
  validator.add(registerForm.password, [{
    strategy: 'minLength:6'.errorMsg: 'Password length must not be less than 6 characters'
  }])
  validator.add(registerForm.phoneNumber, [{
    strategy: 'isMobile'.errorMsg: 'Wrong phone number format'
  }])

  const errorMsg = validator.start()
  return errorMsg // Return an error message.
}
registerForm.onsubmit = function () {
  const errorMsg = validataFunc()
  if (errorMsg) { // If there is an error message, display it and block the onSubmit default event
    console.log(errorMsg)
    return false}}Copy the code

13. Command mode

  • A command in command mode refers to an instruction that does something specific.
  • The command mode is most commonly used when you need to send a request to some objects without knowing who the recipient of the request is or what the requested operation is. At this point, the program can be designed in a loosely coupled manner so that the request sender and the request receiver decouple from each other.

advantages

  • Reduce the coupling degree of code, easy to expand, the emergence of new commands can be easily added

disadvantages

  • Overuse of command mode can result in too many specific commands in your code.

example

  • Suppose you develop a page in a project, and one programmer is responsible for drawing static pages that include certain buttons, while another programmer is responsible for developing the specific behavior of those buttons. The programmer in charge of the static page does not know what will happen to the button in the future. Without knowing what the specific behavior is, the coupling between the button and the object responsible for the specific behavior can be uncoupled with the help of the command mode.
// html-------------------
<button id="button1"> Click the button1</button>
<button id="button2">Click button 2</button>
<button id="button3">Click button 3</button>
// js---------------------
const button1 = document.getElementById('button1'),
  button2 = document.getElementById('button2'),
  button3 = document.getElementById('button3');
  const MenBar = {
    refresh:function(){
      console.log('Refresh menu directory')}}const SubMenu = {
    add:function(){
      console.log('Add submenu')},del:function(){
      console.log('Delete submenu')}}function setCommand(el,command){
    el.onclick = function(){
      command.execute()
    }
  }
  
  class MenuBarCommand{
    constructor(receiver,key){
      this.receiver = receiver
      this.key = key
    }
    execute(){
      this.receiver[this.key]()
    }
  }
  setCommand(button1,new MenuBarCommand(MenBar,'refresh'))
  setCommand(button2,new MenuBarCommand(SubMenu,'add'))
  setCommand(button3,new MenuBarCommand(SubMenu,'del'))
Copy the code

14. Combination mode

  • A composite pattern is a larger object constructed from small children, which themselves may be composed of multiple children.
  • The composition pattern combines objects into a tree structure to represent a partial-whole hierarchy. In addition to being used to represent tree structures, another benefit of the composite pattern is that it enables consistent use of individual and composite objects through the polymorphic representation of objects.

advantages

  • High-level modules are easy to call and nodes can be added freely

disadvantages

  • Its leaf object and child object declarations are implementation classes, not interfaces, which violates the dependency inversion principle

example

  • In our most common folder and file relationship, it is very suitable to describe the composite pattern, folder can include subfolders and files, file cannot contain any files, may eventually be combined into a tree. Let’s add a file, scan the files in the file, and delete the file.
// Folder class
class Folder {
  constructor(name) {
    this.name = name
    this.parent = null;
    this.files = []
  }
  // Add a file
  add(file) {
    file.parent = this
    this.files.push(file)
    return this
  }
  // Scan files
  scan() {
    console.log('Start scanning folders:The ${this.name}`)
    this.files.forEach(file= > {
      file.scan()
    });
  }
  // Delete the specified file
  remove() {
    if (!this.parent) {
      return
    }
    for (let files = this.parent.files, i = files.length - 1; i >= 0; i--) {
      const file = files[i]
      if (file === this) {
        files.splice(i, 1)
        break}}}}/ / file
class File {
  constructor(name) {
    this.name = name
    this.parent = null
  }
  add() {
    throw new Error('No file can be added under file')}scan() {
    console.log('Start scanning files:The ${this.name}`)}remove() {
    if (!this.parent) {
      return
    }
    for (let files = this.parent.files, i = files.length - 1; i >= 0; i++) {
      const file = files[i]
      if (file === this) {
        files.splice(i, 1)}}}}const book = new Folder('E-book')
const js = new Folder('js')
const node = new Folder('node')
const vue = new Folder('vue')
const js_file1 = new File('javascript Advanced Programming ')
const js_file2 = new File('javascript Ninja Secrets')
const node_file1 = new File('Nodejs in a nutshell')
const vue_file1 = new File('Vue in a nutshell')

const designMode = new File('javascript Design Patterns in Action ') js.add(js_file1).add(js_file2) node.add(node_file1) vue.add(vue_file1) book.add(js).add(node).add(vue).add(designMode)  book.remove() book.scan()Copy the code

15. Module method pattern

  • Module method pattern is a design pattern based on inheritance, there is no real sense of inheritance in javascript, all inheritance from the prototype inheritance, with the arrival of ES6 class, the realization of the concept of inheritance, so that we can inherit in a very convenient and concise way. But it’s essentially archetypal inheritance.
  • The template method pattern consists of two parts, the first part is the abstract parent class, the second part is the concrete implementation child class. The abstract superclass mainly encapsulates the algorithm framework of the subclass and implements some common methods and other methods’ execution order. The subclass inherits the algorithm framework of the parent class and overwrites it.

advantages

  • Provide common code for easy maintenance. Behavior is controlled by the parent class and implemented by the child class.

disadvantages

  • Each of its concrete implementations needs to inherit subclasses to implement, which undoubtedly leads to an increase in the number of classes, making the system huge.

example

  • In the case of coffee and tea, both coffee and tea are made by boiling water, boiling water is a common method, and then how to brew it, what to pour into the cup, and what to add to the ingredients they may vary, and by encapsulating them for special treatment constraints the method that subclasses have to implement.
// Abstract superclass
class Beverage {
  boilWater(){
    console.log('Boil the water')}brew(){
    throw new Error('Word class must override brew methods')}pourInCup(){
    throw new Error('Word class must override pourInCup method')}addCondiments(){
    throw new Error('Word classes must override the addCondiments method')}init(){
    this.boilWater()
    this.brew()
    this.pourInCup()
    this.addCondiments()
  }
}
/ / coffee
class Coffee extends Beverage {
  brew(){
    console.log('Brew coffee in boiling water')}pourInCup(){
    console.log('Pour the coffee into the cup')}addCondiments(){
    console.log('With sugar and milk')}}/ / tea
class Tea extends Beverage {
  brew(){
    console.log('Infuse tea with boiling water')}pourInCup(){
    console.log('Pour the tea into the cup')}addCondiments(){
    console.log('Add lemon')}}const coffee = new Coffee()
coffee.init()
const tea = new Tea()
tea.init()
Copy the code

16. Enjoy yuan mode

  • Sharing mode is a mode for performance optimization, the core is to use sharing technology to support a large number of fine-grained objects. If a large number of similar objects are created in the system, the memory consumption is too high. You can use the sharing mode to process and reuse similar objects to reduce the memory consumption and achieve performance optimization.
  • The key to the share pattern is how to distinguish between internal state and external state
    • Internal state: The state that can be shared by objects and usually does not change is called internal state
    • External state: Depends on the scenario, varies according to the scenario, and cannot be shared

advantages

  • Reduces the creation of large number of objects and reduces system memory.

disadvantages

  • The complexity of the system is increased, and the external state needs to be separated from the internal state, and the external state has the nature of inherent, should not change with the change of the internal state, otherwise it will cause the chaos of the system.

example

let id = 0
// Define the internal state
class Upload {
  constructor(uploadType) {
    this.uploadType = uploadType
  }
  If the value is smaller than 3000, you can directly delete it. If the value is larger than 3000, you can prompt the confirm popup to delete it.
  delFile(id) {
    uploadManager.setExternalState(id,this)
    if(this.fileSize < 3000) {return this.dom.parentNode.removeChild(this.dom)
    }
    if(window.confirm('Are you sure you want to delete this file?The ${this.fileName}`)) {return this.dom.parentNode.removeChild(this.dom)
    }
  }
}
// External state
class uploadManager {
  static uploadDatabase = {}
  static add(id, uploadType, fileName, fileSize) {
    const filWeightObj = UploadFactory.create(uploadType)
    const dom = this.createDom(fileName, fileSize, () = > {
      filWeightObj.delFile(id)
    })
    this.uploadDatabase[id] = {
      fileName,
      fileSize,
      dom
    }
  }
  // Create the DOM and remove the event for the button binding.
  static createDom(fileName, fileSize, fn) {
    const dom = document.createElement('div')
    dom.innerHTML = '<span> file name:${fileName}, file size:${fileSize}</span> <button class="delFile"> Delete </button> '
    dom.querySelector('.delFile').onclick = fn
    document.body.append(dom)
    return dom
  }
  static setExternalState(id, flyWeightObj) {
    const uploadData = this.uploadDatabase[id]
    for (const key in uploadData) {
      if (Object.hasOwnProperty.call(uploadData, key)) {
        flyWeightObj[key] = uploadData[key]
      }
    }
  }

}
// Define a factory to create an Upload object and return it if its internal state instance object exists, or create, save and return it otherwise.
class UploadFactory {
  static createFlyWeightObjs = {}
  static create(uploadType) {
    if (this.createFlyWeightObjs[uploadType]) {
      return this.createFlyWeightObjs[uploadType]
    }
    return this.createFlyWeightObjs[uploadType] = new Upload(uploadType)
  }
}
// Start loading
const startUpload = (uploadType, files){
    for (let i = 0, file; file = files[i++];) {
      uploadManager.add(++id, uploadType, file.fileName, file.fileSize)
    }
}

startUpload('plugin'[{fileName: '1.txt'.fileSize: 1000},
  {fileName: '2.html'.fileSize: 3000},
  {fileName: '3.txt'.fileSize: 5000}]); startUpload('flash'[{fileName: '4.txt'.fileSize: 1000},
  {fileName: '5.html'.fileSize: 3000},
  {fileName: '6.txt'.fileSize: 5000}]);Copy the code

17. Chain of responsibility model

  • The chain of responsibility pattern is defined as avoiding coupling between the sender and receiver of the request by using multiple objects that have the opportunity to process the request, linking the objects into a chain and passing the request along the chain until one object handles it.

advantages

  • Reduce coupling, which decouples the sender and receiver of the request.
  • Simplifies objects so that they do not need to know the chain structure.
  • Enhanced flexibility in assigning responsibilities to objects. Responsibilities can be dynamically added or removed by changing members in the chain or reordering them.

disadvantages

  • There is no guarantee that every request will be received.
  • System performance will suffer, and it will be inconvenient to debug the code, which may cause circular calls.
  • Runtime characteristics may not be easily observed, hindering debugging.

example

  • Suppose we are in charge of an e-commerce website selling mobile phones. After paying 500 yuan and 200 yuan deposit for two rounds of booking, we will receive 100 yuan and 50 yuan coupons respectively, while those without deposit will be regarded as ordinary purchase without coupons, and they cannot be guaranteed to be purchased under the condition of limited inventory.
class Order500 {
    constructor(){
        this.orderType = 1
    }
    handle(orderType, pay, stock){
        if(orderType === this.orderType&&pay){
            console.log('500 yuan deposit reservation, get 100 yuan coupon ')}else {
            return 'nextSuccessor'}}}class Order200 {
    constructor(){
        this.orderType = 2
    }
    handle(orderType, pay, stock){
        if(orderType === this.orderType&&pay){
            console.log('$200 deposit, get $50 coupon')}else {
            return 'nextSuccessor'}}}class OrderNormal {
    constructor(){
        this.stock = 0
    }
    handle(orderType, pay, stock){
        if (stock > this.stock) {
            console.log('Regular purchase, no coupon')}else {
            console.log('Lack of mobile phone stock')}}}class Chain {
  constructor(order){
    this.order = order
    this.successor = null
  }
  setNextSuccessor(successor){
    return this.successor = successor
  }
  passRequest(. val){
    const ret = this.order.handle.apply(this.order,val)
    if(ret === 'nextSuccessor') {return this.successor&&this.successor.passRequest.apply(this.successor,val)
    }
    return ret
  }
}
console.log(new Order500())
var chainOrder500 = new Chain( new Order500() );
var chainOrder200 = new Chain( new Order200() );
var chainOrderNormal = new Chain( new OrderNormal() );

chainOrder500.setNextSuccessor( chainOrder200 );
chainOrder200.setNextSuccessor( chainOrderNormal );

chainOrder500.passRequest( 1.true.500 ); // Output: 500 yuan deposit pre-order, get 100 coupons
chainOrder500.passRequest( 2.true.500 ); // Output: 200 yuan deposit pre-order, get 50 coupons
chainOrder500.passRequest( 3.true.500 ); // Output: normal purchase, no coupon
chainOrder500.passRequest( 1.false.0 ); // Output: Not enough phones in stock

Copy the code

18. Mediation patterns

  • The role of the mediator pattern is to decouple objects from each other. When you add a mediator object, all related objects communicate through the mediator object rather than referring to each other, so when an object changes, you only need to go through the mediator object. The mediator loosens the coupling between objects and can independently change their interactions. The mediator pattern turns the netted many-to-many relationship into a relatively simple one-to-many relationship.

advantages

  • Reduces class complexity and converts one-to-many to one-to-one. Decoupling between classes.

disadvantages

  • When intermediaries become large and complex, they become difficult to maintain.

example

// html-----------Select color: <select name="" id="colorSelect">
    <option value="">Please select a</option>
    <option value="red">red</option>
    <option value="blue">blue</option>
  </select>
  <br />Select memory: <select name="" id="memorySelect">
    <option value="">Please select a</option>
    <option value="32G">32G</option>
    <option value="63G">64G</option>
  </select>
  <br />Enter the purchase quantity: <input type="text" id="numberInput" />
  <br />
  <div>You chose the color:<span id="colorInfo"></span></div>
  <div>You chose memory:<span id="memoryInfo"></span></div>
  <div>You chose the quantity:<span id="numberInfo"></span></div>
  <button id="nextBtn" disabled="true">Please select phone color and purchase quantity</button>
  // js -------------------
const goods = {
      "red|32G": 3."red|16G": 0."blue|32G": 1."blue|16G": 6
    },
      colorSelect = document.getElementById('colorSelect'),
      memorySelect = document.getElementById('memorySelect'),
      numberInput = document.getElementById('numberInput'),
      colorInfo = document.getElementById('colorInfo'),
      memoryInfo = document.getElementById('memoryInfo'),
      numberInfo = document.getElementById('numberInfo'),
      nextBtn = document.getElementById('nextBtn'),
      mediator = (function () {
        return {
          changed(obj) {
            const color = colorSelect.value,
              memory = memorySelect.value,
              number = numberInput.value,
              stock = goods[`${color}|${memory}`]

            if (obj === colorSelect) {
              colorInfo.innerHTML = color
            } else if (obj === memorySelect) {
              memoryInfo.innerHTML = memory
            } else if (obj === numberInput) {
              numberInfo.innerHTML = number
            }

            if(! color) { nextBtn.disabled =true
              nextBtn.innerHTML = 'Please select phone color'
              return
            }
            if(! memory) { nextBtn.disabled =true
              nextBtn.innerHTML = 'Please select memory size'
              return
            }
            if (Number.isInteger(number - 0) && number < 1) {
              nextBtn.disabled = true
              nextBtn.innerHTML = 'Please enter the correct purchase quantity'
              return
            }
            nextBtn.disabled = false
            nextBtn.innerHTML = 'Add to cart'
          }
        }
      })()

    colorSelect.onchange = function () {
      mediator.changed(this)
    }
    memorySelect.onchange = function () {
      mediator.changed(this)
    }
    numberInput.oninput = function () {
      mediator.changed(this)}Copy the code

19. Prototyping

  • The prototype pattern is where the prototype instances point to the class of objects to be created, and the prototype is copied to create a new object, in other words, to clone itself and generate a new object.

advantages

  • Instead of relying on constructors or classes to create objects, you can use this object as a template to generate more new objects.

disadvantages

  • For an attribute that contains a reference type value, all instances take the same attribute value by default.

example

const user = {
    name:'Ming'.age:'30'.getInfo(){
        console.log(` name:The ${this.name}Age:The ${this.age}`)}}const xiaozhang = Object.create(user)
xiaozhang.name = 'zhang'
xiaozhang.age = 18
xiaozhang.getInfo() // Name: Xiao Zhang, age: 18

user.getInfo() // Name: Xiao Ming, age: 30
Copy the code

20. Memo mode

  • The memo pattern captures the internal state of an object without breaking encapsulation and stores the state outside of the object to ensure that the object can be restored to its original state later.

advantages

  • It provides a mechanism for the user to restore the state, enabling the user to easily return to a historical state.
  • Information encapsulation is realized, so that users do not need to care about the details of state preservation.

disadvantages

  • If a class has too many member variables, it will take up a lot of resources, and each save will consume a certain amount of memory.

example

/ / pieces
class ChessPieces {
  constructor(){
    this.chess = {}
  }
  // Get the pieces
  getChess(){
    return this.chess
  }
}
// Record the moves
class Record {
  constructor(){
    this.chessTallyBook = [] // Record the moves
  }
  recordTallyBook(chess){
    // console.log(this.chessTallyBook.includes(chess))
    const isLoadtion = this.chessTallyBook.some(
      item= >item.location === chess.location
    )
    if(isLoadtion){
      console.log(`${chess.type}.${chess.location}Other pieces already exist)}else {
      this.chessTallyBook.push(chess)
    }
    // this.chessTallyBook.some(item=>item.location === chess.location)
  }
  getTallyBook(){
    return this.chessTallyBook.pop()
  }
}

// The rules of chess
class ChessRule {
  constructor(){
    this.chessInfo = {}
  }
  playChess(chess){
    this.chessInfo = chess
  }
  getChess(){
    return this.chessInfo
  }
  // Record the moves
  recordTallyBook(){
    return new ChessPieces(this.chessInfo)
  }
  / / back
  repentanceChess(chess){
    this.chessInfo = chess.getTallyBook()
  }
}

const chessRule = new ChessRule()
const record = new Record()

chessRule.playChess({
  type:'black'.location:'X10,Y10'
})
record.recordTallyBook(chessRule.getChess())// Record the moves
chessRule.playChess({
  type:'white'.location:'X11,Y10'
})
record.recordTallyBook(chessRule.getChess())// Record the moves
chessRule.playChess({
  type:'black'.location:'X11,Y11'
})
record.recordTallyBook(chessRule.getChess())// Record the moves
chessRule.playChess({
  type:'white'.location:'X12,Y10'
})
console.log(chessRule.getChess())/ / {type: 'white', the location: 'X12, Y10'}
chessRule.repentanceChess(record) / / back
console.log(chessRule.getChess())/ / {type: 'black', the location: 'X11, Y11'}
chessRule.repentanceChess(record) / / back
console.log(chessRule.getChess())/ / {type: 'white', the location: 'X11, Y10'}
Copy the code

21. Bridge mode

  • Bridge mode refers to the separation of the abstract part and its implementation part, so that they change independently, by using composition relations instead of inheritance relations, reduce the coupling degree of abstraction and implementation of the two variable dimensions.

advantages

  • Separation of abstraction and implementation. Excellent ability to expand. Implementation details are transparent to the customer.

disadvantages

  • The introduction of bridge pattern will increase the difficulty of system understanding and design, because the aggregation association relationship is established in the abstraction layer, requiring developers to design and program for abstraction.

example

  • For example, the phones we use, apple iphoneX and huawei mate40, brand and model are the common abstract parts, they can be extracted separately.
class Phone {
    constructor(brand,modle){
        this.brand = brand
        this.modle = modle
    }
    showPhone(){
        return 'Brand of mobile phone:The ${this.brand.getBrand()}The modelThe ${this.modle.getModle()}`}}class Brand {
    constructor(brandName){
        this.brandName = brandName
    }
    getBrand(){
        return this.brandName
    }
}
class Modle {
    constructor(modleName){
        this.modleName = modleName
    }
    getModle(){
        return this.modleName
    }
}
const phone = new Phone(new Brand('huawei'),new Modle('mate 40'))
console.log(phone.showPhone())
Copy the code

22. Visitor pattern

  • The visitor pattern separates the operation of the data from the structure of the data, encapsulating the operation of each element in the data into a separate class, allowing it to extend the new data without changing the data structure.

advantages

  • Consistent with the principle of single responsibility. Excellent scalability and flexibility.

disadvantages

  • Violates the dependency inversion principle by relying on concrete classes instead of abstractions.

example

    class Phone {
      accept() {
        throw new Error('Subclass ACCEPT must be overridden')}}class Mata40Pro extends Phone {
      accept() {
        const phoneVisitor = new PhoneVisitor()
        return phoneVisitor.visit(this)}}class IPhone13 extends Phone {
      accept() {
        const phoneVisitor = new PhoneVisitor()
        return phoneVisitor.visit(this)}}// Visitor class
    class PhoneVisitor {
      visit(phone) {
        if (phone.constructor === IPhone13) {
          return {
            os: 'ios'.chip: 'A15 Bionic Chip '.screen: 'Capacitance plate'}}else if (phone.constructor === Mata40Pro) {
          return {
            os: 'HarmonyOS'.chip: 'Kirin 9000'.GPUType: 'Mali-G78'.port: 'type-c'}}}}const mata40Pro = new Mata40Pro()
    console.log(mata40Pro.accept())
Copy the code

23. Interpreter mode

  • The interpreter pattern, which provides a way to evaluate the syntax or expressions of a language, is a behavioral pattern. This pattern implements an expression interface that interprets a particular context.

advantages

  • Good scalability and flexibility. Added new ways to interpret expressions.

disadvantages

  • There are few scenarios available and they are almost invisible in Web development. It is difficult to maintain in complex environments.
  • The interpreter pattern causes class bloat. It also uses recursive invocation methods, which can cause crashes if not well controlled.

example

class TerminalExpression {
  constructor(data) {
    this.data = data
  }
  interpret(context) {
    if (context.indexOf(this.data) > -1) {
      return true;
    }
    return false; }}class OrExpression {
  constructor(expr1, expr2) {
    this.expr1 = expr1;
    this.expr2 = expr2;
  }
  interpret(context) {
    return this.expr1.interpret(context) || this.expr2.interpret(context); }}class AndExpression {
  constructor(expr1, expr2) {
    this.expr1 = expr1;
    this.expr2 = expr2;
  }
  interpret(context) {
    return this.expr1.interpret(context) && this.expr2.interpret(context); }}class InterpreterPatternDemo {
  static getMaleExpression() {
    const robert = new TerminalExpression("Xiao Ming");
    const john = new TerminalExpression("Small dragon");
    return new OrExpression(robert, john);
  }

  static getMarriedWomanExpression() {
    const julie = new TerminalExpression("Zhang");
    const married = new TerminalExpression("Little red");
    return new AndExpression(julie, married);
  }

  static init(args) {
    const isMale = this.getMaleExpression();
    const isMarriedWoman = this.getMarriedWomanExpression();
    console.log('Xiao Long is male?${isMale.interpret("Small dragon")}`)
    console.log('Is Xiao Hong a married woman?${isMarriedWoman.interpret("Little Red Zhang SAN")}`)
  }
}
InterpreterPatternDemo.init()
Copy the code

conclusion

  • The above is a summary of my nearly one-month study, and one month is far from enough. After writing this article, I still lack understanding of the application scenarios of some design patterns. Design pattern is a knowledge point that needs to be studied in depth for a long time. It needs to be combined with the actual scene to practice imitation and keep thinking. In addition, due to the characteristics of JS, many design patterns in JS are incomplete and incomplete, forcing JS to imitate the traditional design pattern is a little weak. However, thanks to typescript, design patterns in TS can be as close to traditional design patterns as possible. I plan to write a TS design patterns blog.
  • This article is from:
    • Book JavaScript Design Patterns and Development Practices
    • “Javascript Design Pattern System Explanation and Application” by Teacher Shuang Yue
    • And all sorts of cases that Baidu search draws lessons from