preface

Design Mode series:

Front end design pattern factory pattern

Proxy patterns for front-end design patterns

The strategy pattern of the front-end design pattern

Front end design pattern decoration pattern

Chain of Responsibility model

What is the chain of responsibility model

The definition of Chain of Responsibility pattern: in order to avoid the request sender and multiple request handlers coupling together, all request handlers are linked into a Chain by the previous object remembering the reference of the next object; When a request occurs, it can be passed along the chain until an object processes it. (Here quoted from GOF design pattern)

In the responsibility chain mode, the customer only needs to send the request to the responsibility chain, and does not need to care about the processing details of the request and the transmission process of the request, so the responsibility chain decouples the sender of the request from the handler of the request.

As an object behavior mode, the chain of responsibility mode has the following advantages:

  1. Reduces the degree of coupling between objects. This pattern makes it unnecessary for an object to know which object is handling its request and chain structure, and for sender and receiver to have explicit information about each other.
  2. The scalability of the system is enhanced. New request handling classes can be added as needed to satisfy the open close principle.
  3. Increased flexibility in assigning responsibilities to objects. As the workflow changes, members in the chain can be dynamically changed or reordered, and responsibilities can be dynamically added or removed.
  4. Chains of responsibility simplify the connection between objects. Each object maintains only one reference to its successors, not all other handlers, which avoids the need for numerous if or if· else statements.
  5. Burden sharing. Each class only needs to deal with its own work, should not deal with the next object to complete, clear the scope of responsibility, in line with the single responsibility of the class principle.

Its main disadvantages are as follows.

  1. There is no guarantee that every request will be processed. Because a request has no clear recipient, there is no guarantee that it will be processed, and the request may not be processed all the way to the end of the chain.
  2. For a long chain of responsibilities, the processing of the request may involve multiple processing objects, and the system performance will be affected to some extent.
  3. The rationality of responsibility chain establishment depends on the client to ensure, which increases the complexity of the client, and may lead to system errors due to the wrong setting of responsibility chain, such as circular invocation.

Other instructions

The overall core of the chain of responsibility mode is that the requester does not need to know which node object handles the request. Because the requester can be handled under different objects, the requester and the receiver are decoupled.

Pure chains of responsibility: requests must be processed in these object chains, and one node processes the object, either processing the request only, or forwarding the request to the next node object;

Impure responsibility chain: requirements in the responsibility chain will not necessarily have a processing structure, and a node object, that is, can process part of the request, and forward the request to the next node processing;

Javascript mediator pattern

The chain of responsibility model may seem unfamiliar to front-end development, but it feels familiar from the previous description

In fact, Middleware in Express and Redux can be simply understood as chain of responsibility

The most important implementation details to implement the middleware pattern are:

  1. New middleware can be registered by calling the use() function
  2. When new data is received that needs to be processed, the registered middleware is called in turn in the execution flow. Each middleware takes the execution result of the previous middleware as an input value
  3. Each middleware can stop further processing of data by simply not calling its callback function or passing errors to the callback function. When an error occurs, it usually triggers the execution of another middleware dedicated to handling the error

The project of actual combat

General Middleware Development

class Middleware {
  constructor() {
    this.$cache = []
    this.$middlewares = []
  }
  // Register middleware  use() {  [...arguments].forEach(item= > {  if (typeof item === 'function') {  this.$cache.push(item)  }  })  return this  }   / * ** Each middleware has only two parameters: the first is the parameter passed in and the second is the function that calls the next middlewareMiddleware execution order is based on the order in which you registered middleware* /  next(params) {  while (this.$middlewares.length) {  const ware = this.$middlewares.shift()  ware.call(this, params, this.next.bind(this))  }  }   execute(params) {  this.$middlewares = this.$cache.map(fn= > { // Make a copy  return fn;  });  this.next(params)  }  }  export default Middleware Copy the code

Generic middleware uses Ajax

const middleware = new Middleware()

function transform(options, next) {
  console.log('before', options.data);
  options.data.age = Number(options.data.age)
 next(options); // Pass validation }  function validate(options, next) {  console.log('validate', options.data);  next(options); // Pass validation }  function send(options, next) {  setTimeout(function () { // Simulate asynchrony  console.log('send', options.data);  next();  }, 100); }  middleware.use(transform).use(validate).use(send) middleware.execute({ data: { name: 'cookie'.age: '20'}});Copy the code

As mentioned above, we added type conversion and data verification before sending the request, and stripped the business processing of data with middleware mode, so that the processing process was modular and the maintainability was improved.

Middleware upgrade – Event callback

/ * ** Registration event* @param {String} name Specifies the event name* @param {Function (params)} callback Function* /
on(name, callback) { if (typeof callback === 'function') {  this.$events[name] = callback } else {  throw 'Event callbacks must be functions' } }  / * ** Launch (trigger) events* @param {String} name Specifies the event name* @param {Any} Params callback parameter* / emit(name, params) { if (this.$events[name]) {  let callback = this.$events[name]  callback.call(this, params) } else {  throw 'Not registered for this event' } } Copy the code

The process of each middleware is out of control, all of which are uniformly called by the intermediate class. We can add event callback, which is convenient for us to have additional logic ability in the process of middleware processing

The above method of use will be transformed again, convenient for actual business use

function send(options, next) {
  this.emit('request', options)
  setTimeout((a)= > { // Simulate asynchrony
    console.log('send', options.data);
    this.emit('response', options)
 options.promise.resolve({ data: options.data })  }, 100); }  // The callback function before the request middleware.on('request', params => {  // Here you can do some pre-request processing, such as adding global parameters, etc  console.log(params, 'Do a little more processing.') })  // Request a successful callback middleware.on('response', params => {  // Do some processing for the successful request, such as global loading  console.log(params, 'Request successful') })  middleware.use(transform).use(validate).use(send)  middleware.executeFc({ data: { name: 'cookie'.age: '20' } }).then(({ data }) = > {  console.log('finally', data) }); Copy the code

The above project example uses Ajax to demonstrate, and the actual general middleware class can split some process-based tasks in the business, such as form verification, multi-condition judgment and so on

Multiple conditional judgment

const middleware = new Middleware()

function judge1(options, next) { // Null check
  if(! options.data) {    options.promise.reject({ data: false.msg: 'Data is empty' })
 return  }  next(options); // Pass validation }  function judge2(options, next) { // It is less than 10  if (options.data < 10) {  options.promise.reject({ data: false.msg: 'Data less than 10' })  return  }  next(options); // Pass validation }  function judge3(options, next) { // The value is greater than 30  if (options.data < 30) {  options.promise.reject({ data: false.msg: 'Data less than 30' })  return  }  options.promise.resolve({ data: true.msg: 'Data less than 30' }) }  middleware.use(judge1).use(judge2).use(judge3)  middleware.executeFc({ data: 40 }).then(({ data }) = > {  console.log('finally', data) }).catch(({ msg }) = > {  console.log(msg) }) Copy the code

Decoupling the multiple conditional judgments of process-based execution through middleware can make the conditional judgment method clearer. Typically when you need to use intermediaries to transform business logic, front-end projects can get a little more complicated.

The end of the

Complete demo address: project actual combat demo, like friends can star, the follow-up will be based on the launch of the design mode blog, and gradually expand the project.

Manual doghead town building


This article is formatted using MDNICE