What are the several design patterns commonly used in front-end development_Design pattern principle

Hello everyone, we meet again, I am your friend Quanzhangjun.

design Schema overview

A design pattern is a general solution to a certain type of problem that occurs repeatedly in the software design and development process. Design patterns are more about guiding ideology and methodology than ready-made code. Of course, each design pattern has a specific implementation in each language. Learning design patterns is more about understanding the inner thoughts and solutions of various patterns. After all, this is the best practice summed up by countless previous experiences, and code implementation is an aid to deepening understanding.

Design patterns can be divided into three categories:

  1. Structural Patterns: Simplify system design by identifying simple relationships between components in the system.
  2. Creational Patterns: Deal with the creation of objects, and create objects in an appropriate way according to the actual situation. Conventional object creation methods may cause design problems or increase design complexity. Creational patterns solve problems by controlling the creation of objects in some way.
  3. Behavioral Patterns: Used to identify common interaction patterns between objects and implement them, thus increasing the flexibility of these interactions.

There are a total of 23 design patterns in the above, but we as front end Developers, there are probably the following 10 types that need to be understood.

Design patterns that the front end needs to understand (10 types)

Creational patterns

As the name implies, these patterns are used to create instance objects.

1. Factory pattern

Let's start simple. The simple factory pattern is to determine which instance of a product class to create by a factory object.

Take the above picture as an example, we construct a simple car factory to produce cars:

// car constructor
function SuzukiCar(color) {
  this.color = color;
  this.brand = 'Suzuki';
}

// car constructor
function HondaCar(color) {
  this.color = color;
  this.brand = 'Honda';
}

// car constructor
function BMWCar(color) {
  this.color = color;
  this.brand = 'BMW';
}

// car brand enumeration
const BRANDS = {
  suzuki: 1,
  honda: 2,
  bmw: 3
}

/**
 * car factory
 */
function CarFactory() {
  this.create = (brand, color)=> {
    switch (brand) {
      case BRANDS.suzuki:
        return new SuzukiCar(color);
      case BRANDS.honda:
        return new HondaCar(color);
      case BRANDS.bmw:
        return new BMWCar(color);
      default:
        break;
    }
  }
}
copy

Take a look at our factory:

const carFactory = new CarFactory();
const cars = [];

cars.push(carFactory.create(BRANDS.suzuki, 'brown'));
cars.push(carFactory.create(BRANDS.honda, 'grey'));
cars.push(carFactory.create(BRANDS.bmw, 'red'));

function sayHello() {
  console.log(`Hello, I am a ${this.color} ${this.brand} car`);
}

for (const car of cars) {
  sayHello.call(car);
}
copy

Output result:

Hello, I am a brown Suzuki car
Hello, I am a grey Honda car
Hello, I am a red BMW car
copy

After using the factory pattern, it is no longer necessary to repeatedly introduce constructors one by one, and it is only necessary to introduce factory objects to create various objects conveniently.

2. Singleton mode

First of all we need to understand what is a singleton?

Single: Refers to one. Example: Refers to the created instance. Singleton: means that the same instance is always created. That is, instances created using a class are always the same.

First look at the following piece of code:

class Person{
  constructor(){}
}

let p1 = new Person();
let p2 = new Person();

console.log(p1===p2) //false
copy

The above code defines a Person class, through which two instances are created, we can see that the two instances are not equal in the end. In other words, the instance obtained through the same class is not the same (this is a matter of course), but if we want to always get the same instance, then this is the singleton pattern. So here's how to implement the singleton mode:

To implement the singleton mode, we need to pay attention to two points:

  1. Need to use return. When using new, if return is not manually set, then this will be returned by default. However, we want to make the instance returned each time the same, that is, we need to manually control the created object, so we need to use return here.
  2. We need to return the same object each time. That is to say, in the first instance, the instance needs to be saved. In the next instance, return the saved instance directly. Therefore, closures are needed here.
const Person = (function(){
  let instance = null;
  return class{
      constructor(){
        if(!instance){
         //Create an instance for the first time, then you need to save the instance
          instance = this;
        }else{
          return instance;
      }
  }
  }
})()
let p3 = new Person();
let p4 = new Person();
console.log(p3===p4)  //true
copy

From the above code, we can see that in the closure, the instance variable is used to save the created instance, and the instance created for the first time is returned every time. In this way, no matter how many times it is created, the same instance is created, which is the singleton mode.

3. Prototype pattern

In layman's terms, it is to create a shared prototype and create new objects by copying these prototypes.

In my opinion, the prototype mode is actually specifying the model of the newly created object. More generally, I want the prototype of the newly created object to be the object I specified.

The simplest implementation of the prototype pattern is through Object.create(). Object.create(), will use an existing object to provide the __proto__ of the newly created object. For example the following code:

let person = {
  name:'hello',
  age:24
}

let anotherPerson = Object.create(person);
console.log(anotherPerson.__proto__)  //{name: "hello", age: 24}

anotherPerson.name = 'world';  //properties can be modified
anotherPerson.job = 'teacher';
copy

In addition, if we want to implement the prototype mode ourselves, instead of using the encapsulated Object.create() function, we can use prototype inheritance to achieve it:

function F(){}

F.prototype.g = function(){}
 
//Class G inherits from Class F
 
function G(){
  F.call(this);
}
 
//prototypal inheritance
function Fn(){};
Fn.prototype = F.prototype;
G.prototype = new Fn();
 
G.prototype.constructor = G;
copy

The prototype pattern is to create an object of a specified prototype. If we need to create an object repeatedly, we can use the prototype mode to achieve it.

structural pattern

1. Decorator pattern

Decorator mode: Add new functionality to an object without changing its original structure and functionality.

The original adapter mode can no longer be used, and the interface needs to be repackaged. The decorator mode is still available, but something needs to be added to improve this function.

For example, the mobile phone case, the function of the mobile phone itself is not affected, and the mobile phone case is the decorator mode of the mobile phone.

class Circle {
    draw() {
        console.log('draw a circle');
    }
}

class Decorator {
    constructor(circle) {
        this.circle = circle;
    }
    draw() {
        this.circle.draw();
        this.setRedBorder(circle);
    }
    setRedBorder(circle) {
        console.log('set red border')
    }
}

// test
let circle = new Circle();

let client = new Decorator(circle);
client.draw();
copy

Output result:

draw a circle
 set red border
copy

Now it’s 2021, and es7 is also widely used. We write this in es7 (ES7 decorator):

1. Install yarn add babel-plugin-transform-decorators-legacy

2. Create a new .babelrc file and perform the following configuration

{
    "presets": ["es2015", "latest"],
    "plugins": ["transform-decorators-legacy"]
}
copy

3. Code

@testDec
class Demo {
    // ...
}

function testDec(target) {
    target.isDec = true
}

console.log(Demo.isDec)

//outputtrue
copy

true is printed out, indicating that the decorator @testDec has succeeded. The function is a decorator, and the Demo is decorated with @testDec. This target is actually class Demo, and then add an isDec to her.

After disassembly is the following content:

// Decorator principle
@decorator
class A {}

// Equivalent to
class A {}
A = decorator(A) || A;
copy

The form of the decorator parameter

@testDec(false)

class Demo {
}

function testDec(isDec) {
    return function (target) {
        target.isDec = isDec
    }
}

console.log(Demo.isDec);
copy

Verifying whether it is a true decorator pattern requires verifying the following points:

1.Separate the existing pair from the decorator, the two exist independently
2.in line with the open-closed principle
copy

2. Adapter mode

Adapter mode: The old interface format is not compatible with the user, and an adapter conversion interface is added in the middle.

For example, foreign sockets are different from domestic sockets, and we need to buy a converter to be compatible.

Above code:

class Adaptee {
    specificRequest() {
        return 'German standard plug';
    }
}

class Target {
    constructor() {
        this.adaptee = new Adaptee();
    }
    request() {
        let info = this.adaptee.specificRequest();
        return `${info} -> converter -> Chinese standard plug`
    }
}

// test
let client = new Target();
client.request();
copy

result:

German standard plug -> converter -> Chinese standard plug
copy

The old interface can be encapsulated in the scene:

// The ajax packaged by yourself is used as follows:
ajax({
    url: '/getData',
    type: 'Post',
    dataType: 'json',
    data: {
        id: '123'
    }
}).done(function(){

})
// But for historical reasons, the code is all:
// $.ajax({...})
copy

An adapter is needed at this time:

// make a layer of adapter
var $ = {
    ajax: function (options) {
        return ajax(options)
    }
}
copy

3. Proxy mode

Proxy mode: the user does not have the right to access the target object, a proxy is added in the middle, and authorization and control are done through the proxy.

Celebrity agent: For example, if there is a performance, if you want to invite a star, you must first contact the agent.

Or understand it as: provide a substitute or placeholder for an object in order to control access to it. For example, image lazy loading, intermediary, etc.

/**
 * pre:Proxy mode
 * Xiao Ming pursues A, and B is a good friend of A. Xiao Ming doesn't know when A is in a good mood, so he is embarrassed to hand the flowers to A directly.
 * So Xiao Ming gave the flower to B, and then B gave it to A.
 */

// class of flowers 
class Flower{
    constructor(name){
        this.name = name 
    }
}

// Xiaoming has the sendFlower method
let Xioaming = {
    sendFlower(target){
        var flower = new Flower("roses")
        target.receive(flower)
    }
}
// Object B has a method of receiving flowers, and after receiving flowers, monitor A's mood, and pass in the function when A is in a good mood
let B = {
    receive(flower){
        this.flower =flower
        A.listenMood(()=>{
            A.receive(this.flower)
        })
    }

}
// After receiving the flower, A outputs the name of the flower
let A = {
    receive(flower){
        console.log(`A received ${flower.name} `)
        // A received roses 
    },
    listenMood(func){
        setTimeout(func,1000)
    }
}
Xioaming.sendFlower(B)
copy

Virtual agent is used for preloading of pictures

The picture is very large, and the page will be blank when it loads, and the experience is not good, so we need a placeholder to temporarily replace the picture, and put it on when the picture is loaded.

let myImage = (function(){
    let img = new Image
    document.body.appendChild(img)
    return {
        setSrc:(src)=>{
            img.src = src
        }
    }
})()
let imgProxy =(function(){
    let imgProxy = new Image
    // In this place, I used setTimeout to enhance the presentation effect, otherwise the local loading is too fast to be seen at all.
    imgProxy.onload=function(){
        setTimeout(()=>{
            myImage.setSrc(this.src)
        },2000)
    }
    
    return (src)=>{
        myImage.setSrc("../../img/bgimg.jpeg")
        imgProxy.src=src
    }
})()

imgProxy("../../img/background-cover.jpg")
copy

ES6 Proxy

In fact, in ES6, there is already Proxy, a built-in function. Let's use an example to demonstrate its usage. This is a question of star agency.

let star={
    name : "open XX",
    age:25,
    phone : "1300001111"
}
let agent = new Proxy(star,
    {
        get:function(target,key){
            if(key === "phone"){
                return  "18839552597"
            }else if(key === "name"){
                return "open XX"
            }else if(key === "price"){
                return "12W"
            }else if(key === "customPrice"){
                return target.customPrice
            }
        },
        set:function(target,key,value){
            if(key === "customPrice"){
                if(value < "10"){
                    console.log("too low! ! !")
                    return false
                }else{
                    target[key] = value
                    return true
                }
            }
        }
    }
)

console.log(agent.name)
console.log(agent.price)
console.log(agent.phone)
console.log(agent.age)
agent.customPrice = "12"
console.log(agent)
console.log(agent.customPrice)
copy

Validation of Design Principles

The proxy class is separated from the target class, isolating the target class and the user

in line with the open-closed principle

behavioral model

1. Strategy pattern

The strategy pattern is a simple but commonly used design pattern, and its application scenarios are very wide. Let's first understand the concept of the strategy pattern, and then use code examples to understand it more clearly.

The strategy pattern consists of two parts: one is the strategy group that encapsulates different strategies, and the other is the Context. Context has the ability to execute policies through composition and delegation, so as to achieve reusability, scalability and maintainability, and avoid a lot of copy and paste work.

A typical application scenario of the strategy pattern is the encapsulation of validation rules in form validation. Next, let's take a look at a simple example:

/**
 * login controller
 */
function LoginController() {
  this.strategy = undefined;
  this.setStrategy = function (strategy) {
    this.strategy = strategy;
    this.login = this.strategy.login;
  }
}

/**
 * Username, password login policy
 */
function LocalStragegy() {
  this.login = ({ username, password }) => {
    console.log(username, password);
    // authenticating with username and password... 
  }
}

/**
 * Mobile phone number, verification code login strategy
 */
function PhoneStragety() {
  this.login = ({ phone, verifyCode }) => {
    console.log(phone, verifyCode);
    // authenticating with hone and verifyCode... 
  }
}

/**
 * Third Party Social Login Policy
 */
function SocialStragety() {
  this.login = ({ id, secret }) => {
    console.log(id, secret);
    // authenticating with id and secret... 
  }
}

const loginController = new LoginController();

// Call the username and password login interface, using LocalStrategy
app.use('/login/local', function (req, res) {
  loginController.setStrategy(new LocalStragegy());
  loginController.login(req.body);
});

// Call the mobile phone, verification code login interface, use PhoneStrategy
app.use('/login/phone', function (req, res) {
  loginController.setStrategy(new PhoneStragety());
  loginController.login(req.body);
});

// Call the social login interface, using SocialStrategy
app.use('/login/social', function (req, res) {
  loginController.setStrategy(new SocialStragety());
  loginController.login(req.body);
});
copy

From the above examples, it can be concluded that using the strategy pattern has the following advantages:

  1. Conveniently switch algorithms and strategies at runtime
  2. The code is more concise, avoiding the use of a large number of conditional judgments
  3. Separation of concerns, each strategy class controls its own algorithm logic, and the strategy and its users are also independent of each other

2. Observer pattern

Observer mode is also called publish/subscribe mode (Publish/Subscribe), which defines a one-to-many relationship, allowing multiple observer objects to monitor a subject object at the same time, when the state of the subject object changes, it will Notifies all observer objects so they can update themselves automatically. Typical representatives are vue/react, etc.

Benefits of using the observer pattern:

  1. Supports simple broadcast communication and automatically notifies all subscribed objects.
  2. The target object is dynamically associated with the observer, which increases flexibility.
  3. The abstract coupling relationship between the target object and the observer can be independently extended and reused.

Of course, addEventListener(), which binds events to elements, is also a kind of:

target.addEventListener(type, listener [, options]);
copy

Target is the observed object Subject, and listener is the observer Observer.

Subject objects in observer mode generally need to implement the following API s:

  • subscribe(): Receive an observer observer object to make it subscribe to itself
  • unsubscribe(): Receive an observer observer object to make it unsubscribe itself
  • fire(): trigger an event and notify all observers

Implement the observer pattern manually in JavaScript:

// Observed
function Subject() {
  this.observers = [];
}

Subject.prototype = {
  // subscription
  subscribe: function (observer) {
    this.observers.push(observer);
  },
  // unsubscribe
  unsubscribe: function (observerToRemove) {
    this.observers = this.observers.filter(observer => {
      return observer !== observerToRemove;
    })
  },
  // event trigger
  fire: function () {
    this.observers.forEach(observer => {
      observer.call();
    });
  }
}
copy

Verify that the subscription was successful:

const subject = new Subject();

function observer1() {
  console.log('Observer 1 Firing!');
}


function observer2() {
  console.log('Observer 2 Firing!');
}

subject.subscribe(observer1);
subject.subscribe(observer2);
subject.fire();
copy

output:

Observer 1 Firing! 
Observer 2 Firing!
copy

Verify that unsubscribing was successful:

subject.unsubscribe(observer2);
subject.fire();
copy

output:

Observer 1 Firing!
copy

3. Iterator pattern

Iterators in ES6 Iterator I believe everyone is familiar with iterators. Iterators are used to traverse containers (collections) and access elements in containers, and no matter what the data structure of the container is (Array, Set, Map, etc.), the interface of the iterator should be the same, requiring follow iterator protocol.

The iterator pattern solves the following problems:

  1. Provides a consistent way of traversing various data structures without knowing the internal structure of the data
  2. Provides the ability to iterate over containers (collections) without changing the container's interface

An iterator usually needs to implement the following interface:

  • hasNext(): Determine whether the iteration is over, return Boolean
  • next(): Find and return the next element

Implementing an iterator for a Javascript array can be written like this:

const item = [1, 'red', false, 3.14];

function Iterator(items) {
  this.items = items;
  this.index = 0;
}

Iterator.prototype = {
  hasNext: function () {
    return this.index < this.items.length;
  },
  next: function () {
    return this.items[this.index++];
  }
}
copy

Verify iterators:

const iterator = new Iterator(item);

while(iterator.hasNext()){
  console.log(iterator.next());
}
copy

output:

1, red, false, 3.14
copy

ES6 provides a simpler iterative loop syntax for...of, the premise of using this syntax is that the operation object needs to implement The iterable protocol , simply speaking, the object has a method whose Key is Symbol.iterator, which returns an iterator object.

For example, we implement a Range class to iterate over a certain range of numbers:

function Range(start, end) {
  return {
    [Symbol.iterator]: function () {
      return {
        next() {
          if (start < end) {
            return { value: start++, done: false };
          }
          return { done: true, value: end };
        }
      }
    }
  }
}
copy

verify:

for (num of Range(1, 5)) {
  console.log(num);
}
copy

result:

1, 2, 3, 4
copy

4. State mode

State mode: An object has a state change, and each state change will trigger a logic, which cannot always be controlled by if...else.

For example, traffic lights:

// Status (red light, green light, yellow light)
class State {
    constructor(color) {
        this.color = color;
    }
    // set state
    handle(context) {
        console.log(`turn to ${this.color} light`);
        context.setState(this)
    }
}

// main body
class Context {
    constructor() {
        this.state = null;
    }
    // get status
    getState() {
        return this.state;
    }
    setState(state) {
        this.state = state;
    }
}

// test
let context = new Context();
let green = new State('green');
let yellow = new State('yellow');
let red = new State('red');

// green light on
green.handle(context);
console.log(context.getState())

// yellow light on
yellow.handle(context);
console.log(context.getState())

// red light is on
red.handle(context);
console.log(context.getState())
copy

Validation of Design Principles

Separate the state object from the subject object, and process the state change logic separately

in line with the open-closed principle

Copyright statement: The content of this article is contributed spontaneously by Internet users, and the opinions of this article only represent the author himself. This site only provides information storage space services, does not own ownership, and does not assume relevant legal responsibilities. If you find any suspected infringement/violation of laws and regulations on this site, please send an email to report, once verified, this site will be deleted immediately.

Publisher: Full-Stack Programmer-User IM, please indicate the source for reprinting: https://javaforall.cn/210090.html Original link: https://javaforall.cn

Tags: Java ECMAScript Container

Posted by mem0ri on Fri, 18 Nov 2022 10:32:55 +0530