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:

  1. The calculateBonus function is large and contains many if-else statements
  2. 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.
  3. 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:

  1. 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.
  2. Calculate the position of the ball at some point in time

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