preface
This series of articles is based on JavaScript Design Patterns and Development Practices, with a little thought added. I hope it helps.
Article series
Js design pattern — singleton pattern
Js Design pattern — Policy pattern
Js design pattern — proxy pattern
concept
The definition of a policy pattern is to define a set of algorithms, encapsulate them one by one, and make them interchangeable.
The strategy pattern refers to defining a set of algorithms and encapsulating them one by one. Separating the unchanging from the changing is the theme of every design pattern, and the policy pattern is no exception. The purpose of the policy pattern is to separate the use of an algorithm from the implementation of the algorithm.
A program based on the policy pattern consists of at least two parts. The first is a set of policy classes that encapsulate specific algorithms and are responsible for specific computing processes. The second part is the Context class, which accepts requests from customers and then delegates them to one of the policy classes. To do this, a reference to a policy object must be maintained in the Context.
The implementation of the policy pattern is not complicated. The key is to find the value behind the implementation of the policy pattern to encapsulate the ideas of change, delegation, and polymorphism.
scenario
By definition, policy patterns are used to encapsulate algorithms. But if the strategy pattern is only used to encapsulate the algorithm, it is a little overkill. In practical development, we 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 substituted, we can encapsulate them with a policy pattern.
The advantages and disadvantages
advantages
- The strategy pattern can effectively avoid multiple conditional selection statements by using techniques and ideas such as composition, delegation and polymorphism.
- The strategy pattern provides perfect support for the open-close principle, encapsulating algorithms in separate strategies, making them easy to switch, easy to understand, and easy to extend.
- Algorithms in the policy pattern can also be reused elsewhere in the system, avoiding a lot of duplicated copy-and-paste work.
- Using composition and delegation in the policy pattern to give Context the ability to execute algorithms is also a lighter alternative to inheritance.
disadvantages
- Adding a lot of policy classes or policy objects is actually better than putting the logic they’re responsible for in the Context.
- To use the strategy pattern, it is necessary to understand all strategies and the differences among them, so as to choose an appropriate strategy.
But these shortcomings are not serious
example
Calculate the bonus
Rough implementation
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 the code
Disadvantages:
- The calculateBonus function is large and contains many if-else statements
- CalculateBonus function is inelastic. If a new performance grade C is added or the bonus coefficient of performance S is changed to 5, we must go deep into the internal implementation of calculateBonus function, which violates the open closed principle.
- The reusability of the algorithm is poor
Refactor the code using composite functions
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') {returnperformanceB( salary ); }}; calculateBonus('A' , 10000 ); // Output: 30000
Copy the code
The problem remains: calculateBonus functions can get bigger and bigger and less elastic as the system changes
Refactor the code using policy mode
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 Bonus:
var Bonus = function(){
this.salary = null; // The original salary
this.strategy = null; // The policy object corresponding to the performance level
};
Bonus.prototype.setSalary = function( salary ){
this.salary = salary; // Set the original salary of the employee
};
Bonus.prototype.setStrategy = function( strategy ){
this.strategy = strategy; // Set the policy object corresponding to the employee performance level
};
Bonus.prototype.getBonus = function(){ // The amount of the bonus
return this.strategy.calculate( this.salary ); // Delegate the calculation of bonuses to the corresponding policy object
};
var bonus = new Bonus();
bonus.setSalary( 10000 );
bonus.setStrategy( new performanceS() ); // Set the policy object
console.log( bonus.getBonus() ); // Output: 40000
bonus.setStrategy( new performanceA() ); // Set the policy object
console.log( bonus.getBonus() ); // Output: 30000
Copy the code
But this code is based on a traditional object-oriented language imitation, so let’s implement the strategy pattern in JavaScript.
JavaScript version of policy mode
In JavaScript, functions are also objects, so it is simpler and more straightforward to define strategy as a function
var strategies = {
"S": function( salary ){
return salary * 4;
},
"A": function( salary ){
return salary * 3;
},
"B": function( salary ){
return salary * 2; }};var calculateBonus = function( level, salary ){
return strategies[ level ]( salary );
};
console.log( calculateBonus( 'S'.20000));// Output: 80000
console.log( calculateBonus( 'A'.10000));// Output: 30000
Copy the code
Es6 class implements
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 Bonus:
class Bonus {
constructor() {
this.salary = null; // The original salary
this.strategy = null; // The policy object corresponding to the performance level
}
setSalary(salary) {
this.salary = salary; // Set the original salary of the employee
}
setStrategy(strategy) {
this.strategy = strategy; // Set the policy object corresponding to the employee performance level
}
getBonus() { // The amount of the bonus
return this.strategy.calculate(this.salary); // Delegate the calculation of bonuses to the corresponding policy object}}var bonus = new Bonus();
bonus.setSalary(10000);
bonus.setStrategy(new performanceS()); // Set the policy object
console.log(bonus.getBonus()); // Output: 40000
bonus.setStrategy(new performanceA()); // Set the policy object
console.log(bonus.getBonus()); // Output: 30000
Copy the code
Slow animation
Goal: Write an animation class and some easing algorithms to make the ball move around the page with various easing effects
Analysis:
First of all, the job of the easing algorithm is to realize how does the ball move
Then the animation class (that is, context) is responsible for:
-
Initialize the animation object
Before you start the exercise, you need to record some useful information in advance, including at least the following information:
- The exact point in time when the animation starts;
- The original position of the ball when the animation starts;
- The target position of the ball movement;
- The duration of the ball movement.
-
Calculate the position of the ball at some point in time
-
Update the position of the ball
Implementation:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div style="position:absolute; background:blue" id="div">I am a div</div>
</body>
<script>
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; }};var Animate = function (dom) {
this.dom = dom; // The DOM node that moves
this.startTime = 0; // Start time of the animation
this.startPos = 0; // The position of the DOM node when the animation starts, that is, the initial position of the DOM
this.endPos = 0; // The position of the DOM node at the end of the animation, i.e. the target position of the DOM
this.propertyName = null; // The name of the CSS property to be changed on the DOM node
this.easing = null; // Slow algorithm
this.duration = null; // Animation duration
};
Animate.prototype.start = function (propertyName, endPos, duration, easing) {
this.startTime = +new Date; // Animation start time
this.startPos = this.dom.getBoundingClientRect()[propertyName]; // The initial position of the DOM node
this.propertyName = propertyName; // The name of the CSS property to be changed on the DOM node
this.endPos = endPos; // The target location of the DOM node
this.duration = duration; // Animate the continuous event
this.easing = tween[easing]; // Slow algorithm
var self = this;
var timeId = setInterval(function () { // Start the timer to start the animation
if (self.step() === false) { // If the animation has finished, clear the timerclearInterval(timeId); }},16);
};
Animate.prototype.step = function () {
var t = +new Date; // Get the current time
if (t >= this.startTime + this.duration) { / / (1)
this.update(this.endPos); // Update the CSS property values of the ball
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 CSS property values of the ball
};
Animate.prototype.update = function (pos) {
this.dom.style[this.propertyName] = pos + 'px';
};
var div = document.getElementById('div');
var animate = new Animate(div);
animate.start('left'.500.1000.'linear');
// animate.start( 'top', 1500, 500, 'strongEaseIn' );
</script>
</html>
Copy the code
Validate form
Simple implementation
<html>
<body>
<form action="http:// xxx.com/register" id="registerForm" method="post">Please enter user name:<input type="text" name="userName" />Please enter 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('Username cannot be empty');
return false;
}
if (registerForm.password.value.length < 6) {
alert('Password length must not be less than 6 characters');
return false;
}
if (!/ (a ^ 1 [3 | | 5 8] [0-9] {9} $) /.test(registerForm.phoneNumber.value)) {
alert('Incorrect mobile number format');
return false; }}</script>
</body>
</html>
Copy the code
Use the policy pattern to improve
<html>
<body>
<form action="http:// xxx.com/register" id="registerForm" method="post">Please enter user name:<input type="text" name="userName" />Please enter password:<input type="text" name="password" />Please enter your mobile number:<input type="text" name="phoneNumber" />
<button>submit</button>
</form>
<script>
var strategies = {
isNonEmpty: function (value, errorMsg) { / / not be empty
if (value === ' ') {
returnerrorMsg; }},minLength: function (value, length, errorMsg) { // Limit the minimum length
if (value.length < length) {
returnerrorMsg; }},isMobile: function (value, errorMsg) { // Mobile number format
if (!/ (a ^ 1 [3 | | 5 8] [0-9] {9} $) /.test(value)) {
returnerrorMsg; }}};var validataFunc = function () {
var validator = new Validator(); // Create a validator object
/*************** Add some verification rules ****************/
validator.add(registerForm.userName, 'isNonEmpty'.'Username cannot be empty');
validator.add(registerForm.password, 'minLength:6'.'Password length must not be less than 6 characters');
validator.add(registerForm.phoneNumber, 'isMobile'.'Incorrect mobile number format');
var errorMsg = validator.start(); // Obtain the verification result
return errorMsg; // Return the verification result
}
var registerForm = document.getElementById('registerForm');
registerForm.onsubmit = function () {
var errorMsg = validataFunc(); // If errorMsg returns an exact value, the check fails
if (errorMsg) {
alert(errorMsg);
return false; // Block form submission}};var Validator = function () {
this.cache = []; // Save the verification rule
};
Validator.prototype.add = function (dom, rule, errorMsg) {
var ary = rule.split(':'); // Separate strategy from parameters
this.cache.push(function () { // Wrap the validation step in an empty function and put it in cache
var strategy = ary.shift(); // The strategy selected by the user
ary.unshift(dom.value); // Add the value of input to the parameter list
ary.push(errorMsg); // Add the errorMsg to the argument list
return strategies[strategy].apply(dom, ary);
});
};
Validator.prototype.start = function () {
for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
var msg = validatorFunc(); // Start the verification and obtain the return information after the verification
if (msg) { // If there is an exact return value, the check failed
returnmsg; }}};</script>
</body>
</html>
Copy the code
Disadvantages: A text input box can only correspond to one verification rule
Further improvement: you can have more than one validation rule
<html>
<body>
<form action="http:// xxx.com/register" id="registerForm" method="post">Please enter user name:<input type="text" name="userName" />Please enter 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 (value, errorMsg) {
if (value === ' ') {
returnerrorMsg; }},minLength: function (value, length, errorMsg) {
if (value.length < length) {
returnerrorMsg; }},isMobile: function (value, errorMsg) {
if (!/ (a ^ 1 [3 | | 5 8] [0-9] {9} $) /.test(value)) {
returnerrorMsg; }}};/ * * * * * * * * * * * * * * * * * * * * * * * the Validator class * * * * * * * * * * * * * * * * * * * * * * * * * * /
var Validator = function () {
this.cache = [];
};
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) {
returnerrorMsg; }}};/ * * * * * * * * * * * * * * * * * * * * * * * customer calling code * * * * * * * * * * * * * * * * * * * * * * * * * * /
var registerForm = document.getElementById('registerForm');
var validataFunc = function () {
var validator = new Validator();
validator.add(registerForm.userName, [{
strategy: 'isNonEmpty'.errorMsg: 'Username cannot be empty'
}, {
strategy: 'minLength:6'.errorMsg: 'User name must not be less than 10 characters long'
}]);
validator.add(registerForm.password, [{
strategy: 'minLength:6'.errorMsg: 'Password must be at least 6 characters long'
}]);
var errorMsg = validator.start();
return errorMsg;
}
registerForm.onsubmit = function () {
var errorMsg = validataFunc();
if (errorMsg) {
alert(errorMsg);
return false; }};</script>
</body>
</html>
Copy the code