[JS design pattern]: strategy pattern and application – Implementation of bonus calculation and form verification (5)

Time:2020-12-29

introduce

The meaning of strategy pattern is to define a series of algorithms, encapsulate them one by one, and make them replaceable. This mode allows algorithm changes not to affect customers who use the algorithm.

realization

For example, data validity verification is usually implemented by swich or if statement. If there are too many verification rules, the scalability and maintainability of the code will be poor, and unit testing will become more and more complex. The code is as follows:

var validator = {
    validate: function(value,type) {
        switch(type) {
            case 'isNonEmpty':
                return true
            case 'isNumber':
                return true;
            case 'isAlphaNum':
                return true;
            default:
                return true;
        }
    }
}

alert(validator.validate('123','isNonEmpty'))

How to avoid the disadvantages of the above code? We can use the policy mode to encapsulate the same working code into different verification classes. We only need to call different verification class methods (that is, different algorithms) by passing different names. The implementation code is as follows:

var validator = {
    Types: {// store validation rules
        isNonEmpty: {
            validate: function(value) {
                return value !== ''
            },
            Instructions: 'the value passed in cannot be empty'
        },
        isNumber: {
            validate: function(value) {
                return !isNaN(value);
            },
            Instructions: 'the value passed in is not a number'
        },
        isAlphaNum: {
            validate: function(value) {
                return !/[^a-z0-9]/i.test(value)
            },
            Instructions: 'the values passed in can only be numbers or letters, not special characters'
        }
    }, 
    Config: {}, // type to verify
    Messages: [], // store error messages
    Validate: function (data) {// the data passed in is a key value pair
        var i, type, checker, resultOk;
        this.messages  =[]; // clear the error message first
        for(i in data) {
            If ( data.hasOwnProperty (i) ) {// judge that I is not an attribute on the prototype
                type =  this.config [i] ; // get the validation type
                If (! Type) {// skip without current check type
                    continue;
                }
                checker =  this.types [type]; // get the validation class method of the validation rule
                If (! Checker) {// verification rule class does not exist, throw an exception directly
                    throw {
                        name: "ValidationError",
                        message: "No handler to validate type " + type
                    }
                }
                resultOk = checker.validate(data[i]);
                If (! Resultok) {// verification failed
                    this.messages.push(checker.instructions);
                }
            }
        }
        return this.hasErrors();
    },
    hasErrors: function() {
        return this.messages.length !== 0;
    }
    
}

The usage is as follows:

var data = {
    firstName: '',
    lasName: 'shu',
    age: '',
    userName: 'tom shu'
}
validator.config = {
    firstName: 'isNonEmpty',
    age: 'isNumber',
    userName: 'isAlphaNum'
}

validator.validate(data);
if(validator.hasErrors()) {
    console.log(validator.messages.join("\n"));
}
//Results: the results were as follows
//The value passed in cannot be empty
//The value passed in can only be a number or letter, not a special character

Examples of other policy patterns

Animate method used in jquery

$(DIV). Animal ({"left: 200px"}, 1000, 'linear'); // uniform motion
$(DIV). Animal ({"left: 200px"}, 1000, 'cubic'); // cubic slow motion

Linear and cubic are the encapsulation of strategy mode

Calculate bonus

For example, the company’s year-end bonus is assessed according to the salary and performance of employees. For people with performance a, the year-end bonus is 4 times of the salary, for people with performance B, the year-end bonus is 3 times of the salary, and for people with performance C, the year-end bonus is 2 times of the salary. Now we use the general coding method to write the code as follows:

var calculateBouns = function(salary,level) {
    if(level === 'A') {
        return salary * 4;
    }
    if(level === 'B') {
        return salary * 3;
    }
    if(level === 'C') {
        return salary * 2;
    }
};
//The call is as follows:
console.log(calculateBouns(4000,'A')); // 16000
console.log(calculateBouns(2500,'B')); // 7500

Disadvantages: the function contains a lot of if statements, which is lack of flexibility, and the reusability of the algorithm is poor. If there are similar algorithms in other places, but the rules are not the same, these codes cannot be used universally.

The code of using policy mode is as follows:

//The code is as follows:
var obj = {
        "A": function(salary) {
            return salary * 4;
        },
        "B" : function(salary) {
            return salary * 3;
        },
        "C" : function(salary) {
            return salary * 2;
        } 
};
var calculateBouns =function(level,salary) {
    return obj[level](salary);
};
console.log(calculateBouns('A',10000)); // 40000

Policy pattern not only encapsulates algorithms, but also encapsulates a series of business rules,As long as the goals of these business rules are consistent, we can use policy patterns to encapsulate them

Form verification

For example, the registration page needs to verify the user name, password, mobile phone number and other rules. The verification rules are as follows:

  • The user name cannot be empty;
  • The password cannot be less than 6 digits;
  • The mobile phone number conforms to the regular rules of mobile phone

The HTML code is as follows:

Please enter the user name:
        
    
    
        Please input a password:
        
    
    
        Please enter mobile number:
        
    
    
        Submit

The validation method of submitvalidate is as follows:

function submitValidate() {
    var registerForm = document.getElementById("registerForm");
    if(registerForm.userName.value === '') {
            Alert ('user name cannot be empty ');
            return false;
        }
    else if(registerForm.password.value.length < 6) {
        Alert ("password length cannot be less than 6 digits");
        return false;
    }
    else if(!/(^1[0-9]{10}$)/.test(registerForm.phoneNumber.value)) {
        Alert ("incorrect format of mobile phone number");
        return false;
    }
    return true;
}

Disadvantages:

  • The if else if code in the submitvalidate function will gradually increase according to the validation items;
  • The submitvalidate function is inflexible. If adding a new verification rule is to add an else if statement, but if modifying the original verification rule, the code in the function needs to be changed, which violates the open close principle;
  • The reusability of the algorithm is poor, if other pages also need to use similar verification, then this method can not be shared, it can also copy the code.

Next, we use the policy pattern to refactor the above code.

The first step is to encapsulate the policy object, that is, to verify different algorithms. The code is as follows:

var strategys = {
    isNotEmpty: function(value,errorMsg) {
        if(value === '') {
            return errorMsg;
        }
    },
    //Limit minimum length
    minLength: function(value,length,errorMsg) {
        if(value.length < length) {
            return errorMsg;
        }
    },
    //Mobile phone number format
    mobileFormat: function(value,errorMsg) {
        if(!/(^1[0-9]{10}$)/.test(value)) {
            return errorMsg;
        }
    }
}

The second step is to implement the validator class, which is used as the context to receive the user’s request and delegate it to the strategy object.

In common words, it is to add some rules that need to be verified in the form and obtain the verification results (whether the verification passed or not). The code is as follows:

function Validator() {
    this.cache  =[]; // save validation rules
}

Validator.prototype = {
    constructor: Validator,
    add: function(dom,rule,errorMsg) {
        var str =  rule.split (":"); //  minLength:6 The scene of
        var fn = function() {
            var strategyType =  str.shift (); // delete the first element in the str array and return it, that is, the function name of the verification rule
            str.unshift ( dom.value ); // insert the value value into the first bit of the array str
            str.push(errorMsg);
            return strategys[strategyType].apply(dom,str);
        }
        this.cache.push(fn);
    },
    start: function() {
        for(var i = 0, fn; fn = this.cache[i++];) {
            var msg = fn();
            if(msg) {
                return msg;
            }
        }
    }
}

Call method:

function submitValidate() {
    var registerForm = document.getElementById("registerForm");
    var validator = new Validator();
    validator.add ( registerForm.userName ,'isnotempty ','user name cannot be empty');
    validator.add ( registerForm.password ' minLength:6 ','the length of the password cannot be less than 6 digits');
    validator.add ( registerForm.phoneNumber ,'mobileformat ','incorrect mobile number format');
    var resultMsg = validator.start();
    if(resultMsg) {
        alert(resultMsg);
        return false;
    }
    return true;
}

Complete JS code:

var strategys = {
    isNotEmpty: function(value,errorMsg) {
        if(value === '') {
            return errorMsg;
        }
    },
    //Limit minimum length
    minLength: function(value,length,errorMsg) {
        if(value.length < length) {
            return errorMsg;
        }
    },
    //Mobile phone number format
    mobileFormat: function(value,errorMsg) {
        if(!/(^1[0-9]{10}$)/.test(value)) {
            return errorMsg;
        }
    }
}

function Validator() {
    this.cache  =[]; // save validation rules
}

Validator.prototype = {
    constructor: Validator,
    add: function(dom,rule,errorMsg) {
        var str =  rule.split (":"); //  minLength:6 The scene of
        var fn = function() {
            var strategyType =  str.shift (); // delete the first element in the str array and return it, that is, the function name of the verification rule
            str.unshift ( dom.value ); // insert the value value into the first bit of the array str
            str.push(errorMsg);
            return strategys[strategyType].apply(dom,str);
        }
        this.cache.push(fn);
    },
    start: function() {
        for(var i = 0, fn; fn = this.cache[i++];) {
            var msg = fn();
            if(msg) {
                return msg;
            }
        }
    }
} 

function submitValidate() {
    var registerForm = document.getElementById("registerForm");
    var validator = new Validator();
    validator.add ( registerForm.userName ,'isnotempty ','user name cannot be empty');
    validator.add ( registerForm.password ' minLength:6 ','the length of the password cannot be less than 6 digits');
    validator.add ( registerForm.phoneNumber ,'mobileformat ','incorrect mobile number format');
    var resultMsg = validator.start();
    if(resultMsg) {
        alert(resultMsg);
        return false;
    }
    return true;
}

In the above code, we only implement binding one validation rule to a DOM element. What if we need to bind multiple validation rules?

For example, in the above code, we can only verify whether the input box is empty,validator.add ( registerForm.userName ,’isnotempty ‘,’user name cannot be empty’);However, if we want to verify whether the input box is empty and whether the length of the input box is not less than 10 bits, we expect to pass parameters as follows:

validator.add ( registerForm.userName , [{strategy: 'isnotempty', errormsg: 'user name cannot be empty'}, {strategy: ' minLength:10 ', errormsg:' user name length cannot be less than 10 '}])

We only need to modify the add method, as follows:

function Validator() {
    this.cache  =[]; // save validation rules
}

Validator.prototype = {
    constructor: Validator,
    add: function(dom,rules) {
        var self = this;
        for(var i = 0, len = rules.length; i < len; i++) {
            var rule = rules[i];
            (function(rule){
                var str =  rule.strategy.split (":"); //  minLength:6 The scene of
                var fn = function() {
                    var strategyType =  str.shift (); // delete the first element in the str array and return it, that is, the function name of the verification rule
                    str.unshift ( dom.value ); // insert the value value into the first bit of the array str
                    str.push(rule.errorMsg);
                    return strategys[strategyType].apply(dom,str);
                }
                self.cache.push(fn);
            })(rule)
        }
    },
    start: function() {
        for(var i = 0, fn; fn = this.cache[i++];) {
            var msg = fn();
            if(msg) {
                return msg;
            }
        }
    }
}

Change the call mode:

function submitValidate() {
    var registerForm = document.getElementById("registerForm");
    var validator = new Validator();
    validator.add(registerForm.userName, [{
        strategy: 'isNotEmpty',
        Errormsg: 'user name cannot be empty'
    }, {
        strategy: 'minLength:10',
        Errormsg: 'the length of user name cannot be less than 10 bits'
    }]);
    validator.add(registerForm.password, [{
        strategy: 'minLength:6',
        Errormsg: 'password length cannot be less than 6 bits'
    }]);
    validator.add(registerForm.phoneNumber, [{
        strategy: 'mobileFormat',
        Errormsg: 'incorrect format of mobile number'
    }]);
    var resultMsg = validator.start();
    if (resultMsg) {
        alert(resultMsg);
        return false;
    }
    return true;
}

The complete code is as follows:

var strategys = {
    isNotEmpty: function(value, errorMsg) {
        if (value === '') {
            return errorMsg;
        }
    },
    //Limit minimum length
    minLength: function(value, length, errorMsg) {
        if (value.length < length) {
            return errorMsg;
        }
    },
    //Mobile phone number format
    mobileFormat: function(value, errorMsg) {
        if (!/(^1[0-9]{10}$)/.test(value)) {
            return errorMsg;
        }
    }
}

function Validator() {
    this.cache  =[]; // save validation rules
}

Validator.prototype = {
    constructor: Validator,
    add: function(dom, rules) {
        var self = this;
        for (var i = 0, len = rules.length; i < len; i++) {
            var rule = rules[i];
            (function(rule) {
                var str =  rule.strategy.split (":"); //  minLength:6 The scene of
                var fn = function() {
                    var strategyType =  str.shift (); // delete the first element in the str array and return it, that is, the function name of the verification rule
                    str.unshift ( dom.value ); // insert the value value into the first bit of the array str
                    str.push(rule.errorMsg);
                    return strategys[strategyType].apply(dom, str);
                }
                self.cache.push(fn);
            })(rule)
        }
    },
    start: function() {
        for (var i = 0, fn; fn = this.cache[i++];) {
            var msg = fn();
            if (msg) {
                return msg;
            }
        }
    }
}

function submitValidate() {
    var registerForm = document.getElementById("registerForm");
    var validator = new Validator();
    validator.add(registerForm.userName, [{
        strategy: 'isNotEmpty',
        Errormsg: 'user name cannot be empty'
    }, {
        strategy: 'minLength:10',
        Errormsg: 'the length of user name cannot be less than 10 bits'
    }]);
    validator.add(registerForm.password, [{
        strategy: 'minLength:6',
        Errormsg: 'password length cannot be less than 6 bits'
    }]);
    validator.add(registerForm.phoneNumber, [{
        strategy: 'mobileFormat',
        Errormsg: 'incorrect format of mobile number'
    }]);
    var resultMsg = validator.start();
    if (resultMsg) {
        alert(resultMsg);
        return false;
    }
    return true;
}

Of course, we can also put all kinds of algorithms for verification on the constructor validator prototype, which will not be dealt with here.

For the article about form verification, please refer to:

summary

Advantages of strategy mode:

  • The strategy pattern uses the combination, delegation and other technologies and ideas to effectively avoid many if conditional statements.
  • The policy pattern provides the open close principle, which makes the code easier to understand and expand.
  • The code in the policy pattern can be reused.

reference resources

Recommended Today

How to use computer to make good handwriting signature?

Many friends hope to write good-looking signatures, but their font is not beautiful enough. Writing is not good, how to have a good-looking signature, the following small series for you to introduce how to use the computer to make a good-looking imitation handwriting signature method, hope you like. Method / step Software name: Chinese green […]