JavaScript Design Patterns Series - Policy Patterns and Dynamic Form Verification

Strategy Pattern, also known as policy pattern, defines a series of algorithms, encapsulates them one by one, and makes them interchangeable. The encapsulated policy algorithm is generally independent, and the policy pattern adjusts which algorithm to use according to the input. The key is the separation of strategy implementation and use.

Note: Some coding techniques may be used in this article, such as IIFE (Immediately Invoked Function Expression, call function expression immediately), ES6 syntax let/const,Arrow function,rest parameterShort circuit operator Wait, if you haven't touched it, you can click on the link and learn a little.~

1. Strategic Patterns You Have Seen

There are many kinds and sizes of electronics nowadays. Sometimes you can't help but want to take apart and see what's inside (think of the toy car and remote control that you dismantled when you were a child). But there are many specifications for the screw and the size of the screwdriver is not small. If you buy a screwdriver for every specification, your home will be full of screwdrivers. So now people use multi-functional screwdriver set, screwdriver handle only needs one, encounter different specifications of the screw as long as the screwdriver head can be changed, it is very convenient, and the volume is much smaller.

Another chestnut, a car tire has many specifications, in the muddy road section can be used when driving more muddy tires, in the snow can be used more snow tires, highway driving more time to use high-performance tires, for different use scenarios to replace different tires can be, without changing the whole car.

These are examples of strategy patterns. Screw driver/vehicle belongs to the packaging context, encapsulating and using different screwdriver head/tire, screwdriver head/tire here is equivalent to strategy, and different use strategies can be changed according to different needs.

In these scenarios, there are the following characteristics:

  1. The screwdriver head/tire (strategy) is independent of each other, but can be replaced by each other.
  2. Screw driver/vehicle (encapsulation context) can choose different strategies according to different needs.

2. Code Implementation of Instances

Specific examples we use programming examples to demonstrate, better quantification.

The scenario is that an e-commerce website hopes to organize an activity to sell inventory items through discount promotion. Some items are 100 minus 30, some items are 200 minus 80, and some items are sold at 8% discount directly (recalling the fear of being dominated by double eleven). This logic is given to us. How can we achieve it?

function priceCalculate(discountType, price) {
    if (discountType === 'minus100_30') {           // Full 100 minus 30
        return price - Math.floor(price / 100) * 30
    }
    else if (discountType === 'minus200_80') {  // Full 200 minus 80
        return price - Math.floor(price / 200) * 80
    }
    else if (discountType === 'percent80') {    // 20 percent off
        return price * 0.8
    }
}

priceCalculate('minus100_30', 270)    // Output: 210
priceCalculate('percent80', 250)      // Output: 200

By judging the type of discount input to calculate the total price of goods, several if-else can meet the demand, but the shortcomings of such an approach are obvious:

  1. With the increase of discount types, if-else judgment statements become more and more bulky.
  2. If a new discount type is added or the algorithm of the discount type is changed, the implementation of the priceCalculate function needs to be changed, which violates the open-closed principle.
  3. The reusability is poor. If there are similar algorithms in other places, but the rules are different, the above code can not be reused.

We can modify it by extracting the algorithm part of calculating discount and saving it as an object, and using the type of discount as a key, so that when indexing, we can call a specific algorithm through the key index of the object:

const DiscountMap = {
    minus100_30: function(price) {
        return price - Math.floor(price / 100) * 30
    },
    minus200_80: function(price) {
        return price - Math.floor(price / 200) * 80
    },
    percent80: function(price) {
        return price * 0.8
    }
}

/* Calculate the total price*/
function priceCalculate(discountType, price) {
    return DiscountMap[discountType] && DiscountMap[discountType](price)
}

priceCalculate('minus100_30', 270)
priceCalculate('percent80', 250)

// Output: 210
// Output: 200

In this way, the implementation of the algorithm and the use of the algorithm are separated, and it is very simple to add a new algorithm.

DiscountMap.minus150_40 = function(price) {
    return price - Math.floor(price / 150) * 40
}

If you want the algorithm to be hidden, you can use closures with IIFE, and you need to add a policy entry to facilitate expansion:

const PriceCalculate = (function() {
    /* Price calculation method */
    const DiscountMap = {
        minus100_30: function(price) {      // Full 100 minus 30
            return price - Math.floor(price / 100) * 30
        },
        minus200_80: function(price) {      // Full 200 minus 80
            return price - Math.floor(price / 200) * 80
        },
        percent80: function(price) {        // 20 percent off
            return price * 0.8
        }
    }
    
    return {
        priceClac: function(discountType, price) {
            return DiscountMap[discountType] && DiscountMap[discountType](price)
        },
        addStrategy: function(discountType, fn) {        // New Computing Method for Registration
            if (DiscountMap[discountType]) return
            DiscountMap[discountType] = fn
        }
    }
})()

PriceCalculate.priceClac('minus100_30', 270)    // Output: 210

PriceCalculate.addStrategy('minus150_40', function(price) {
    return price - Math.floor(price / 150) * 40
})
PriceCalculate.priceClac('minus150_40', 270)    // Output: 230

In this way, the algorithm is hidden and the entrance of adding strategy is reserved for easy expansion.

3. General Implementation of Policy Patterns

According to the example above, the discount calculation can be considered as a Strategy. These strategies can be substituted for each other, and the specific discount calculation process can be considered as a Context. The encapsulation context can choose different strategies according to the need.

There are mainly the following concepts:

  1. Context: Encapsulate the context, call the required policy according to the need, shield the direct call of external policy, only provide an interface to the outside world, call the corresponding policy according to the need;
  2. Strategy: A strategy, which contains specific algorithms, has the same appearance and can be substituted for each other.
  3. StrategyMap: A collection of all policies for encapsulating context calls;

The structure diagram is as follows:

The following is a general approach.

const StrategyMap = {}

function context(type, ...rest) {
    return StrategyMap[type] && StrategyMap[type](...rest)
}

StrategyMap.minus100_30 = function(price) { 
      return price - Math.floor(price / 100) * 30
}

context('minus100_30', 270)            // Output: 210

Universal implementations seem to be relatively simple, so here's a look at the actual project.

4. Strategic Models in Actual Warfare

4.1 Form Formter

Here is an example used in the Vue + ElementUI project. The project principles of other frameworks are similar. Let me share with you.

Element Form control Column accepts a formatter parameter to format content, which is of type function, and can also accept several specific parameters, such as Function(row, column, cellValue, index).

Taking file size conversion as an example, the back end often transfers the file size of bit unit directly. Then the front end needs to convert the file size of bit unit into the file size of the unit it needs, such as KB/MB, according to the data of the back end.

Firstly, the algorithm of file computing is implemented.

export const StrategyMap = {
    /* Strategy 1: Converting file size (bit) to KB */
    bitToKB: val => {
        const num = Number(val)
        return isNaN(num) ? val : (num / 1024).toFixed(0) + 'KB'
    },
    /* Strategy 2: Converting file size (bit) to MB */
    bitToMB: val => {
        const num = Number(val)
        return isNaN(num) ? val : (num / 1024 / 1024).toFixed(1) + 'MB'
    }
}

/* Context: Generate el form formatter */
const strategyContext = function(type, rowKey){ 
  return function(row, column, cellValue, index){
      StrategyMap[type](row[rowKey])
  }
}

export default strategyContext

Then in the component we can directly:

<template>
    <el-table :data="tableData">
        <el-table-column prop="date" label="date"></el-table-column>
        <el-table-column prop="name" label="file name"></el-table-column>
        <!-- Direct call strategyContext -->
        <el-table-column prop="sizeKb" label="file size(KB)"
                         :formatter='strategyContext("bitToKB", "sizeKb")'>
        </el-table-column>
        <el-table-column prop="sizeMb" label="Appendix size(MB)"
                         :formatter='strategyContext("bitToMB", "sizeMb")'>
        </el-table-column>
    </el-table>
</template>

<script type='text/javascript'>
    import strategyContext from './strategyContext.js'
    
    export default {
        name: 'ElTableDemo',
        data() {
            return {
                strategyContext,
                tableData: [
                    { date: '2019-05-02', name: 'Document 1', sizeKb: 1234, sizeMb: 1234426 },
                    { date: '2019-05-04', name: 'Document 2', sizeKb: 4213, sizeMb: 8636152 }]
            }
        }
    }
</script>

<style scoped></style>

Examples of code can be consulted Codpen - Practical Operations of Strategic Mode

The results are as follows:

4.2 Form Verification

In addition to formatter s in tables, policy patterns are often used in form validation scenarios. Here's an example of a Vue + ElementUI project. Other frameworks are the same.

ElementUI Form form It has form validation function, which is used to validate the form content entered by users. Form validation items are usually more complex in actual requirements, so it is necessary to add a validator custom validation method to each form item.

We can be like Official Web Example Form validation is also written in the component's state data function, but this makes it difficult to reuse the frequently used form validation method. At this time, we can reconstruct it with the knowledge of policy patterns and function coritization. First, we implement a common form validation method in the project's tool module (usually utils folder):

// src/utils/validates.js

/* Name verification consists of 2-10 bits of Chinese characters */
export function validateUsername(str) {
    const reg = /^[\u4e00-\u9fa5]{2,10}$/
    return reg.test(str)
}

/* Mobile phone number verification consists of 11 digits beginning with 1.  */
export function validateMobile(str) {
    const reg = /^1\d{10}$/
    return reg.test(str)
}

/* Mailbox Check */
export function validateEmail(str) {
    const reg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/
    return reg.test(str)
}

Then add a coritization method in utils/index.js to generate form validation functions:

// src/utils/index.js

import * as Validates from './validates.js'

/* Generating Form Custom Check Function */
export const formValidateGene = (key, msg) => (rule, value, cb) => {
    if (Validates[key](value)) {
        cb()
    } else {
        cb(new Error(msg))
    }
}

The formValidateGene function above accepts two parameters. The first is the validation rule, which is the method name of the general validation rule extracted from the src/utils/validates.js file. The second parameter is the prompt information for form validation when an error is reported.

<template>
    <el-form ref="ruleForm"
             label-width="100px"
             class="demo-ruleForm"
             :rules="rules"
             :model="ruleForm">
        
        <el-form-item label="User name" prop="username">
            <el-input v-model="ruleForm.username"></el-input>
        </el-form-item>
        
        <el-form-item label="Cell-phone number" prop="mobile">
            <el-input v-model="ruleForm.mobile"></el-input>
        </el-form-item>
        
        <el-form-item label="mailbox" prop="email">
            <el-input v-model="ruleForm.email"></el-input>
        </el-form-item>
    </el-form>
</template>

<script type='text/javascript'>
    import * as Utils from '../utils'
    
    export default {
        name: 'ElTableDemo',
        data() {
            return {
                ruleForm: { pass: '', checkPass: '', age: '' },
                rules: {
                    username: [{
                        validator: Utils.formValidateGene('validateUsername', 'The name is given by 2.-10 Bit Chinese Character Composition'),
                        trigger: 'blur'
                    }],
                    mobile: [{
                        validator: Utils.formValidateGene('validateMobile', 'The mobile phone number consists of 11 digits starting with 1.'),
                        trigger: 'blur'
                    }],
                    email: [{
                        validator: Utils.formValidateGene('validateEmail', 'Incorrect mailbox format'),
                        trigger: 'blur'
                    }]
                }
            }
        }
    }
</script>

It can be seen that it is very convenient to use the form validation method as a strategy, and the form validation method is dynamically selected by using the Curitization method, so that the strategy can be used flexibly and the development efficiency can be greatly accelerated.

Examples of code can be consulted Codsandbox - Policy Mode Form Verification Practice

Operation results:

5. Advantages and disadvantages of strategy model

The strategy pattern splits the implementation and use of the algorithm, which brings many advantages:

  1. Policies are independent of each other, but they can be switched freely. The characteristics of this strategy mode bring a lot of flexibility to the strategy mode, and also improve the reuse rate of the strategy.
  2. If the strategy mode is not used, the multiple condition judgment will be used in the selection of the strategy. The strategy mode can avoid the multiple condition judgment and increase the maintainability.
  3. The scalability is good, and the strategy can be easily extended.

Disadvantages of the strategy model:

  1. Strategies are independent of each other, so some complex algorithmic logic can not be shared, resulting in some waste of resources.
  2. If users want to adopt any strategy, they must understand the implementation of the strategy, so all the strategies need to be exposed, which is contrary to the Dimit rule/the principle of minimum knowledge, and also increases the cost of users to use the strategy objects.

6. Scenarios for the application of policy patterns

In what scenarios should we use the policy pattern?

  1. Many algorithms only have slightly different scenarios in their behavior, so we can use the strategy pattern to dynamically select the algorithm.
  2. The algorithm needs the scene of free switching.
  3. Sometimes multiple conditional judgment is needed, so the strategy pattern can be used to avoid the situation of multiple conditional judgment.

7. Other relevant models

7.1 Policy Model and Template Method Model

Policy pattern and template method pattern have similar functions, but their structure and implementation are somewhat different.

  1. The policy pattern lets us dynamically specify the algorithm to be used while the program is running.
  2. Template method pattern determines the algorithm used when the subclass is defined.

7.2 Strategic Model and Hedonic Model

See the introduction in the Hedonic Model.

Most of the posts on the Internet are different in depth, even some contradictions. The following articles are summaries of the learning process. If you find errors, you are welcome to leave a message pointing out.~

This is my column. <JavaScript Design Patterns Essential> Interested students can click on the link to see more articles.

PS: Welcome to my public number, Front End Afternoon Tea. Come on.~

In addition, you can join the "Front End Afternoon Tea Exchange Group" Wechat Group. Long press to identify the two-dimensional code below can add my friends, notes add group, I will pull you into the group to

Keywords: Javascript Mobile REST Vue

Added by seany123 on Wed, 21 Aug 2019 05:29:20 +0300