Front end design mode

Posted by Flinch on Wed, 22 Sep 2021 05:53:14 +0200

Strategy mode

Define a series of algorithms, close them one by one, and make them replace each other.

Example: processing store promotion types

prePrice - Treatment preheating price
onSalePrice - Deal with big price promotion
backPrice - Processing return price
freshPrice - Deal with early taste price
askPrice - Distribute RFQ logic

Implementation: originally, it was all placed in askPrice, and each case was processed through the ifelse branch. Instead, it encapsulated each individual function
However, there are still shortcomings: the effect of "opening to expansion and closing to modification" is not realized.

// Treatment preheating price
function prePrice(originPrice) {
  if(originPrice >= 100) {
    return originPrice - 20
  } 
  return originPrice * 0.9
}

// Deal with big price promotion
function onSalePrice(originPrice) {
  if(originPrice >= 100) {
    return originPrice - 30
  } 
  return originPrice * 0.8
}

// Processing return price
function backPrice(originPrice) {
  if(originPrice >= 200) {
    return originPrice - 50
  }
  return originPrice
}

// Deal with early taste price
function freshPrice(originPrice) {
  return originPrice * 0.5
}

function askPrice(tag, originPrice) {
  // Treatment preheating price
  if(tag === 'pre') {
    return prePrice(originPrice)
  }
  // Deal with big price promotion
  if(tag === 'onSale') {
    return onSalePrice(originPrice)
  }

  // Processing return price
  if(tag === 'back') {
    return backPrice(originPrice)
  }

  // Deal with early taste price
  if(tag === 'fresh') {
     return freshPrice(originPrice)
  }
}

Policy pattern implementation: object mapping

// Inquiry function
function askPrice(tag, originPrice) {
  return priceProcessor[tag](originPrice)
}
// Define a RFQ processor object
const priceProcessor = {
  pre(originPrice) {
    if (originPrice >= 100) {
      return originPrice - 20;
    }
    return originPrice * 0.9;
  },
  onSale(originPrice) {
    if (originPrice >= 100) {
      return originPrice - 30;
    }
    return originPrice * 0.8;
  },
  back(originPrice) {
    if (originPrice >= 200) {
      return originPrice - 50;
    }
    return originPrice;
  },
  fresh(originPrice) {
    return originPrice * 0.5;
  },
};

Add a new price

priceProcessor.newUser = function (originPrice) {
  if (originPrice >= 100) {
    return originPrice - 50;
  }
  return originPrice;
}

Summary: define a series of algorithms, encapsulate them one by one, and make them replaceable.

A program based on policy pattern consists of at least two parts. The first part is a group of policy classes, which encapsulates the specific algorithm and is responsible for the specific calculation process. The second part is the environment class context. comtext accepts the customer's request and then delegates the request to a policy class.

The algorithm is the inquiry logic in our scenario, and it can also be the logic of any of your function functions; "Encapsulation" is to put forward the logic corresponding to a function point; "Replaceable" is based on encapsulation. It just means that in the judgment process of "replacement", we can't directly connect if else, but consider a better mapping scheme.

proxy pattern

Proxy mode provides a proxy object for an object, and the proxy object controls the access to the original object.

The proxy mode is commonly used in the front end, including virtual proxy and cache proxy.

Why use proxy mode
Mediation isolation function: in some cases, a client class does not want or cannot directly reference a delegate object, and the proxy class object can act as an intermediary between the client class and the delegate object. Its feature is that the proxy class and the delegate class implement the same interface.

Opening and closing principle and adding functions: in addition to being the intermediary between the customer class and the delegate class, the agent class can also expand the functions of the delegate class by adding additional functions to the agent class. In this way, we only need to modify the agent class without modifying the delegate class, which is in line with the opening and closing principle of code design. The agent class is mainly responsible for preprocessing messages for the delegate class, filtering messages, forwarding messages to the delegate class, and processing the returned results afterwards. The proxy class itself does not really implement services, but calls the relevant methods of the delegate class to provide specific services. The real business function is still implemented by the delegate class, but some public services can be added before and after the execution of the business function. For example, if we want to add caching and logging functions to the project, we can use the proxy class instead of opening the encapsulated delegate class

Protection agent:
It is used to control the access of objects with different permissions to the target object, but it is not easy to implement the protection proxy in js, because we can't judge who has accessed a proxy.

Virtual agent:

  1. Virtual agents delay the creation of expensive objects until they are really needed.
  2. The characteristic of virtual proxy is that both proxy class and real class expose the same interface, which is insensitive to the caller.

When it comes to virtual agent, the most representative example is Image preloading. Preloading is mainly to avoid the problem of long-time white space caused by network delay or too large pictures. The usual solution is to first display a bitmap for the img tag, then create an Image instance, and let the src of this instance point to the real target Image address. When the real Image is loaded, then point the src attribute of the img tag on the DOM to the real Image address.

class MyImg {
	static imgNode = document.createElement("img")
	constructor(selector) {
    	selector.appendChild(this.imgNode);
    }
    
    setSrc(src) {
    	this.imgNode = src
    }
}

class ProxyMyImg {
	static src = 'xxx Local preview address loading.gif'
	constructor(selector) {
		this.img = new Image
        this.myImg = new MyImg(selector)
        this.myImg.setSrc(this.src)
    }
    
    setSrc(src) {
    	this.img.src = src
    	this.img.onload = () => {
    		this.myImg.setSrc(src)
        }
    }
}

const img = new ProxyMyImg(document.body)
img.setSrc('xxx')


Cache agent:
The cache agent can provide temporary cache for some expensive operation results. In the next operation, if the parameters passed in are the same as before, the cached operation results can be returned directly.

const mult = (...args) => {
	console.log('multing...')
    let res = 1
    args.forEach(item => {
    	res*=item
    })
    return res 
}
const proxyMult = (() => {
	const cache = {}
    return (...args) => {
    	const key = [].join.call(args, ',')
        if (key in cache) {
        	return cache[args]
        }
        return cache[key] = mult.apply(null, args)
    }
})()
proxyMult(1,2,3,4)// multing... 24
proxyMult(1,2,3,4)//24

Proxy: the proxy class proxy is added in ES6
grammar
const p = new Proxy(target, handler)

  • Target the target object to be wrapped by Proxy (it can be any type of object, including a native array, a function, or even another Proxy).
  • handler is an object that usually takes functions as attributes. The functions in each attribute define the behavior of processing p when performing various operations.
const mult = (args) => {
	console.log('multing...')
    let res = 1
    args.forEach(item => {
    	res*=item
    })
    return res
}

const handler = {
    cache: {},
    apply: function(target, thisArg, args) {
        const key = [].join.call(args, ',')
        if(key in this.cache) {
            return this.cache[key]
        }
        return this.cache[key] = target(args)
    }
}
const proxyMult = new Proxy(mult, handler)
proxyMult(1,2,3,4)
//multing...
//24
proxyMult(1,2,3,4)
//24


Topics: Javascript Design Pattern