On the use of strategy patterns in JavaScript:

  • What are design patterns
  • What are strategic patterns
  • Application of Policy Pattern in JavaScript (using policy pattern to encapsulate Baidu AI recognition calls)
  • Application of Policy Pattern in Vue Component Encapsulation (Using Policy Pattern to encapsulate Select Component)

What are design patterns

Consider an electronics enthusiast who, despite his lack of formal training, has accumulated over time to design and build a number of useful electronic devices: ham radios, Geiger counters, alarms, etc. One day the enthusiast decided to go back to school for a degree in electronics to get formal recognition for his talents. As the course unfolded, the hobbyist suddenly realized that everything was familiar. What is familiar is not the terminology or the way it is expressed, but the concept behind it. The hobbyist kept learning names and principles which he did not know, but which he had been using for years. The whole process was just one Epiphany after another.

Meditations on Design Patterns, John Vlissides, Chapter 1, section 1.2

We must have encountered many similar scenarios when writing code. With the increase of experience, we are more and more comfortable in dealing with these common scenes, and even summarize the targeted “routines”. Next time we encounter such problems, we can directly use the “routines” to solve them, saving both worry and effort. These “tropes” that accumulate over the course of software development are design patterns.

One of the goals of design patterns is to improve code reusability, extensibility, and maintainability. Because of this, sometimes we don’t know about a design pattern, but when we read a book or article about it, it’s like, “Oh, that’s a design pattern!”

If you feel that way after reading this article, you’re well on your way to becoming a productive programmer.

What are strategic patterns

Strategy pattern is a simple but commonly used design pattern, and its application scenarios are very wide. Let’s take a look at the concept of policy patterns and use code examples to make it clearer.

A policy pattern consists of two parts: one is a policy group that encapsulates different policies, and the other is a Context. Make the Context reusable, extensible, and maintainable by combining and delegating the ability to enforce policies and avoid a lot of copy-and-paste work.

A typical application of policy pattern is encapsulation of validation rules in form validation. Let’s take a look at a simple example:

Crude form validation

A common login form code is as follows:

<form id='login-form' action="" method="post">
    <label for="account">Mobile phone no.</label>
    <input type="number" id="account" name="account">
    <label for="password">password</label>
    <input type="password" id="password" name="password">
    <button id='login'>The login</button>
</form>
<script>
    var loginForm = document.getElementById('login-form');

    loginForm.onsubmit = function (e) {
        e.preventDefault();  
        var account = document.getElementById("account").value;
        var pwd = document.getElementById("password").value;

        if(account===null||account===' '){
            alert('Mobile phone number cannot be empty');return false;
        }
        if(pwd===null||pwd===' '){
            alert('Password cannot be empty');return false;
        }
        if (!/ (a ^ 1 [3 4 5 7 | | | | 8] [0-9] {9} $) /.test(account)) {
            alert('Mobile phone number format error');return false;
        }
        if(pwd.length<6){
            alert('Password must be no less than six characters');return false;
        }
        // Ajax sends the request
    }
</script>
Copy the code

The above code, while functional, but also obvious shortcomings:

The code is littered with if statements, and they’re inelastic: Every time we add one or change the validation rule, we have to change the code inside loginForm. Onsubmit. The logic is also poorly reusable: if there are other forms that use the same rules, the code cannot be reused, only copied. When the verification rule changes, such as the previous regular verification does not match the virtual carrier 14/17 segment, we need to manually synchronize multiple code changes (Ctrl+C/Ctrl+V).

Good form validation

Next, we will rewrite the previous code by using the policy pattern thinking. First, we will extract and encapsulate the verification logic as the policy group:

var strategies = {
    isNonEmpty: function (value, errorMsg) {
        if (value === ' ' || value === null) {
            returnerrorMsg; }},isMobile: function (value, errorMsg) { // Mobile phone number format
        if (!/ (a ^ 1 [3 4 5 7 | | | | 8] [0-9] {9} $) /.test(value)) {
            returnerrorMsg; }},minLength: function (value, length, errorMsg) {
        if (value.length < length) {
            returnerrorMsg; }}};Copy the code

Next modify the Context:

var loginForm = document.getElementById('login-form');

loginForm.onsubmit = function (e) {
    e.preventDefault(); 
    var accountIsMobile = strategies.isMobile(account,'Mobile phone number format error');
    var pwdMinLength = strategies.minLength(pwd,8.'Password must be no less than 8 characters');
    var errorMsg = accountIsMobile||pwdMinLength; 
    if(errorMsg){
        alert(errorMsg);
        return false; }}Copy the code

Comparing the two implementations, we can see that code that has separated the validation logic can be used by adding new definitions to the policy group if it needs to extend the validation type. If you need to modify the verification implementation, directly modify the corresponding policy to take effect globally. There are significant efficiency improvements for development and maintenance.

Extension: Epic form validation

Async-validator is an epic form validator encapsulated by Element-UI and ANTD

Through the comparison of form verification, I believe that everyone has some understanding of the policy mode, so let’s take two examples to understand the application of the policy mode in JavaScript:

Use strategy mode to call Baidu AI image recognition

Because the interface type of Baidu AI image recognition is different, the required parameter format is not the same. However, image compression and upload, error handling and other parts are common. Therefore, policy mode encapsulation can be adopted:

Defining a Policy Group

Policy groups are defined to encapsulate different interfaces and their parameters: for example, side field of ID card identification interface, templateSign field of custom identification, and poparamstData as the receiving parameter of driving pass identification.

/** * Policy group * IDCARD: ID identification * CUSTOMIZED: VL: ID identification */
var strategies = {
    IDCARD: function (base64) {
        return {
            path: 'idcard'.param: {
                'side': 'front'.'base64': base64
            }
        };
    },

    CUSTOMIZED: function (base64) {
        return {
            path: 'customized'.param: {
                'templateSign': '52cc2d402155xxxx'.'base64': base64
            }
        };
    },
    VL: function (base64) {
        return {
            path: 'vehicled'.poparamstData: {
                'base64': base64 } }; }};Copy the code

Defines the Context

var ImageReader = function () {};/** * get the result **@param {*} Type Indicates the file type *@param {*} Base64 Indicates the base64 code * of the file to be identified@param {*} CallBack identifies the result callBack */
ImageReader.prototype.getOcrResult = function (type, base64, callBack) {
    let fileSize = (base64.length / (1024 * 1024)).toFixed(2);
    let compressedBase64 = ' ';
    let image = new Image();
    image.src = base64;
    image.onload = function () {
        /** ** picture compression processing and exception processing, code omitted */
         

        let postData = strategies[type](compressedBase64);

        ajax(
            host + postData.path, {
                data: postData.param,
                type: 'POST'.headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                },
                success: function (res) {
                    var data = JSON.parse(res);
                    // The unified error code exposed to the UI layer
                    if(data.error_code ! = =undefined&& data.error_code ! = =0) {
                        var errorData = {
                            error: 1.title: 'wrong' + data.error_code,
                            content: 'error message'
                        };
                        callBack(errorData);
                    } else{ callBack(data); }}}); }; };Copy the code

Call way

var imageReader = new ImageReader();
imageReader.getOcrResult('IDCARD'.this.result.toString(), callback);
Copy the code

Encapsulate the Vue Select component with the policy pattern

The Element-UI select component is used in several places in a project, with similar internal logic in that it gets the data source for the drop-down list at initialization and dispatches different actions when an item is selected. Therefore, the use of strategic mode encapsulation is considered.

Context

In this case, the component exposes a prop externally, which the caller specifies to load a different policy. Define the Context as follows:

<template>
  <el-select v-model="selectedValue" placeholder="Please select" @change="optionChanged" size="mini" clearable>
    <el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id">
    </el-option>
  </el-select>
</template>
Copy the code
data() {
    return {
      selectedValue: undefined.options: [].action: ""}; },props: {
    // Expose to external select-type
    selectType: {
      type: String}},created() {
   / / get the options
   this.valuation();
  },
    methods: {
    optionChanged() {
      this.$emit(this.action, this.selectedValue);
    },
    setOptions(option) {
      this.$store.dispatch(this.action, option);
    },
    valuation() {
      // Get options data}},Copy the code

The external calls the component as follows:

<MySelect selectType="product"/>
Copy the code

strategies

Then define the policy group:

let strategies = {
    source: {
        action: "sourceOption".getOptions:  function() {
            / / pull options}},product: {
        action: "productOption".getOptions:  function() {
            / / pull options}},... }Copy the code

asynchronous

At this point, the basic structure of the component is clear, but there is a problem: when the component is loaded, the options are pulled asynchronously, and when the page is initialized, it is possible that the options are not returned, causing the options of the select to remain empty. So you should change the code here and get options synchronously:

// Modify the policy group
source: {
    action: "sourceOption".getOptions: async function() {
        // await pull options}},// Component modification
methods: {...async valuation(){... }}Copy the code

Continue to optimize

However, we do not need to pull options every time we load a component. If these options are used in other components or pages, we can consider storing them in VUex.

The original idea was to define a high-order component, that is, to define a wrapped SELECT template that extends its data source and action (the variable part) in a high-order component way. However, this idea was not so vUE (mainly slots is not easy to deal with), so we considered the policy pattern to rewrite this component

conclusion

From the above two examples, we can see:

  • The strategic pattern conforms to the open-closed principle
  • If you have to write a lot of codeif-elseStatement, then consider using the policy pattern
  • Consider the policy pattern if the only difference between multiple components (classes) is their behavior

Refer to JavaScript design patterns and development practices (explored) in Chapter 5