Front end to improve code quality -- Design Pattern

Premise: when you initiate a project merger request, you are often called back for modification. The reason is that there are too many low-quality codes. You have thought about it before writing the code, but you have realized the requirements, but the degree of thinking is too superficial. Now you plan to summarize the design model of the code and cheer for writing high-quality code

Requirements for high quality codes:
1. Code semantics: variable names and function names are meaningful. It is best to annotate the code
2. Reduce duplicate code: the code is highly abstract and the reusability of the code is improved
3. The function of the module is single: a module focuses on one function. When we need to expand the function, we can combine multiple modules, and the reusability will increase
4. High code expansibility: low code coupling and high expansibility. Local code modification will not introduce large-scale changes, and new functions and modules can be easily introduced
5. The encapsulated code has strong performance: high cohesion, low coupling, internal variables will not pollute the outside, and can be used as a module for external calls, which external callers do not need to know
The details of the internal implementation only need to be used according to the agreed specifications. It is open to extension and closed to modification (opening and closing principle). The module cannot be modified externally
Only the extension interface needs to be reserved, and the correctness of the module is also guaranteed
1. Policy / status mode
**Basic structure of policy mode: * * reduces the number of if... else, improves the readability of code, and selects specific policies by passing in a state
If we need to make a calculator and support addition, subtraction, multiplication and division, we need four if... else to judge which operation the user needs to perform. If more operations are supported, the if... else will be longer, which is not conducive to reading and looks not elegant. Therefore, we can use the policy mode to optimize as follows:

function calculator(type, a, b) {
  const strategy = {
    add: function(a, b) {
      return a + b;
    },
    minus: function(a, b) {
      return a - b;
    },
    division: function(a, b) {
      return a / b;
    },
    times: function(a, b) {
      return a * b;
    }
  }
  
  return strategy[type](a, b);
}

// When used
calculator('add', 1, 1);

State mode basic architecture:
The state mode is similar to the policy mode. There is also an object to store some policies, but there is also a variable to store the current state. We obtain specific operations according to the current state:

function stateFactor(state) {
  const stateObj = {
    status: '',
    state: {
      state1: function(){},
      state2: function(){},
    },
    run: function() {
      return this.state[this.status];
    }
  }
  
  stateObj.status = state;
  return stateObj;
}

// When used
stateFactor('state1').run();

2. Appearance mode (common)
**Basic structure of appearance mode: * * encapsulate each small module into a higher-level interface and transfer it to a simple interface outside
When we design a module, the internal methods can be designed in detail, but when exposed to external use, we do not have to directly expose these small interfaces. What external users need may be to combine some interfaces to realize a function. In fact, we can organize this when exposed.

function model1() {}

function model2() {}

// A higher-order interface can be provided, and model1 and model2 are combined for external use
function use() {
  model2(model1());
}

Example: common interface encapsulation
The appearance mode is actually very common. Many modules are very complex internally, but there may be one or two external interfaces. We don't need to know the complex internal details. We just need to call a unified high-level interface, such as the following tab module:

// A tab class may have multiple sub modules
function Tab() {}

Tab.prototype.renderHTML = function() {}    // Sub modules for rendering pages
Tab.prototype.bindEvent = function() {}    // Sub module of binding event
Tab.prototype.loadCss = function() {}    // Load sub module of style

// There is no need to expose the specific sub modules above, just a high-level interface
Tab.prototype.init = function(config) {
  this.loadCss();
  this.renderHTML();
  this.bindEvent();
}

3. Iterator mode
**Basic structure of iterator pattern: * * when a large number of data with similar structures are transmitted from the back end, and the js array can not handle these data well, we can encapsulate the iterator function according to our needs
Iterator mode is very common in JS. forEach provided by array is an application of iterator mode. We can also implement a similar function:

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

Iterator.prototype.dealEach = function(fn) {
  for(let i = 0; i < this.items.length; i++) {
    fn(this.items[i], i);
  }
}

4. Memo mode
**Basic structure of memo mode: * * add a cache object to record the status of previously obtained data or operations, which can be used to speed up access or roll back the status later
The memo mode is similar to the cache function often used by JS. It internally records a state, that is, cache. When we access it again, we can directly take the cached data and use it to realize the forward and backward function of operation:

function memo() {
  const cache = {};
  
  return function(arg) {
    if(cache[arg]) {
      return cache[arg];
    } else {
      // When there is no cache, execute the method first to get the result res
      // Then write res to the cache
      cache[arg] = res;
      return res;
    }
}

5. Bridging mode
The bridging mode, as its name suggests, is actually equivalent to a bridge, bridging variables of different dimensions together to realize functions. Suppose we need to implement three shapes (rectangle, circle and triangle), and each shape has three colors (red, green and blue). There are two schemes for this requirement. One scheme writes nine methods, and each method implements a graph:

function redRectangle() {}
function greenRectangle() {}
function blueRectangle() {}
function redCircle() {}
function greenCircle() {}
function blueCircle() {}
function redTriangle() {}
function greenTriangle() {}
function blueTriangle() {}


After using the bridge pattern, we can observe that duplicate code is broken into multiple dimensions, and then these dimensions are spliced together
function rectangle(color) {     // rectangle
  showColor(color);
}

function circle(color) {     // circular
  showColor(color);
}

function triangle(color) {   // triangle
  showColor(color);
}

function showColor(color) {   // Method of displaying color
  
}

// When using, a red circle is required
let obj = new circle('red');

**6. Meta sharing mode: * * when we observe that there are a large number of similar code blocks in the code, they may do the same things, but the objects applied each time are different, we can consider using meta sharing mode. Now suppose we have a requirement to display multiple pop-up windows, and the text and size of each pop-up window are different:

// There is already a pop-up class
function Popup() {}

// The pop-up class has a display method
Popup.prototype.show = function() {}

If we don't use the meta mode, it's like this one by one:
var popup1 = new Popup();
popup1.show();

var popup2 = new Popup();
popup2.show();

After using the meta mode:
var popupArr = [
  {text: 'popup 1', width: 200, height: 400},
  {text: 'popup 2', width: 300, height: 300},
]

var popup = new Popup();
for(var i = 0; i < popupArr.length; i++) {
  popup.show(popupArr[i]);    // Note that the show method needs to receive parameters
}

In short, the meta model is commonly used in development. We extract the methods that are repeatedly used in different instances. We only need to pass in different parameters to realize different functions in the same way

**7. Template method pattern: * * template method pattern is actually similar to inheritance, that is, we first define a general template skeleton, and then continue to expand on this basis. Let's look at its basic structure through a requirement. Suppose we need to implement a navigation component now, but there are still many navigation types, some with message prompts, some horizontal and some vertical, and new types may be added later:

// First build a basic class
function baseNav() {
}

baseNav.prototype.action = function(callback){}  //Receive a callback for specific processing

In the above code, we first build a basic class, which has only the most basic properties and methods. In fact, it is equivalent to a template, and callback can be received in specific methods, so that the later derived classes can pass in callback according to their own needs

Example: pop up window

In the previous pop-up example, we need to make a pop-up component with different sizes of text. However, this time, our pop-up window also has two buttons: Cancel and confirm. These two buttons may have different behaviors in different scenarios, such as initiating requests. However, they also have a common operation, that is, after clicking these two buttons, the pop-up window will disappear, so that we can write the common part first as a template:

function basePopup(word, size) {
  this.word = word;
  this.size = size;
  this.dom = null;
}

basePopup.prototype.init = function() {
  // Initializing DOM elements
  var div = document.createElement('div');
  div.innerHTML = this.word;
  div.style.width = this.size.width;
  div.style.height = this.size.height;
  
  this.dom = div;
}

// Method of cancellation
basePopup.prototype.cancel = function() {
  this.dom.style.display = 'none';
}

// Method of confirmation
basePopup.prototype.confirm = function() {
  this.dom.style.display = 'none';
}

**8. Factory mode: * * the encapsulated module is like a factory, which outputs the required objects in batches. A feature of the common factory pattern is that new is not required when calling, and the parameters passed in are relatively simple. However, the number of calls may be frequent, and it is often necessary to produce different objects. It is much more convenient to call frequently without new. The code structure of a factory mode is as follows:

function factory(type) {
  switch(type) {
    case 'type1':
      return new Type1();
    case 'type2':
      return new Type2();
    case 'type3':
      return new Type3();
  }
}
// We pass in the type, and then the factory creates different objects according to different types
 example: Pop up window assembly
 Our project needs a pop-up window. There are several pop-up windows: Message pop-up window, confirmation pop-up window and cancellation pop-up window. Their colors and contents may be different. Factory mode transformation:
 // Add a new method pop to wrap these classes
function popup(type, content, color) {
  switch(type) {
    case 'infoPopup':
      return new infoPopup(content, color);
    case 'confirmPopup':
      return new confirmPopup(content, color);
    case 'cancelPopup':
      return new cancelPopup(content, color);
  }
}
// Call method
let infoPopup1 = popup('infoPopup', content, color); 
Transform into object-oriented
 Although the above code implements the factory mode, but switch Always feel not very elegant. We use object-oriented transformation popup,Change it into a class and mount different types of pop-up windows on this class to become factory methods:
function popup(type, content, color) {
  // If it is called through new, the pop-up window of the corresponding type is returned
  if(this instanceof popup) {
    return new this[type](content, color);
  } else {
    // If it is not called by new, you will go to the above line of code by using new
    return new popup(type, content, color);
  }
}

// All types of pop-up windows are mounted on the prototype to become an example method
popup.prototype.infoPopup = function(content, color) {}
popup.prototype.confirmPopup = function(content, color) {}
popup.prototype.cancelPopup = function(content, color) {}

9. Builder mode
The builder mode is used to build complex large objects, such as Vue. Vue contains a powerful and logically complex object, and many parameters need to be passed in during construction. The builder mode is applicable when there are few cases that need to be created and the created object itself is very complex. The general structure of the builder model is as follows:

function Model1() {}   // Module 1
function Model2() {}   // Module 2

// End use class
function Final() {
  this.model1 = new Model1();
  this.model2 = new Model2();
}

// When used
var obj = new Final();
// In the above code, we finally use Final, but the structure in Final is complex and there are many sub modules,
// Final is to combine these sub modules to complete the function. This fine structure is applicable to the builder mode.

Keywords: Javascript Front-end

Added by bg on Sun, 09 Jan 2022 11:38:14 +0200