# Design pattern -- strategy pattern

2021-08-27 00:14:43

# 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

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) {
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 ``````

• 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 .