Writing in the front

Many things in life do more skilled later can often bear some routines, using routines can make things easier to deal with. A design pattern is a routine for writing code, like a swordsman’s sword score, which is a template for code.

Even those of you who have never seen design patterns must have been exposed to or inadvertently used some design patterns in your work. Because the principle of the code is the same, the implementation of a requirement may naturally follow the best pattern.

These patterns have been summarized by others, and can be used directly. When you read the source code of open source libraries, you will find that these libraries make extensive use of design patterns.

This series of content mainly comes from the understanding and summary of the book JavaScript Design Pattern by Zhang Rongming (6 articles in total). I have implemented my own code in this book, and used some new syntax, abandoned some tedious content, and even corrected the “wrong” code in the book. So if I find that I understand wrong, code write wrong place trouble be sure to point out! Thank you very much!

Front knowledge

Master basic javaScript syntax, and have a deep understanding of JS principles (especially prototype chain).

Creative design patterns

1. Simple factory model

Also known as static factory methods, a factory object decides to create an instance of a product object class. Mainly used to create objects of the same class.

When creating a batch of objects with the same function and different base classes, only a unified factory method is provided to create objects, rather than declaring many classes to create them separately. The advantage is to minimize the creation of global variables, which is conducive to team development.

Method 1: Instantiate different classes
// Basketball base class
function BasketBall() {
  this.intro = 'Basketball is popular in America'
}
BasketBall.prototype = {
  getMember() {
    console.log('Five players per team')}}// Soccer base class
function FootBall() {
  this.intro = 'Football is popular all over the world'
}
FootBall.prototype = {
  getMember() {
    console.log('Each team needs 11 players')}}// Tennis base class
function Tennis() {
  this.intro = 'Many tennis series every year'
}
Tennis.prototype = {
  getMember() {
    console.log('1 player per team')}}// Sports factory
function SportFactory(type) {
  switch(type) {
    case 'basketball':
      return new BasketBall()

    case 'football':
      return new FootBall()

    case 'tennis':
      return new Tennis()
  }
}
Copy the code
/ / use
const football = SportFactory('football')
console.log(football.intro) // Football is very popular around the world
football.getMember() // Each team needs 11 players

// Instance prototype method sharing of the same base class
const football2 = SportFactory('football')
console.log(football.getMember === football2.getMember) // true
Copy the code
Method 2: Create an object and extend properties and methods on the object (you can extract common parts of the abstract)
function createSport(type, intro, menubar) {
  const o = new Object(a)// Public
  o.intro = intro
  o.getMember = function() {
    console.log('Every team needs it' + menubar + 'the players')}// The difference part
  if(type === 'basketball') {
    // do sometion
    o.nba = true
  }
  if(type === 'football') {
    // do sometion
    o.wordCup = true
  }
  if(type === 'tennis') {
    // do sometion
    o.frenchOpen = true
  }

  return o
}
Copy the code
/ / use
const football = createSport('football'.'Football is popular all over the world'.11)
console.log(football.intro) // Football is very popular around the world
football.getMember() // Each team needs 11 players
console.log(football.wordCup) // true
console.log(football.nba) // undefined

// Each created object is a new individual. Methods are not shared
const football2 = createSport('football'.'Football is popular all over the world'.11)
console.log(football.getMember === football2.getMember) // false
Copy the code

The main difference between method one and method two is whether it is created through a base class or an entirely new object. Methods that inherit from a prototype created from a base class can be shared in approach one, but not in approach two.

Second, factory method mode

The creation business is primarily responsible for creating instances of multiple product classes by abstracting them.

Assuming that the simple factory method is adopted for development, the base class may be continuously added in the face of new requirements, and each new base class not only needs to write the base class, but also needs to modify the factory method itself, that is, a new class needs to modify two, and the factory method mode can solve this problem.

/ / the factory class
function SportFactory(type, content) {
  const instance = new this[type](content)
  return instance
}

// Set all base classes on the factory prototype
SportFactory.prototype = {
  BasketBall: function(content) {
    this.content = content
    
    // Other business logic
    console.log('Basketball is popular in America:' + content)
  },

  FootBall: function(content) {
    this.content = content
    
    // Other business logic
    console.log('China wins World Cup:' + content)
  },

  Tennis: function(content) {
    this.content = content
    
    // Other business logic
    console.log('Nadal is the top star:' + content)
  }
}
Copy the code
/ / use
const array = [
  {type: 'BasketBall'.content: 'You need five people on a basketball team.'},
  {type: 'FootBall'.content: 'You need 11 players in a football team'},
  {type: 'FootBall'.content: 'Indoor soccer requires five players for a team'},
  {type: 'Tennis'.content: 'Tennis a team of one'},]for(let item of array) {
 new SportFactory(item.type, item.content)
 // Basketball is popular in the United States: you need five players on a team
 // China won the World Cup: Football requires 11 players in a team
 // China won the World Cup: indoor soccer requires five players for a team
 // Nadal is a top star: one player on a tennis team
}
Copy the code

By defining factory methods in this way, you can add a base class in the factory prototype, and the factory functions can find the corresponding base class.

The sample code is a simple demonstration of the principle of the factory method pattern, which can be applied to base classes that have some commonality, but each has quite different logic. For example, you can create a button on a page, but there are many types of buttons, each with different functions.

Tips: Safe mode classes

In this chapter, the book describes a safe way to create classes and declare factory methods in this way, because constructors cannot not only create instances but also declare global variables when they are not called using the new keyword. This ensures that the desired purpose is achieved either by using the new keyword or by calling the constructor directly.

function Demo() {
  if(this instanceof Demo) {
    this.test = true
  } else {
    return new Demo
  }
}

const d1 = new Demo()
const d2 = Demo()

console.log(d1.test) // true
console.log(d2.test) // true
Copy the code

I don’t define classes in this way because I think a class that uses the new keyword and calls it directly is not conducive to team code collaboration and maintenance. Second, in ES6, you can use the class keyword directly to define classes to avoid this problem.

Not all functions can be used as constructors

All I knew was that the arrow function could not be used as a constructor, which is how I first defined the base class on the prototype of the factory method.

SportFactory.prototype = {
  BasketBall(content) {
    this.content = content
    
    // Other business logic
    console.log('Basketball is popular in America:' + content)
  }
  // ...
}
Copy the code

The results kept coming back with errors that, when checked, were due to the use of a concise representation of object properties. A concise representation of a property is supposed to be just shorthand, and nothing else is different, but a function that looks up to shorthand really cannot be used as a constructor.

Reference: www.stefanjudis.com/today-i-lea…

Abstract factory model

By factory abstraction of classes, their business is used to abstract clusters of product classes rather than being responsible for instances of a particular class of products.

Abstract class in js

Classes that are not used to create instances but only to inherit are called abstract classes

Abstract classes do not exist in JS. You can simulate abstract classes in the following ways.

function Car() {}

Car.prototype = {
  getPrice() {
    return new Error('Abstract methods cannot be called')},getSpeed() {
    return new Error('Abstract methods cannot be called')}}Copy the code

The Car class does not have any attributes and the methods on the stereotype cannot be used, but it is useful for inheritance because a class is defined and the necessary methods for that class are defined (subclasses are overridden when inherited).

One of the purposes of an abstract class is to define a product cluster and declare some necessary methods that throw an error if the subclass does not override it.

Abstract factory method

Abstract classes are simply classes of classes. To define an abstract class is to define an interface in TS, and then to let the subclass inherit the abstract class is to use the interface to constrain the subclass.

Cars, for example, can be divided into many categories by brand.

/ / BMW
function BMW(price, speed) {
  this.price = price
  this.speed = speed
}

/ / the audi
function Audi(price, speed) {
  this.price = price
  this.speed = speed
}

/ /... , etc.
Copy the code

If you want to define a general Car class (abstract class) for all branded classes, constrain these subclasses to have a common Type and two methods.

function Car() {
  this.type = 'car'
}

Car.prototype = {
  getPrice() {
    return new Error('Abstract methods cannot be called')},getSpeed() {
    return new Error('Abstract methods cannot be called')}}Copy the code

This is where the abstract factory method comes in. Its role is to make subclasses like BMW and Audi inherit Car.

Start by defining the abstract factory method and the abstract class Car.

// subType is the subclass to inherit from its parent (BMW, Audi)
// superTypeName is the parent class identifier ('Car')
function abstructFactory(subType, superTypeName) {
  // Create a transition class
  function F() {}
  
  // Assign the prototype of the transition class to an instance of the parent class
  F.prototype = new abstructFactory[superTypeName]()
  
  // Assign the prototype of the subclass to an instance of the transition class
  subType.prototype = new F()
  // Since the prototype of the transition class is an instance of the parent class, the instance of the transition class inherits the attributes and methods of the parent class
  // If the prototype of a subclass is an instance of a transition class, the instance of a subclass inherits the attributes and methods of its parent class
}

// Define the abstract class on the properties of the factory method
abstructFactory.Car = function() {
  this.type = 'car'
}
abstructFactory.Car.getPrice = function() {
  return new Error('Abstract methods cannot be called')
}
abstructFactory.Car.getSpeed = function() {
  return new Error('Abstract methods cannot be called')}Copy the code

You then define subclasses and use abstract factory methods to make them inherit from their parent class.

// Subclass BMW
function BMW(price, speed) {
  this.price = price
  this.speed = speed
}

// Subclass BMW inherits Car
abstructFactory(BMW, 'Car')

// Subclasses implement methods on their parent classes
BMW.prototype.getPrice = function() {
  console.log(this.price)
}
BMW.prototype.getSpeed = function() {
  console.log(this.speed)
}

/ / the result
const bmw = new BMW('30w'.'1.5 t')
console.log(bmw.type) // car
bmw.getPrice() // 30w
bmw.getSpeed() / / 1.5 t
console.log(bmw.__proto__.constructor) // F(){}
Copy the code

One problem you can see in the last line of console is that the constructor of the BMW instance prototype object points to the transition function F instead of its constructor BMW.

Add a line of code to the abstract factory method to fix:

function abstructFactory(subType, superTypeName) {
  // Create a transition class
  function F() {}
  
  // Assign the prototype of the transition class to an instance of the parent class
  F.prototype = new abstructFactory[superTypeName]()
  // Fix the orientation of the subclass prototype object constructor
  F.prototype.constructor = subType
  
  // Assign the prototype of the subclass to an instance of the transition class
  subType.prototype = new F()
}
Copy the code
// Print again, pointing correctly
console.log(bmw.__proto__.constructor) // BMW(price, speed){}
Copy the code

Abstract factories can be used to manage the same abstract class. For example, in addition to Car abstract class, we can also define Bus abstract class and Truck abstract class. Each abstract class constraints the different methods that different subclasses need to implement.

// Car
abstructFactory.Car = function() {
  this.type = 'car'
}
abstructFactory.Car.getPrice = function() {
  return new Error('Abstract methods cannot be called')
}
abstructFactory.Car.getSpeed = function() {
  return new Error('Abstract methods cannot be called')}// Bus
abstructFactory.Bus = function() {
  this.type = 'bus'
}
abstructFactory.Bus.getPrice = function() {
  return new Error('Abstract methods cannot be called')
}
abstructFactory.Bus.getBusRoute = function() {
  return new Error('Abstract methods cannot be called')}// Truck
abstructFactory.Truck = function() {
  this.type = 'truck'
}
abstructFactory.Truck.getPrice = function() {
  return new Error('Abstract methods cannot be called')
}
abstructFactory.Truck.getTrainload = function() {
  return new Error('Abstract methods cannot be called')}Copy the code

The abstract factory pattern is the most abstract of the design patterns. The result of the abstract factory pattern is not a real object instance, but a class cluster, which specifies the structure of the class.

Tips: Code differences

The abstract factory method above differs from the book by one line:

function abstructFactory(subType, superTypeName) {
  function F() {}

  F.prototype = new abstructFactory[superTypeName]()
  subType.constructor = subType  // The original code in the book
  // f.prototype. constructor = subType

  subType.prototype = new F()
}
Copy the code

Constructor = subType; constructor = subType;

Segmentfault.com/q/101000000…

The constructor of the class seems to be confused with the constructor of the prototyped object of the class.

Blog.csdn.net/qq_41146956…

The author itself is trying to fix the constructor pointing problem for the subclass prototype object. It should read:

function abstructFactory(subType, superTypeName) {
  function F() {}

  F.prototype = new abstructFactory[superTypeName]()

  // Assign the prototype of the subclass first
  subType.prototype = new F()
  
  // Modify the constructor of the subclass prototype
  subType.prototype.constructor = subType
}
Copy the code

However, this approach means that not only one line of code is wrong in the original text, but also the order of the code, because if you correct the constructor direction of the prototype object first, the assignment becomes meaningless.

I don’t think it’s likely that there will be two simple errors in a row at the same time, and I suspect that the author intended to use parasitic inheritance without changing the code order and implementing the constructor direction for modifying the subclass prototype object, so he ended up defining the abstract factory method as described above.

4. Builder mode

By separating the building layer of a complex object from its presentation layer, the same building process can have different representations.

A complex object (composite object) may have some independent parts, and these independent parts are combined to form a complex entity. The Builder pattern is to maintain these independent parts independently and then assemble them.

For example, to process information about candidates, create a candidate:

  • You should first create a person: person, with basic information like the name:person.fullName
  • In addition, some private information should be handled separately:person.privacy.xxx
  • In addition, job information is also a relatively independent part:person.work.xxx

Ultimately, all this information comes together to form a complete candidate.

// Basic "people" class
function Man(name) {
  this.fullName = name

  if(name.split(' ').length > 0) {
    this.firstName = name.split(' ') [0]
    this.secondName = name.split(' ') [1]}}// Private information class
function Privacy(age, sex) {
  this.age = age || 'secret'
  this.sex = sex  || 'unknown'
}
Privacy.prototype = {
  getAge() {
    console.log(this.age)
  },
  getSex() {
    console.log(this.sex)
  }
}

/ / position
function Work(workType) {
  switch(workType) {
    case 'code':
      this.job = 'Programmer'
      this.jobDesc = 'Programmers lose their hair'
      break
    case 'ui':
      this.job = 'Designer'
      this.jobDesc = 'UI has a lot of little sisters.'
      break
    case 'product':
      this.job = 'Product Manager'
      this.jobDesc = 'The product is annoying'
      break
    default:
      this.job = 'Not available at present'
      this.jobDesc = 'No job description at present'}}// Candidate builder
function Person(name, age, sex, workType) {
  // Create a candidate
  const _person = new Man(name)

  // Add privacy information
  _person.privacy = new Privacy(age, sex)

 // Add job information
  _person.work = new Work(workType)

 // Return the completed candidate
  return _person
}
Copy the code
/ / use
const p = new Person('King Dog egg'.' '.'male'.'code')

console.log(p.fullName) / / the dog egg king
p.privacy.getAge() / / a secret
p.privacy.getSex() / / male
console.log(p.work.jobDesc) // The programmer loses his hair
Copy the code

The Builder pattern typically modularizes the classes that create objects so that each module of the created class can be used flexibly and reused with high quality. However, this approach also increases the complexity of the structure for the overall object splitting.

5. Prototype mode

The prototype instance points to the class that created the object, and the class used to create the new object shares the properties and methods of the prototype object.

To put it simply, instances of a class will share the characteristics of the class’s prototype objects (including all the prototype objects searched up by the prototype chain), share methods and attributes, and then rewrite the parts that need to be rewritten in individual subclasses to avoid unnecessary consumption.

The toString() method does not need to be initialized when the Object is created, no matter how many layers it inherits from the parent class. The toString() method is called on object.Prototype, unless overridden somewhere in the middle.

Implementation requirements

Assume that the need to create a caroute map on the page, caroute map has common attributes and methods, but also varies according to different types, such as different styles, switching methods, etc., can be defined as follows:

// Basic multicast map class
function LoopImgs(imgArray, container) {
  this.imgArray = imgArray // Rotate the image array
  this.container = container // Multicast container

  // Create a caroute map
  this.createImg = function() {
    console.log('Basic create wheel cast chart')}// Switch to the next image
  this.changeImg = function() {
    console.log('Base sliding left/right toggle wheel casting chart')}}// Slide up and down to toggle the class
function SlideLoopImgs(imgArray, container) {
  // Inherits the base multicast graph class
  LoopImgs.call(this, imgArray, container)
  
  // Overwrite the toggle image method
  this.changeImg = function() {
    console.log('Slide up and down to toggle wheel cast chart')}}// Click the arrow to toggle the class
function ArrowLoopImgs(imgArray, container, arrow) {
  // Inherits the base multicast graph class
  LoopImgs.call(this, imgArray, container)

  // Attributes unique to the subclass
  this.arrow = arrow

  // Overwrite the toggle image method
  this.changeImg = function() {
    console.log('Click the arrow to toggle the wheel cast image')}}Copy the code
/ / use
const slideLoop = new SlideLoopImgs(['img1.png'.'img2.png'].'.container1')
const arrowLoop = new ArrowLoopImgs(['img1.png'.'img2.png'].'.container2'['left-arrow.png'.'right-arrow.png'])

slideLoop.createImg() // Create a rotation map
slideLoop.changeImg() // Toggle the toggle graph

arrowLoop.createImg() // Create a rotation map
arrowLoop.changeImg() // Click the arrow to toggle the wheel map
Copy the code
Optimize with prototype patterns

The above code does, but you can see that every time you create an instance of a subclass, you do some repetitive work in the constructor of the parent class. If there is some complicated logic in the constructor of the parent class, it can cause a lot of unnecessary consumption.

So you can do this repetitive work on the parent class’s prototype object, and then assign the child class’s prototype object to an instance of the parent class.

// Basic multicast map class
function LoopImgs(imgArray, container) {
  this.imgArray = imgArray // Rotate the image array
  this.container = container // Multicast container
}
LoopImgs.prototype = {
  // Create a caroute map
  createImg() {
    console.log('Basic create wheel cast chart')},// Switch to the next image
  changeImg() {
    console.log('Base sliding left/right toggle wheel casting chart')}}// Slide up and down to toggle the class
function SlideLoopImgs(imgArray, container) {
  // Inherits the multicast graph class
  LoopImgs.call(this, imgArray, container)
}
SlideLoopImgs.prototype = new LoopImgs()
// Overwrite the toggle image method
SlideLoopImgs.prototype.changeImg = function() {
  console.log('Up and down toggle wheel cast chart')}// Click the arrow to toggle the class
function ArrowLoopImgs(imgArray, container, arrow) {
  // Inherits the multicast graph class
  LoopImgs.call(this, imgArray, container)

  // Attributes unique to the subclass
  this.arrow = arrow
}
ArrowLoopImgs.prototype = new LoopImgs()
// Overwrite the toggle image method
ArrowLoopImgs.prototype.changeImg = function() {
  console.log('Click the arrow to toggle the wheel cast image')}Copy the code

This way the methods on the prototype are shared, and because the prototype object is shared, both the parent and subclass of the prototype object can be extended.

// Use and extend
const slideLoop = new SlideLoopImgs(['img1.png'.'img2.png'].'.container1')
const arrowLoop = new ArrowLoopImgs(['img1.png'.'img2.png'].'.container2'['left-arrow.png'.'right-arrow.png'])

slideLoop.createImg() // Create a rotation map
slideLoop.changeImg() // Toggle the toggle graph

arrowLoop.createImg() // Create a rotation map
arrowLoop.changeImg() // Click the arrow to toggle the wheel map

// Extend the parent prototype
LoopImgs.prototype.getImgsLength = function() {
  console.log(this.imgArray.length)
}

// Extend the subclass prototype
SlideLoopImgs.prototype.getImgContainer = function() {
  console.log(this.container)
}

slideLoop.getImgsLength() / / 2
slideLoop.getImgContainer() // .container1
Copy the code

Prototype pattern is the soul of javaScript language. Many object-oriented programming ideas or design patterns in JS are realized based on prototype pattern inheritance.

Application of the idea of archetypal inheritance

The core idea of the stereotype pattern is to assign values to the prototype objects of a class, not only by superclass and subclass methods.

Sometimes business complexity can be achieved by creating multiple objects. In this case, it is best not to use the new keyword to assign base classes, but to define a prototype pattern object copy method.

function prototypeExtend(. args) {
  // Create a transition class
  function F() {}
  
  // Merge template objects into prototype objects (shallow copy, deep copy if necessary)
  F.prototype = Object.assign({}, ... args)// Return the transition class instance
  return new F()
}
Copy the code

For example, to create a penguin object in penguin game, if there is no penguin base class, only provide some action object templates, and want a separate prototype object, you can use the object copy method of prototype mode to achieve the inheritance of these object templates, and create instances:

/ / use
const penguin = prototypeExtend(
  {
    speed: 20.run() {
      console.log('Running speed:' + this.speed)
    }
  },
  {
    swim() {
      console.log('Swimming speed:' + this.speed)
    }
  },
  {
    jumo() {
      console.log('jump')
    }
  }
)

penguin.run() // Running speed: 20
penguin.swim() // Swim speed: 20
penguin.jumo() / / jump
Copy the code

Singleton mode

Also known as singleton schema, is an object class that is allowed to be instantiated only once. Sometimes we use an object to plan a namespace, managing properties and methods on the object in a tidy way.

Namespace (namespace)

Having variables or methods exposed globally, when multiple people collaborate, or when maintaining code can cause naming duplication, you can use namespaces to constrain variables defined by each person. Just like with jquery, all methods are accessed through the jquery namespace without affecting other code.

// The namespace of xiaowang
const Wang = {
  fn1() {
    console.log('wang: fn1')},fn2() {
    this.fn1()
  }
}

// Xiao Li's namespace
const Li = {
  fn1() {
    console.log('li: fn1')},fn2() {
    this.fn1()
  }
}

Wang.fn2() // wang: fn1
Li.fn2() // li: fn1
Copy the code
Module and clear

In addition to defining the namespace, the singleton mode can also manage the modules in the namespace, which makes the structure of the code base clearer and easier to use. For example, customize a small code base:

const Wang = {
  Tools: {
    tool_fn1() {},
    tool_fn2(){},},Message: {
    success() {},
    warning() {},
    danger(){}},Ajax: {
    get() {},
    post(){}}}Copy the code
Static variables that cannot be changed

The singleton pattern is also suitable for creating a variable that can only be accessed, not modified, and used once created.

const Conf = (function() {
  const conf = {
    MAX_NUM: 100.MIN_NUM: 10
  }

  return {
    getMax() {
      return conf.MAX_NUM
    },

    getMin() {
      return conf.MIN_NUM
    }
  }
})()

console.log(Conf.getMax()) / / 100
console.log(Conf.getMin()) / / 10
Copy the code
Inert singleton

For example, if the requirement is to create a person, it can only be created once, and then the call will not create a new person.

const LazySingle = (function() {
  let _instance = null

  function Single(name, age) {
    this.name = name
    this.age = age
  }

  return function(name, age) {
    if(! _instance) { _instance =new Single(name, age)
    }

    return _instance
  }
})()
Copy the code
/ / test
const p1 = LazySingle('King Dog egg'.18)
console.log(p1.name) / / the dog egg king
console.log(p1.age) / / 18

const p2 = LazySingle('Li Fugui'.68)
console.log(p2.name) / / the dog egg king
console.log(p2.age) / / 18

console.log(p1 === p2) // true
Copy the code