current position:Home>Design pattern -- strategy pattern

Design pattern -- strategy pattern

2021-08-27 00:14:43 Xiao Guo is in Shenzhen

1. Definition

The strategy pattern : Define a set of algorithms , Encapsulate them one by one , And make them interchangeable .

2. Use the strategy model to calculate bonus

The strategy pattern has a wide range of applications . Taking the calculation of year-end bonus as an example . for example , Performance is S The year-end bonus for the people of 4 Double salary , Performance is A The year-end bonus for the people of 3 Double salary , And performance is B Our year-end bonus is 2 Double salary . Provide a piece of code , To calculate the employee's year-end bonus .

2.1 The original code implementation

Write a project called calculateBonus To calculate everyone's bonus amount . Function takes two parameters : The employee's salary and his performance appraisal grade .

var calculateBonus = function(performanceLevel, salary){
    if (performanceLevel === 'S') {
        return salary * 4;
    }
    if (performanceLevel === 'A') {
        return salary * 3;
    }
    if (performanceLevel === 'B') {
        return salary * 2;
    }
}

calculateBonus('B', 20000) //  Output : 40000
calculateBonus('S', 6000)  //  Output : 24000
 Copy code 

This code is very simple , But there are obvious disadvantages .

  • calculateBonus The function is huge , It contains a lot of if-else sentence , These statements need to cover all logical branches .

  • calculateBonus The function lacks elasticity , If a new performance level is added C, Or want to put performance S The bonus factor is changed to 5, Then we must go deep calculateBonus Internal implementation of function , It's against openness - Closed principle .

  • The reusability of the algorithm is poor , What if you need to reuse these bonus algorithms elsewhere in the program ? Our choice is to copy and paste .

2.2 Use composite functions to refactor code

Encapsulate this algorithm into small functions , These small functions are well named , You can know at a glance which algorithm it corresponds to , They can also be reused elsewhere in the program .

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) //  Output : 30000
 Copy code 

The program has been improved , But very limited , Still not solved :calculateBonus Functions can get bigger and bigger , And it lacks flexibility when the system changes .

2.3 Refactoring code using policy patterns

The strategy pattern is to define a series of algorithms , Encapsulate them one by one . Separating the unchanging from the changing is the theme of every design pattern , The strategic model is no exception , The purpose of strategy pattern is to separate the use of algorithm from the implementation of algorithm .

In this case , The way the algorithm is used is the same , The calculated bonus amount is obtained according to a certain algorithm . The implementation of the algorithm is different and changing , Each performance corresponds to different calculation rules .

A program based on policy patterns consists of at least two parts . The first part is a set of policy classes , The strategy class encapsulates the specific algorithm , And responsible for the specific calculation process . The second part is the environment class Content, Content Accept the customer's request , Then delegate the request to a policy class . To do that , explain Content To maintain a reference to a policy object in .

Now refactor the above code with the policy pattern . The first version imitates the implementation in traditional object-oriented languages . First, the calculation rules of each performance are encapsulated in the corresponding policy class :

var performanceS = function(){};

performanceS.prototype.calculate = function(salary) {
    return salary * 4;
};

var performanceA = function(){};

performanceA.prototype.calculate = function(salary) {
    return salary * 3;
};

var performanceB = function(){};

performanceB.prototype.calculate = function(salary) {
    return salary * 2;
};

//  Next, define the bonus class Bonus:
var Bonus = function(){
    this.salary = null; // The original wage 
    this.strategy = null; // The strategic target corresponding to the performance level 
}

Bonus.prototype.setSalary = function(salary) {
    this.salary = salary; // Set the employee's original salary 
}

Bonus.prototype.setStrategy = function(strategy) {
    this.strategy = strategy; // Set the strategy object corresponding to the employee performance level 
}

Bonus.prototype.getBonus = function(){ // Get the bonus amount 
    if(!this.strategy) {
        throw new Error(' Not set strategy attribute ')
    }
    return this.strategy.calculate(this.salary) // Delegate the operation of bonus calculation to the corresponding strategic object 
}
 Copy code 

Before completing the final code , Review the idea of the strategic model : Define a set of algorithms , Encapsulate them one by one , And make them interchangeable .

If this sentence is more detailed , Namely : Define algorithms for some columns , Encapsulate them into policy classes , Algorithms are encapsulated in methods within the policy class . At the customer's right Context When making a request ,Context Always delegate the request to one of these policy objects for calculation .

Now let's finish the rest of the code in this example . So let's create one bonus object , And give bonus Object to set some original data , For example, the original salary of employees . Next, a policy object for calculating bonus is also passed into bonus The object is stored inside . When calling bonus.getBonus() When calculating the bonus ,bonus The object itself does not have the ability to calculate , Instead, the request is delegated to the previously saved policy object :

var bonus = new Bonus();

bonus.setSalary(10000);
bonus.setStrategy(new performanceS()); //  Set policy object 

console.log(bonus.getBonus());  Output 40000

bonus.setStrategy(new performanceA()); //  Set policy object 
console.log(bonus.getBonus());  Output 30000
 Copy code 

After refactoring through the policy pattern , The code becomes clearer , The responsibilities of each category are more distinct . But this code is based on the imitation of traditional object-oriented language .

3.JavaScript Version of the policy model

In the previous example ,strategy Objects are created from various policy classes , This is to simulate the implementation of some traditional object-oriented languages . In fact, JavaScript In language , Functions are also objects , So a simpler and more direct way is to put strategy Directly defined as a function :

var strategies = {
    "S": function(salary){
       return salary * 4;     
    },
    "A": function(salary){
       return salary * 3;     
    },
    "B": function(salary){
       return salary * 2;     
    }
}
 Copy code 

Again , Context There is no need to use Bonus Class , We still use calculateBonus Functions act as Context To accept the user's request .

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

console.log(calculateBonus('S', 20000)); // Output : 80000 
console.log(calculateBonus('A', 10000)); // Output : 30000 
 Copy code 

4. The embodiment of polymorphism in policy pattern

Refactoring code by using policy patterns , We eliminate a large number of conditional branch statements in the original program . All the logic related to the calculation of bonus is no longer put in Context in , Instead, it is distributed among various policy objects .Context No ability to calculate bonus , Instead, the responsibility is delegated to a policy object . The algorithm responsible for each policy object has been encapsulated in the object . When we issue... To these policy objects “ Calculate the bonus ” The request of , They will return different calculation results , This is the embodiment of object polymorphism , It's also “ They can replace each other ” Purpose . Replace Context Currently saved policy objects in , We can execute different algorithms to get the results we want .

5. Use strategy mode to realize jog animation

5.1 The principle of realizing animation effect

Play some original pictures with small gap in faster frames , To achieve visual animation . stay JavaScript in , You can change one of the elements continuously CSS attribute , such as :left、top、background-position To achieve animation .

5.2 Ideas and some preparatory work

The goal is : Write an animation class and some slow motion algorithms , Let the ball move in the page with various slow effects .

Ideas : Before the exercise starts , Information needed :

  • At the beginning of the animation , The original position of the ball ;
  • The target position where the ball moves
  • The exact point in time at the beginning of the animation
  • The duration of the ball movement

adopt setInterval Create a timer , Timer intervals 19ms Cycle time . In every frame of the timer , Take the time that animation has consumed 、 The original position of the ball 、 The information such as the target position of the ball and the total duration of the animation are transmitted to the slow motion algorithm . The algorithm will pass these parameters , Calculate the current position of the ball . Finally, update the div Corresponding CSS attribute , The ball can move smoothly .

5.3 Let the ball move

Slow motion algorithm : Accept 4 Parameters , The meanings are respectively : Time consumed by animation 、 The original position of the ball 、 The target position of the ball 、 The total duration of the animation , The returned value is the current position where the animation element should be .

var tween = {
    linear: function(t,b,c,d) {
        return c*t/d + b;
    },
    easeIn: function(t,b,c,d) {
        return c * (t/= d) * t + b;
    },
    strongEaseIn: function(t,b,c,d) {
        return c * (t/= d) * t * t * t * t+ b;
    },
    strongEaseOut: function(t,b,c,d) {
        return c * ((t = t/d - 1) * t * t * t * t + 1)+ b;
    },
    sineaseIn: function(t,b,c,d) {
        return c * (t/= d) * t * t + b;
    },
    sineaseOut: function(t,b,c,d) {
        return c * ((t= t/ d - 1) * t * t + 1) + b;
    },
}
 Copy code 

Complete code

First place a... In the page div:

<body>
    <div style="position:absolute;background:blue" id="div">  I am a div </div>
</body>
 Copy code 

So let's define Animate class ,Animate The constructor of takes a parameter : About to move dom node .

var Animate = function(dom) {
    this.dom = dom; //  Moving dom node 
    this.startTime = 0; // Start time of exercise 
    this.startPos = 0; // At the beginning of the animation ,dom Location of nodes , namely dom Initial position 
    this.endPos = 0; // At the end of the animation ,dom Location of nodes , namely dom Target location 
    this.propertyName = null; //dom Nodes need to be changed css Property name 
    this.easing = null; // Slow motion algorithm 
    this.duration = null; //  Animation duration 
}
 Copy code 

Next Animate.prototype.start Method is responsible for starting the animation , The moment the animation is started , To record some information , It is used by the slow motion algorithm to calculate the current position of the ball in the future . After recording this information , This method is also responsible for starting the timer .

Animate.prototype.start = function(propertyName, endPos, duration, easing) {
    this.startTime = +new Date; //  Animation start time 
    this.startPos = this.dom.getBoundingClientRect()[propertyName]; // dom Initial position of node 
    this.propertyName = propertyName; // dom Nodes need to be changed css Property name 
    this.endPos = endPos; // dom Node target location 
    this.duration = duration; // Animation duration 
    this.easing = tween[easing]; //  Slow motion algorithm 
    
    var self = this;
    var timeId = setInterval(function(){ //  Start timer , Start animating 
        if(self.step()===false){ // If the animation is over , Then clear the timer 
            clearInterval(timeId);
        }
    }, 19);
}
 Copy code 

Animate.prototype.start The method accepts the following 4 Parameters :

  • propertyName: To change CSS Property name , such as :'left'、'top', Move left and right and move up and down respectively .
  • endPos: The target position of the ball
  • duration: Animation duration
  • easing: Slow motion algorithm

Next up is Animate.prototype.step Method , This method represents what to do in each frame of the ball motion . Here, , This method is responsible for calculating the current position of the ball and calling the update CSS Method of attribute value Animate.prototype.update.

Animate.prototype.step = function(){
    var t = +new Date; //  Get the current time 
    if (t >= this.startTime + this.duration) {
        this.update(this.endPos); //  Update the ball CSS Property value 
        return false;
    }
    var pos = this.easing(t - this.startTime, this.startPos, this.endPos - this.startPos, this.duration);
    // pos Is the current position of the ball 
    this.update(pos); //  Update the ball CSS Property name 
}
 Copy code 

The current time is greater than the sum of the animation start time plus the animation duration , It means that the animation is over , At this point, correct the position of the ball . Because after the start of this frame , The position of the ball is close to the target position , But it may not be exactly equal to the target position . At this point, we should actively correct the current position of the ball as the final target position . In addition, let Animate.prototype.step Method returns false, You can inform Animate.prototype.start Method clear timer

Finally, it is responsible for updating the ball CSS Property value Animate.prototype.update Method :

Animate.prototype.update = function(pos) {
    this.dom.style[this.propertyName] = pos + 'px';
}

//  test 
var div = document.getElementById('div');
var animate = new Animate(div);

animate.start('left', 500, 1000, 'strongEaseOut');
 Copy code 

6. More broadly “ Algorithm ”

The strategy pattern is to define a series of algorithms , And encapsulate them . The previous examples of calculating bonus and slow motion animation encapsulate some algorithms .

By definition , The strategy pattern is used to encapsulate the algorithm . But if the strategy pattern is only used to encapsulate the algorithm , It's a bit overqualified . In actual development , It usually spreads the meaning of the algorithm , So that the policy pattern can also be used to encapsulate some columns “ Business rules ”. As long as these business rules point to the same goal , And can be replaced by , You can encapsulate them with a policy pattern .

7. Form verification

Use policy mode to complete form verification

Check logic

  • The username cannot be empty
  • Password length cannot be less than 6 position
  • The phone number must be in the format

7.1 The first version of the form validation

No policy pattern was introduced

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

This is a very common coding format , Its disadvantages are the same as the original version of bonus calculation .

  • registerForm.onsubmit The function is huge , It contains a lot of if-else sentence , These statements need to override all validation rules
  • registerForm.onsubmit The function lacks elasticity , If a new verification rule is added , Or you want to check the length of the password from 6 Change to 8, Must go deep registerForm.onsubmit Internal implementation of function , It's illegal to open - Closed principle
  • The reusability of the algorithm is poor , If you add another form to the program , This form also needs some similar checks , Then we may copy these verification logic everywhere .

7.2 Refactoring form validation with policy mode

First step : Encapsulate the verification logic into policy objects :

var strategies = {
    isNonEmpty: function(val, errorMsg) { //  Not empty 
        if (value === '') {
            return errorMsg;
        }
    },
    minLength: function(value, length, errorMsg) { //  Limit the minimum length 
        if(value.length < length) {
            return errorMsg;
        }
    },
    isMobile: function(value, errorMsg) {
        if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) { // Cell phone number format 
            return errorMsg
        }
    }
}
 Copy code 

Next, we'll implement Validator class .Validator Class here as Context, Responsible for receiving user requests and delegating them to strategy object . In the given Validator Class code , Let's first understand how users respond to Validator Class to send the request :

var validataFunc = function(){
    var validator = new Validator(); //  Create a validator object 
    
    //  Add some validation rules 
    validator.add(registerForm.userName, 'isNonEmpty', ' The username cannot be empty ');
    validator.add(registerForm.password, 'minLength:6', ' Password length cannot be less than 6 position ');
    validator.add(registerForm.phoneNumber, 'isMobile', ' Incorrect format of mobile phone number ');
    
    var errorMsg = validator.start(); //  Get the calibration results 
    return errorMsg; // Return verification result 
}

var registerForm = document.getElementById('registerForm');
registerForm.onsubmit = function(){
    var errorMsg = validataFunc(); //  If errorMsg There is an exact return value , Description failed the verification 
    if (errorMsg) {
        alert(errorMsg)
        return false; // Block form submission 
    }
}
 Copy code 

You can see that in this code , First create a validator object , And then through validator.add Method , Go to validator Add some validation rules to the object .validator.add Method acceptance 3 Parameters , Use the following code to illustrate :

 validator.add(registerForm.password, 'minLength:6', ' Password length cannot be less than 6 position ');
 Copy code 
  • registerForm.password For those involved in verification input Input box .
  • 'minLength:6' Is a string separated by colons . In front of the colon minLength Selected on behalf of customers strategy object , The number after the colon 6 Indicates some parameters necessary in the verification process .'minLength:6' It means checking registerForm.password This text input box is value The minimum length is 6. If the string does not contain a colon , It indicates that no additional parameter information is required in the verification process , such as ‘isNonEmpty’.
  • The first 3 The first parameter is the error message returned when the verification fails .

When we went to validator After adding a series of verification rules to the object , Would call validator.start() Method to start verification . If validator.start() Returns an exact errorMsg String as return value , This indicates that the verification failed , At this point, let registerForm.onsubmit Method returns false To prevent form submission .

And finally Validator The realization of the class :

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

Validator.prototype.add = function(dom, rule, errorMsg) {
    var ary = rule.split(':'); //  hold strategy Separate from parameters 
    this.cache.push(function(){ //  Wrap the verification steps with empty functions , And put in cache
        var strategy = ary.shift(); //  User selected strategy
        ary.unshift(dom.value); //  hold input Of value Add to parameter list 
        ary.push(errorMsg); //  hold errorMsg Add to parameter list 
        return strategies[strategy].apply(dom, ary);
    });
};

Validator.prototype.start = function(){
    for (var i=0, validatorFunc; validatorFunc = this.cache[i++];) {
        var msg = validatorFunc(); // Start checking , And get the returned information after verification 
        if (msg) { //  If there is an exact return value , Indicates that the verification failed 
            return msg;
        }
    }
}
 Copy code 

After refactoring the code using the policy pattern , Just passed “ To configure ” You can complete the verification of a form , These validation rules can also be reused anywhere in the program , It can also be in the form of plug-ins , It can be easily transplanted to other projects .

When modifying verification rules , Just write or rewrite a small amount of code . For example, if you want to change the verification rule of the user name input box to that the user name cannot be less than 10 Characters , The code is as follows

validator.add(registerForm.userName, 'isNonEmpty', ' The username cannot be empty ');
//  Change to 
validator.add(registerForm.userName, 'minLength:10', ' User name length cannot be less than 10 position ');
 Copy code 

7.3 Add multiple verification rules to a file input box

<html>
   <body> <form action="http://xxx.com/register" id="registerForm" method="post">  Please enter a user name :<input type="text" name="userName" />  Please input a password :<input type="text" name="password" />  Please enter your mobile number :<input type="text" name="phoneNumber" /> <button> Submit </button> </form> <script> //  Policy object  var strategies = { isNonEmpty: function(val, errorMsg) { //  Not empty  if (value === '') { return errorMsg; } }, minLength: function(value, length, errorMsg) { //  Limit the minimum length  if(value.length < length) { return errorMsg; } }, isMobile: function(value, errorMsg) { if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) { // Cell phone number format  return errorMsg } } } // Validator  class  var Validator = function(){ this.cache = []; //  Save validation rules  } 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) } }; Validator.prototype.start = function(){ for (var i=0, validatorFunc; validatorFunc = this.cache[i++];) { var errorMsg = validatorFunc(); if (errorMsg) { return errorMsg; } } } //  Customer call code  var registerForm = document.getElementById('registerForm'); var validataFunc = function(){ var validator = new Validator(); validator.add(registerForm.userName, [{ strategy: 'isNonEmpty', errorMsg: ' The username cannot be empty ' },{ strategy: 'minLength:10', errorMsg: ' User name length cannot be less than 10 position ' }]) validator.add(registerForm.password, [{ strategy: 'minLength:6', errorMsg: ' Password length cannot be less than 6 position ' }]) validator.add(registerForm.phoneNumber, [{ strategy: 'isMobile', errorMsg: ' Incorrect format of mobile phone number ' }]) var errorMsg = validator.start(); return errorMsg; } registerForm.onsubmit = function(){ var errorMsg = validataFunc(); if (errorMsg) { alert(errorMsg) return false; } } </script> </body>
</html>  
 Copy code 

5.8 Advantages and disadvantages of the policy model

advantage :

  • Strategic patterns use combinations 、 Technologies and ideas such as delegation and polymorphism , It can effectively avoid multiple conditional selection statements
  • The strategic model provides openness to - The perfect support of the closed principle , Encapsulate the algorithm in a separate strategy in , Make them easy to switch , Easy to understand , extensible
  • The algorithm in the policy pattern can also be reused in other parts of the system , So as to avoid a lot of duplicate copy and paste work
  • Use composition and delegation in a policy pattern to make Context Have the ability to execute algorithms , It's also a lighter alternative to inheritance

shortcoming :

  • Using the policy pattern will add many policy classes or policy objects to the program , But it's actually better than stacking the logic they're responsible for Context Better in China

  • Using the policy pattern , You have to know all about strategy, You have to know all of them strategy The difference between , So that we can choose the right one strategy . such as , We have to choose a suitable travel route , You must first understand the choice of aircraft 、 train 、 Details of plans like bicycles . here strategy To expose all its implementations to customers , At this time, those who violate the principle of least knowledge .

5.9 First class function objects and policy patterns

The previous policy pattern examples , Existing versions that simulate traditional object-oriented languages , There are also targets for JavaScript A unique implementation of the language . In the traditional class centered object-oriented language , Different algorithms or behaviors are encapsulated in various policy classes ,Context Delegate requests to these policy objects , These policy objects will return different execution results according to the request , This will show the polymorphism of the object

In languages where functions are first-class objects , The strategy model is invisible .strategy It's a variable whose value is a function . stay JavaScript in , In addition to using classes to encapsulate algorithms and behaviors , Using functions is of course an option . these ” Algorithm “ Can be encapsulated in functions and passed around , That's what I often say ” Higher order function “. In fact, JavaScript In a language that treats functions as first-class objects , The strategy model has been integrated into the language itself , We often use higher-order functions to encapsulate different behaviors , And pass it to another function . When we call these functions “ call ” Information time , Different functions will return different execution results . stay JavaScript in ,“ Polymorphism of function objects ” It's easier .

stay JavaScript in , Policy classes are often replaced by functions , At this time, the strategy mode becomes a kind of “ Invisible ” The pattern of .

copyright notice
author[Xiao Guo is in Shenzhen],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2021/08/20210827001435096a.html

Random recommended