The code has been written for several years, and the design pattern is in a state of forgetting and forgetting. Recently, I have some feelings about the design pattern, so I will learn and summarize it again.

Most speak design pattern articles are using Java, c + + such based on the class the static type of language, as a front-end developer, js this prototype based dynamic language, function has become a first class citizen, slightly different on some design patterns, even simple to don’t like to use the design patterns, sometimes also can produce some confusion.

The following are summarized in the order of “scenario” – “design pattern definition” – “code implementation” – “general”. If there are any improper points, welcome to discuss.

scenario

In normal business development, we generally encapsulate network requests into a module and expose GET and POST methods for everyone to use.

// src/util/request.js
import Http from '.. /http';

export function get(options) {
    return Http.get(options);
}

export function post(obj) {
    return Http.post(options);
}

Copy the code

The Http module mainly encapsulates ajax requests, populates headers, etc., and then the business side only needs to introduce the above GET and POST.

import { post, get } from 'src/util/request';

async generateShareImage() {
  const body = this.generateConfig();
  try {
    const res = await post({
      url: '/getData',
      body,
      setting: {
        domain: config.getExhibitionDomain(),
      },
    });
    if(res? .picUrl) {return res;
    }
    return null;
  } catch (error) {
    log.error(` failure `.JSON.stringify(error));
  }
  return null;
}
Copy the code

Now we have a new requirement that we need to insert the GRAYType field from the back end of the first request into the headers field of the subsequent request, as shown below.

import { post, get } from 'src/util/request';
let graytype = -1;
async generateShareImage() {
  const body = this.generateConfig();
  try {
    const options = {
      url: '/getData',
      body,
      setting: {
        domain: config.getExhibitionDomain(),
      },
      headers: {}}// Get the grayType before you put it in
    if(graytype ! = = -1) {
      	options.headers.graytype = graytype;
    }
    const res = await post(options);
    // Add logic
    if(res.graytype ! = =undefined&& res.graytype ! = =null) {
        graytype = res.graytype;
    }
    if(res? .picUrl) {return res;
    }
    return null;
  } catch (error) {
    log.error(` failure `.JSON.stringify(error));
  }
  return null;
}
Copy the code

If it’s just one request, you can change it like this, but if it’s multiple requests, it’s silly to change it like this.

Why not just change the Http module? Adding the grayType field is only responsible for business changes, and the Http module is shared by all lines of business, so we can’t change it directly.

This is where the proxy pattern is needed.

The proxy pattern

Post some explanations from Wikipedia:

What problems can the Proxy design pattern solve?

  • The access to an object should be controlled.
  • Additional functionality should be provided when accessing an object.

What solution does the Proxy design pattern describe?

Define a separate Proxy object that

  • can be used as substitute for another object (Subject) and
  • implements additional functionality to control the access to this subject.

The proxy mode extends the original object to achieve control or additional operations on the original object. In different scenarios, the proxy mode can be divided into many categories:

  1. Remote proxy: In proxy mode, remote objects can be operated as local objects.

  2. Virtual proxy: In place of a complex or heavy object, a skeleton representation may be advantageous In some cases. For example, we can introduce proxy objects to load a small image, and then display the large image after loading the large image.

  3. Protected proxy: Controls access to properties of the original object.

  4. Caching proxy: Introduces a cache to cache previous results, such as the Fibonacci sequence.

    .

Whatever the new name, the essence is the same, and it looks like this in a class diagram:

The RealSubject and Proxy objects inherit the Subject interface. The Client calls DoAction(), passes through the Proxy object, and the Proxy does additional operations. Finally, the RealSubject is delegated to execute.

Take a look at a Java example:

interface Image {
    public void displayImage(a);
}

// On System A
class RealImage implements Image {
    private final String filename;

    /**
     * Constructor
     * @param filename
     */
    public RealImage(String filename) {
        this.filename = filename;
        loadImageFromDisk();
    }

    /** * Loads the image from the disk */
    private void loadImageFromDisk(a) {
        System.out.println("Loading " + filename);
    }

    /** * Displays the image */
    public void displayImage(a) {
        System.out.println("Displaying "+ filename); }}// On System B
class ProxyImage implements Image {
    private final String filename;
    private RealImage image;
    
    /**
     * Constructor
     * @param filename
     */
    public ProxyImage(String filename) {
        this.filename = filename;
    }

    /** * Displays the image */
    public void displayImage(a) {
        if (image == null) {
           image = newRealImage(filename); } image.displayImage(); }}class ProxyExample {
   /** * Test method */
   public static void main(final String[] arguments) {
        Image image = new ProxyImage("HiRes_10MB_Photo1"); image.displayImage(); }}Copy the code

The original RealImage class calls loadImageFromDisk when a new object is created. If there is no displayImage call afterwards and loadImageFromDisk is a resource hog, it would be a waste.

Through ProxyImage, the object of RealImage is held internally, and the object is instantiated when displayImage is called, realizing the lazy loading of the object.

The downside of course is that it might take time to call the displayImage the first time. Therefore, whether to introduce the proxy pattern in this example depends on the actual scenario.

Let’s rewrite this with js:

function RealImage(filename) {
    this.filename = filename;
    const loadImageFromDisk = () = > {
        console.log('Loading ' + filename);
    };
    loadImageFromDisk();
    return {
        displayImage: () = > {
            console.log('Displaying '+ filename); }}; }function ProxyImage(filename) {
    this.filename = filename;
    let image = null;
    return {
        displayImage: () = > {
            if (image === null) { image = RealImage(filename); } image.displayImage(); }}; }// Test
const image = ProxyImage('HiRes_10MB_Photo1');
image.displayImage();
Copy the code

The whole idea is the same, but JS doesn’t have to define interfaces or classes, so it looks much simpler. You just need to implement the same return as the original object.

Code implementation

Back to the original scenario: now we have a new requirement to plug the GRAYType field in the backend return request from the first request into the HEADERS field in the subsequent request.

We can use proxy mode to encapsulate get and POST in Request.js and expose get and POST as well.

// src/util/requestNew.js

import { post as Post, get as Get } from './request.js';

let graytype = -1;

const getNewParams = (params) = > {
    // Add grayType
    if(graytype ! = = -1) { newParams = { ... params,headers: {
                ...params.headers,
                graytype,
            },
        };
    }
    return newParams;
};
export const get = async (params) => {
    const response = await Get(getNewParams(params));
    const res = response.data;
    if(res.graytype ! = =undefined&& res.graytype ! = =null) {
        graytype = res.graytype;
    }
    return response;
};
export const post = async (params) => {
    const response = await Post(getNewParams(params));
    const res = response.data;
    if(res.graytype ! = =undefined&& res.graytype ! = =null) {
        graytype = res.graytype;
    }
    return response;
};
Copy the code

We import the original GET and POST, and rename the imported ones to GET and POST because we still need to export get and POST.

GrayType is then inserted into headers before the request and assigned to grayType during GET and POST.

So in real business, if grayType is needed, we just need to rewrite SRC /util/requestNew.js to introduce get and POST, and nothing else needs to be changed.

The total

The proxy pattern is simply a matter of wrapping the original object/function with another layer and maintaining the same behavior as the original object. So why not just change the original object?

First, it may not be convenient to change the original object directly, so the proxy mode package layer is adopted.

Second, “single responsibility principle”. If the original object is directly modified, the complexity of the original object will increase. If the original object is responsible for too many responsibilities, there will be more reasons for object modification.

Third, if new functions need to be removed in the future, it will not be convenient to modify them. If the proxy pattern is used, all you need to do is restore the original references.