I have been reading Javascript design patterns recently and want to write articles to increase my understanding of strategy patterns and record my learning experience. I hope you can give a thumbs-up to show your support.

Definition of policy patterns

The definition of a strategy pattern is to define a series of algorithms, encapsulate them one by one, and make them interchangeable.

Every time I encounter the definition of this design pattern, I feel confused and confused at first glance. In fact, there is a way: quality three even ask. We can break down the definition and analyze the meaning of each field. Together, we can understand what the definition really means.

Therefore, for the definition of strategic mode, we can ask a wave of three qualities:

  1. What is the algorithm here
  2. Why do we need to wrap them up
  3. Student: Substituting with each other

Before answering the three questions of quality, we can start from life to see the application scenarios of strategy mode. In fact, there are many scenarios where we can use different strategies to solve the problem.

tourism

  1. If you’re rich or on a tight schedule, you can take a plane
  2. If you are a small bourgeoisie and not in a hurry, you can choose to take the bullet train
  3. If you travel on a budget, you can ride a bike and so on

The compressed file

  1. Zip algorithm
  2. Gzip algorithm

As you can see, our strategy is actually a way to solve our problem, which we can define as an algorithm.

Application of policy patterns

So much said, in fact, everyone is most concerned about the application scenario of the strategy pattern. Next, we will use the example of annual bonus to solve our quality three questions step by step. The year-end bonus is based on the employee’s salary base and year-end performance. For example, the annual bonus for performance S is 4 times salary, the annual bonus for performance A is 3 times salary, and the annual bonus for performance B is only 2 times salary. This logic can be implemented in basic code

var calculateBonus = function (performanceLevel, salary) {
    if (performanceLevel === 'S') {
        return salary * 4;
    };
    if (performanceLevel === 'A') {
        return salary * 3;
    };
    if (performanceLevel === 'B') {
        returnsalary * 2; }; } // Get employee calculateBonus with performance of B ('B', 20000); // Get employee calculateBonus with S performance ('S', 6000);
Copy the code

Obviously, this code does what we want, but it is limited and has the following shortcomings

  1. Contains too many if-else statements, making the function too large
  2. The calculateBonus function is inelastic and needs to change its internal implementation if different performance levels need to be added, which violates the open-closed principle
  3. The algorithm has poor reusability. If this calculation rule is needed elsewhere, it can only be re-output (copy and paste).

Optimization one (combination function)

We can reconstruct this code by combining functions, encapsulating various algorithms (i.e., the calculation rules of year-end bonus) into independent small functions and giving them good names. Then we can solve the problem of algorithm reuse mentioned above. The following code

var performanceS = function (salary) {
    return salary * 4;
};
var performanceA = function (salary) {
    return salary * 3;
};
var performanceB = function (salary) {
    return salary * 2;
};

var calculateBonus = function ( performanceLevel, salary) {
    if ( performanceLevel === 'S' ) {
        return performanceS( salary );
    };
    if ( performanceLevel === 'A' ) {
        return performanceA( salary );
    };
    if ( performanceLevel === 'B' ) {
        return performanceB( salary );
    };
}

calculateBonus( 'A', 10000);Copy the code

By combining functions, we can see that our algorithm is encapsulated into small functions, which can solve the problem of function reuse. However, our core problem, namely, the above shortcomings 1 and 2, have not been solved, so we continue to improve and optimize through the strategy mode this time.

Optimization 2 (Strategic Pattern)

The first step is to understand that separating what is constant from what is changing is the theme of every design pattern, and that the purpose of a strategy pattern is to separate the use of an algorithm from its implementation. In this example, the method of using the algorithm is unchanged. The amount of bonus is calculated according to an algorithm, but the implementation of the algorithm is variable, such as the implementation of performance S and A.

Composition of the strategy pattern:

  1. The policy class encapsulates the specific algorithm (the calculation method of performance) and is responsible for the specific calculation process.
  2. Context, which receives a request from a client and delegates the request to a policy class.
  3. Bridge, a Context in which a reference to a policy object is maintained
// Strategy class (S) var performanceS =function() {} / / algorithm S internal implementation performanceS. The prototype. Calculate =function ( salary ) {
    returnsalary * 4; } // Performance class (A) var performanceA =function() {} / / algorithm within A specific implementation performanceA. Prototype. Calculate =function ( salary ) {
    returnsalary * 3; } // performanceB = performanceBfunction() {} / / internal concrete realization algorithm B performanceB. Prototype. Calculate =function ( salary ) {
    return salary * 2;
}
Copy the code

Next, define the environment class, in this case our Bonus class

var Bonus = function() { this.salary = null; // This. Strategy = null; / / performance corresponding policy object} Bonus. The prototype. SetSalary =function(salary) { this.salary = salary; // Set the original salary}; Bonus.portotype.setStrategy =function(strategy) { this.strategy = strategy; // Set the policy object corresponding to the employee performance level} Bonus.prototype.getBonus =function() {// get the bonus amount // maintain the reference to the policy objectreturnthis.strategy.calculate( this.salary ); // Delegate to the corresponding policy object}Copy the code

Call to the bonus class

var bonus = new Bonus(); bonus.setSalary( 10000 ); bonus.setStrategy( new performanceS() ); // Set the policy object console.log(bonus. GetBonus ()); // Delegates the request to the previously saved policy objectCopy the code

The above example shows the application of the policy pattern, which makes the code clearer, the responsibility of each class more distinct, and also solves the disadvantages of the above ordinary function call. The implementation of this class is modeled after a traditional object-oriented language, so we can further optimize this code into a JavaScript version of the strategy pattern

Optimization three (JavaScript Versioning strategy mode)

Why is the JavaScript version of the strategy pattern different from the traditional object-oriented language strategy pattern? In fact, in JavaScript, functions are also objects, so you can define the Strategy class directly as a function. The code is as follows:

Var strategies = {// a series of algorithms"S" : function ( salary ) {
        return salary * 4;
    },
    "A" : function ( salary ) {
        return salary * 3;
    },
    "B" : function ( salary ) {
        returnsalary * 2; }};Copy the code

Similarly, we can accept user requests directly by using the calculateBonus function as the Context, without the Need for the Bonus class

var calculateBonus = function ( level, salary) {
    return strategies[ level ]( salary );    
}

console.log( calculateBonus('S', 20000));Copy the code

From the above examples, we have actually answered the three questions of quality in the definition of strategic pattern: what does the algorithm of strategic pattern refer to (the calculation method of performance), why encapsulation (reusable), and what does mutual substitution mean (performance can change, but it does not affect the function call, just change the parameters).

extending

While the concept of algorithms in policy patterns has been defined above, in practice we can often spread the meaning of algorithms so that policy patterns can also be used to encapsulate a set of ‘business rules’. As long as these business rules point to the same goal and can be used interchangeably, we can encapsulate them with a policy pattern and the validation of forms in our ‘business rules’ conforms to the policy pattern we use.

Form validation

Suppose we are writing a registration page with the following validation rules before clicking the button:

  1. The user name cannot be empty
  2. The password must contain at least six characters
  3. The mobile phone number must conform to the format

With this requirement, before we introduced the policy pattern, we could write the following code

<html>
    <body>
        <form action='xxx.com' id='registerForm' method='post'> Please enter the user name: <inputtype='text' name='userName'/ > Please enter the password: <inputtype='text' name='password'/ > Please enter the mobile phone number: <inputtype='text' name='phoneNumber'/ > <button> Submit </button> </form> <script> var registerForm = document.getelementbyId ('registerForm');
            
            registerForm.onsubmit = function () {
                if ( registerForm.userName.value === ' ') {
                    alert('User name cannot be empty');
                    return false;
                }
                if (registerForm.password.value.length < 6) {
                    alert('Password length must not be less than 6 characters');
                    return false;
                }
                if(! /(^1[3|5|8][0-9]{9}$/.test(registerForm.phoneNumber.value))) { alert('Incorrect format of mobile number');
                  return false;
                }
            }
        </script>
    </body>
</html>
Copy the code

Such code also has the same disadvantages as the year-end bonus mentioned above, the function is too large, inflexible, and poor reuse, so we have learned the strategic pattern, and definitely need to optimize this situation

Optimization of a

  1. Make it clear what the algorithm is in this scenario, and it is clear that the algorithm here refers to the business rules for our form validation logic. So we can encapsulate these business rules into corresponding policy objects:
var strategies = {
    isNonEmpty: function ( value, errorMsg) {
        if ( value === ' ') {
            return errorMsg;
        }
    },
    minLength: function ( value, length, errorMsg ) {
        if ( value.length < length ) {
            return errorMsg
        }
    },
    isMobile: function ( value, errorMsg) {
        if ( !/(^1[3|5|8][0-9]{9}$)/.test( value )) {
            returnerrorMsg; }}]Copy the code

Having implemented the algorithm for the policy object, we also need an environment class that accepts the user’s request and delegates it to the Strategy object. But before we can do that, we need to understand how the environment class directly Bridges the policy object, in other words, how the user sends requests to the Validator class. This makes it easy to implement the environment class, the Validator class here. Here is the code for our user to send a request to the Validator class:

var validataFunc = functionVar validator = new validator (); // Add (registerform.username, registerform.name, registerform.name, registerform.name, registerform.name)'isNonEmpty'.'User name cannot be empty');
    validator.add( registerForm.password, 'minLength:6'.'Password length must not be less than 6 characters');
    validator.add( registerForm.phoneNumber, 'isMobile'.'Incorrect format of mobile number'); var errorMsg = validator.start(); // Return the verification resultreturn errorMsg;
}
var registerForm = document.getElementById('registerForm');
registerForm.onsubmit = function() { var errorMsg = validataFunc(); // If yes, the verification failsif ( errorMsg ) {
        alert( errorMsg );
        return false; // Block form submission}}Copy the code

In our Validator, we have add, which adds validation rules, and start, which starts our Validator. If there is an error, So we return an error message (errorMsg) and with the policy object and the bridge between the policy object and the environment class (Validator), we can write our Validator class code

var validator = function() { this.cache = []; // Save the validation rule}; // Add the validation rule function validator.prototype.add =function(dom, rule, errorMsg) {// Separate strategy from parameters'minLength:6''minLength:6'- > ["minLength"."6"]
    var ary = rule.split(':'); 
    this.cache.push ( function() { var strategy = ary.shift(); // User selected strategy ["minLength"."6"] - >'minLength'ary.unshift( dom.value ); // Add the input value to the argument list ary.push(errorMsg); // Add errorMsg to the parameter listreturnstrategies[ strategy ].apply( dom, ary ); / / entrust policy object call})} / / inspection begin the Validator () function. The prototype. Start =function () {
    for( var i = 0,validatorFunc; validatorFunc = this.cache[i++];) { var msg = validatorFunc(); // Start the verification and get the return information after the verificationif(MSG) {// If MSG exists, the verification failsreturnmsg; }}}Copy the code

In the above, we did our form validation by abstracting the algorithm from the business rules and using the policy pattern. When we changed a validation rule, we only had to change a small amount of code. If we want to change the user name to at least 4 characters, we just need to change our minLength:6 to minLength:4

Optimization 2 (Multiple validation rules)

In fact, so far, our understanding of the policy pattern and the basic concepts of application have been explained by the above examples, but there is a small flaw in the form validation we have implemented so far, that is, we only have one validation rule for each text input field. So if we want to add multiple validation rules, we can add them in the following way:

validator.add( registerForm.userName, [{
    strategy: 'isNonEmpty',
    errorMsg: 'User name cannot be empty'
},{
    strategy: 'minLength:10',
    errorMsg: 'Username length cannot be less than 10 characters'
}])
Copy the code

We can modify the Add method in our Validator to add our validators to the cache by iterating through them.

validator.prototype.add = function (dom, rules) {
    var self = this;
    
    for(var i = 0,rule; rule = rules[i++];) {(function ( rule ) {
            var strategyAry = rule.strategy.split( ':' );
            var errorMsg = rule.errorMsg;
            
            self.cache.push( function () {
                var strategy = strategyAry.shift();
                strategyAry.unshift( dom.value );
                strategyAry.push( errorMsg );
                return strategies[ strategy ].apply( dom, strategyAry )
            })
        })( rule )
    }
};
Copy the code

Advantages and disadvantages of strategic patterns

From the above examples, it is clear that the advantages of the strategic pattern can be summarized

  1. The techniques and ideas of combination, delegation and polymorphism are used to avoid multiple conditional selection statements
  2. The open-closed principle is adopted to encapsulate the algorithm in an independent strategy, which is easy to understand, switch and expand
  3. Algorithms in the policy pattern can be reused to avoid copy-and-paste in many places

At the same time, the strategic pattern also has its disadvantages, but it does not affect our use of the strategic pattern

  1. In policy mode, we will add many policy classes, policy objects
  2. To use strategic patterns, we must understand all strategies and understand the differences between each strategy in order to choose a suitable strategy.

conclusion

After you read it, if you think there is something wrong, please make suggestions. Also hope that if this article is helpful to you, please give a lot of praise, forward support!

This article is based on: JavaScript Design Pattern and Development Practice by Zeng Tan