Behavior design pattern

Behavioral patterns encapsulate changes in the behavior of objects and describe “how classes or objects collaborate to accomplish tasks that a single object cannot accomplish alone, and how responsibilities are assigned.” Common patterns include Strategy, Iterator, publish-subscribe, Command, Template Method, and Chain of Responsibility, Mediator, and State

1. Strategy

The policy pattern encapsulates a defined set of algorithms and makes them interchangeable.

Take the calculation of year-end bonus as an example, many companies pay year-end bonus according to the employee’s salary base and year-end performance. For example, the person performing at S gets 4 times his salary, the person performing at A gets 3 times his salary, and the person performing at B gets 2 times his salary. Suppose the finance department asks us for a code to help them calculate employees’ year-end bonuses.

We can write a function called calculateBonus to calculate each person’s bonus. Obviously, the calculateBonus function needs to accept two parameters: the employee’s salary and his performance rating. The code is as follows:

    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

This code is quite simple, but has obvious drawbacks. The calculateBonus function is quite large and contains many if-else statements that need to override all logical branches. Moreover, calculateBonus function is inelastic. If we add a new performance level C, or want to change the bonus coefficient of performance S to 5, we must go deep into the internal implementation of calculateBonus function, which is against the open-closed principle.

We use the policy pattern to rewrite this code. Encapsulate a defined set of algorithms using policy patterns

    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

Separating the immutable from the changing is the theme of every design pattern, and policy patterns are no exception. The purpose of policy patterns is to separate the use of algorithms from the implementation of algorithms. In this example, the algorithm is used in the same way, according to a certain algorithm to obtain the calculated amount of the bonus. The implementation of the algorithm is different and varied, and each performance corresponds to different calculation rules.

A policy-based program consists of at least two parts. The first part is the policy group, which encapsulates the specific algorithm and is responsible for the specific calculation process. The second part is the Context, which accepts a client’s request and then delegates it to a policy. To do this, a reference to a policy object is maintained in the Context.

2. Iterator pattern

The iterator pattern provides a way to access the elements of an aggregate object sequentially without exposing the internal representation of the object. The iterator pattern separates the process of iterating from the business logic. After using the iterator pattern, each element of an object can be accessed sequentially, even without caring about its internal construction.

JavaScript has a built-in iterator implementation called array.prototype.foreach. Of course, we can also implement an iterator ourselves.

    var each = function (ary, callback) {
      for (var i = 0, l = ary.length; i < l; i++) {
        callback.call(ary[i], i, ary[i]); // Pass the subscript and element as arguments to the callback function}}; each([1.2.3].function (i, n) {
      alert([i, n]);
    });
Copy the code

Iterators can be divided into internal iterators and external iterators, which have their own applicable scenarios. The each function we just wrote is an inner iterator. Each has defined the iteration rules inside, and it takes over the entire iteration process completely, with only one initial call outside. Since the rules for iterating inner iterators were specified in advance, the each function above cannot iterate over two arrays at the same time. For example, if we have a requirement to determine whether the values of the elements in two arrays are exactly the same, the only place we can start seems to be the callback for each without rewriting the code itself.

    var compare = function (ary1, ary2) {
      if(ary1.length ! == ary2.length) {throw new Error('ary1 is not equal to ary2 ');
      }
      each(ary1, function (i, n) {
        if(n ! == ary2[i]) {throw new Error('ary1 is not equal to ary2 '); }}); alert('ary1 is equal to ary2 ');
    };
    compare([1.2.3], [1.2.4]); // throw new Error ('ary1 and ary2 are not equal ');
Copy the code

An external iterator must explicitly request iteration of the next element. External iterators add some complexity to the calls, but they also increase the flexibility of the iterators, allowing us to manually control the iteration process or sequence. Let’s see how we can rewrite compare using an external iterator.

    var Iterator = function (obj) {
      var current = 0;
      var next = function () {
        current += 1;
      };
      var isDone = function () {
        return current >= obj.length;
      };
      var getCurrItem = function () {
        return obj[current];
      };
      return {
        next: next,
        isDone: isDone,
        getCurrItem: getCurrItem
      }
    };

    var compare = function (iterator1, iterator2) {
      while(! iterator1.isDone() && ! iterator2.isDone()) {if(iterator1.getCurrItem() ! == iterator2.getCurrItem()) {throw new Error(Iterator1 and iterator2 are not equal);
        }
        iterator1.next();
        iterator2.next();
      }
      alert(Iterator1 is equal to iterator2);
    }
    var iterator1 = Iterator([1.2.3]);
    var iterator2 = Iterator([1.2.3]);
    compare(iterator1, iterator2); Iterator1 is equal to iterator2
Copy the code

3. Publish and subscribe (Obsever)

The publisk-subscribe pattern, also known as the observer pattern, defines a one-to-many dependency between objects. When an object’s state changes, all dependent objects are notified. The publish-subscribe model is widely used both in the app world and in the real world. Let’s start with a real world example.

Xiao Ming, Xiao Hong and Xiao Gang are interested in a particular property, but the selling office tells them that the property is sold out, but there will be some final offers, so they exchange their contact information. When not using the publish-subscribe model, the three call the sales office every day, or more if there are more customers; In the publish-subscribe model, the sales office will notify the three when a listing becomes available.

You can see that there are obvious advantages to using the publish-subscribe model in this example.

  • Home buyers do not need to call the sales office every day to consult the opening time, at the appropriate time point, the sales office as a publisher will inform these news subscribers.
  • There is no longer a strong coupling between the buyer and the sales office. When a new buyer appears, he only needs to leave his mobile phone number at the sales office. The sales office does not care about any situation of the buyer, and other changes in the sales office will not affect the buyer.

The first point illustrates that the publish-subscribe pattern can be widely used in asynchronous programming as an alternative to passing callbacks. The second point is that publishable reading can replace hard-coded notification between objects; one object no longer explicitly calls an interface of another object.

In fact, as long as we’ve ever tied event functions to DOM nodes, we’ve used publish-subscribe. In addition to DOM events, we often implement custom events, and this publish-subscribe model of custom events can be used in any JavaScript code.

    var salesOffices = {}; // Define the sales office
    salesOffices.clientList = []; // Caches a list of subscriber callback functions
    salesOffices.listen = function (fn) { // Add subscribers
      this.clientList.push(fn); // Subscribed messages are added to the cache list
    };
    salesOffices.trigger = function () { // Publish the message
      for (var i = 0, fn; fn = this.clientList[i++];) {
        fn.apply(this.arguments); // (2) // arguments are arguments to take when Posting messages}}; salesOffices.listen(function (price, squareMeter) { // Xiaoming subscribes to the message
      console.log('Price =' + price);
      console.log('squareMeter= ' + squareMeter);
    });
    salesOffices.listen(function (price, squareMeter) { // Red subscribes message
      console.log('Price =' + price);
      console.log('squareMeter= ' + squareMeter);
    });
    salesOffices.trigger(2000000.88); // Output: 2 million, 88 square meters
    salesOffices.trigger(3000000.110); // Output: 3 million, 110 square meters
Copy the code

4. Command mode

A Command in Command mode refers to an instruction that does something specific. The most common scenario is when you need to send a request to some object without knowing who the recipient of the request is or what action is being requested. At this point, you want to design the program in a loosely coupled way so that the request sender and the request receiver can decouple from each other.

Imagine we are writing a user interface program with at least a dozen buttons. Because of the complexity of the project, we decided that one programmer would be responsible for drawing the buttons, while another programmer would be responsible for writing the behavior of clicking the buttons, which would be encapsulated in objects. For the programmer who draws the button, he has no idea what the button will do in the future, whether it will refresh the menu interface or add submenus. He only knows that something will happen when the button is clicked.

We can quickly see the reason for using the command pattern here: after the button is clicked, the request must be sent to some object responsible for the specific behavior, and these objects are the recipients of the request. But it is not known what the recipient is or what exactly the recipient will do. At this point we need the help of a command object to decouple the button from the object responsible for the specific behavior.

  <button id="button1">Click button 1</button>
  <button id="button2">Click button 2</button>
  <button id="button3">Click button 3</button>
Copy the code
    var button1 = document.getElementById('button1');
    var button2 = document.getElementById('button2');
    var button3 = document.getElementById('button3');
    var MenuBar = {
      refresh: function () {
        console.log('Refresh menu directory'); }};var SubMenu = {
      add: function () {
        console.log('Add submenu');
      },
      del: function () {
        console.log('Delete submenu'); }};var RefreshMenuBarCommand = function (receiver) {
      return {
        execute: function () {
          receiver.refresh()
        }
      }
    };
    var AddSubMenuCommand = function (receiver) {
      return {
        execute: function () {
          receiver.add()
        }
      }
    };
    var DelSubMenuCommand = function (receiver) {
      return {
        execute: function () {
          receiver.del()
        }
      }
    };
    var setCommand = function (button, command) {
      button.onclick = function () { command.execute(); }};var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar);
    var addSubMenuCommand = AddSubMenuCommand(SubMenu);
    var delSubMenuCommand = DelSubMenuCommand(SubMenu);
    setCommand(button1, refreshMenuBarCommand);
    setCommand(button2, addSubMenuCommand);
    setCommand(button3, delSubMenuCommand);
Copy the code

Clicking a button executes a command, which is contracted to call the execute() method of the command object. It’s not clear what the commands represent, but the programmer who draws the buttons doesn’t care. All he has to do is reserve the interface to install the commands, and the command object knows how to communicate with the right object

5. Template Method

The template method pattern consists of two parts, the first part is the abstract parent class, the second part is the concrete implementation child class. The algorithm framework of a subclass is usually encapsulated in an abstract superclass, including the implementation of some common methods and the execution order of all methods in the subclass. By inheriting this abstract class, subclasses also inherit the entire algorithm structure and can choose to override the methods of the parent class.

Let’s say we have some parallel subclasses, some of the same behaviors, some of the different behaviors. If the same and different behaviors are mixed in the implementation of each subclass, it means that the same behaviors are repeated in each subclass. But in fact, the same behavior can be moved to another single place, and the template method pattern was created to solve this problem. In the template method pattern, the same parts of the subclass implementation are moved up into the parent class, leaving the different parts to be implemented by the subclass.

Coffee and Tea is a classic example that is often used to illustrate the template method pattern. This example is based on the Head First Design Pattern.

First of all, let’s make a cup of coffee. If there is nothing too personalized, the steps of making coffee are usually as follows:

  1. The water to a boil
  2. Brew coffee in boiling water
  3. Pour the coffee into the cup
  4. Add sugar and milk
    var Coffee = function () {}; Coffee.prototype.boilWater =function () {
      console.log('Boil the water');
    };
    Coffee.prototype.brewCoffeeGriends = function () {
      console.log('Brew coffee in boiling water');
    };
    Coffee.prototype.pourInCup = function () {
      console.log('Pour the coffee into the cup');
    };
    Coffee.prototype.addSugarAndMilk = function () {
      console.log('With sugar and milk');
    };
    Coffee.prototype.init = function () {
      this.boilWater();
      this.brewCoffeeGriends();
      this.pourInCup();
      this.addSugarAndMilk();
    };
    var coffee = new Coffee();
    coffee.init();

Copy the code

Next, we prepare our tea, which is not too different from the process of making coffee:

  1. The water to a boil
  2. Soak tea leaves in boiling water
  3. Pour the tea into the cup
  4. With lemon
  var Tea = function () {}; Tea.prototype.boilWater =function () {
    console.log('Boil the water');
  };
  Tea.prototype.steepTeaBag = function () {
    console.log('Soak tea leaves in boiling water');
  };
  Tea.prototype.pourInCup = function () {
    console.log('Pour the tea into the cup');
  };
  Tea.prototype.addLemon = function () {
    console.log('Add lemon');
  };
  Tea.prototype.init = function () {
    this.boilWater();
    this.steepTeaBag();
    this.pourInCup();
    this.addLemon();
  };
  var tea = new Tea();
  tea.init();
Copy the code

Compare the process of making coffee and tea

We found the main differences between making coffee and tea.

  • The ingredients are different. One is coffee and one is tea, but we can abstract them both as “drinks.”
  • The way of soaking is different. Coffee is brewed, while tea is soaked. We can abstract them both as “brews”.
  • The spices are different. One is sugar and milk, and one is lemon, but we can abstract them both as “spices.”

After abstraction, whether we make coffee or tea, we can organize it into the following four steps:

  1. The water to a boil
  2. Brew drinks with boiling water
  3. Pour the drink into the glass
  4. Add seasoning

You can now create an abstract superclass to represent the entire process of making a drink.

    var Beverage = function () {}; Beverage.prototype.boilWater =function () {
      console.log('Boil the water');
    };
    Beverage.prototype.brew = function () {};// Empty method, should be overridden by subclasses
    Beverage.prototype.pourInCup = function () {};// Empty method, should be overridden by subclasses
    Beverage.prototype.addCondiments = function () {};// Empty method, should be overridden by subclasses
    Beverage.prototype.init = function () {
      this.boilWater();
      this.brew();
      this.pourInCup();
      this.addCondiments();
    };
Copy the code

Next we create the coffee class and make it inherit from the drinks class:

    var Coffee = function () {}; Coffee.prototype =new Beverage();
    Coffee.prototype.brew = function () {
      console.log('Brew coffee in boiling water');
    };
    Coffee.prototype.pourInCup = function () {
      console.log('Pour the coffee into the cup');
    };
    Coffee.prototype.addCondiments = function () {
      console.log('With sugar and milk');
    };
    var Coffee = new Coffee();
    Coffee.init();
Copy the code

So far, our Coffee class has been completed. When we call the init method of the Coffee object, since there is no corresponding init method on the prototype of the Coffee object and the Coffee constructor, the request will follow the prototype chain. The init method delegated to Coffee’s “parent” Beverage prototype.

The order of brewing is specified in the beverage.prototype. init method, so we can successfully brew a cup of coffee. Beverage.prototype.init is the template method.

6. Chain of Responsibility

The name of the chain of responsibility pattern is very descriptive. A series of objects that might handle a request are connected in a chain, and the request is passed from one object to another until an object that can handle it is encountered, which we call nodes in the chain.

Examples of the chain of responsibility pattern are not hard to find in reality. When you get on a bus during rush hour, there are often too many people on the bus to put a coin in, so you have to pass the two-yuan coin to the front. Unless you’re lucky, your coin usually has to pass through N hands before it reaches the box.

The greatest advantage of the chain of responsibility pattern is that the request sender only needs to know the first node in the chain, thus weakening the strong connection between the sender and a set of receivers. Without the chain of responsibility model, I would have to go to the coin box on the bus before I could drop a coin.

Let’s say we are in charge of an e-commerce website selling mobile phones. After two rounds of bookings of 500 yuan and 200 yuan deposit, we are now in the stage of formal purchase. The company has a certain preferential policy for users who have paid a deposit. After the official purchase, users who have paid 500 yuan deposit will receive 100 yuan mall coupons, users who have paid 200 yuan deposit can receive 50 yuan coupons, and users who did not pay the deposit before can only enter the ordinary purchase mode, that is, there is no coupon, and in the case of limited inventory may not be guaranteed to buy.

Let’s write this process in code:

    var order = function (orderType, pay, stock) {
      if (orderType === 1) { // 500 yuan deposit purchase mode
        if (pay === true) { // A deposit has been paid
          console.log('500 yuan deposit, get 100 coupons');
        } else { // If no deposit is paid, downgrade to normal purchase mode
          if (stock > 0) { // There are still phones available for general purchase
            console.log('Regular purchase, no coupons');
          } else {
            console.log('Lack of mobile phone stock'); }}}else if (orderType === 2) { // 200 yuan deposit purchase mode
        if (pay === true) {
          console.log('200 yuan deposit, get 50 coupons');
        } else {
          if (stock > 0) {
            console.log('Regular purchase, no coupons');
          } else {
            console.log('Lack of mobile phone stock'); }}}else if (orderType === 3) {
        if (stock > 0) {
          console.log('Regular purchase, no coupons');
        } else {
          console.log('Lack of mobile phone stock'); }}};Copy the code

While we got the results we expected, the order function was not only hard to read, it also required constant modification. Now let’s refactor this code using the chain of responsibility pattern.

    var order500yuan = function (orderType, pay, stock) {
      if (orderType === 1 && pay === true) {
        console.log('500 yuan deposit, get 100 coupons');
      } else {
        return 'nextSuccessor'; // I don't know who the next node is, just pass the request to the next node}};var order200yuan = function (orderType, pay, stock) {
      if (orderType === 2 && pay === true) {
        console.log('200 yuan deposit, get 50 coupons');
      } else {
        return 'nextSuccessor'; // I don't know who the next node is, just pass the request to the next node}};var orderNormal = function (orderType, pay, stock) {
      if (stock > 0) {
        console.log('Regular purchase, no coupons');
      } else {
        console.log('Lack of mobile phone stock'); }};Function.prototype.after = function (fn) {
      var self = this;
      return function () {
        var ret = self.apply(this.arguments);
        if (ret === 'nextSuccessor') {
          return fn.apply(this.arguments);
        }
        returnret; }};var order = order500yuan.after(order200yuan).after(orderNormal);
    order(1.true.500); // Output: 500 yuan deposit pre-order, get 100 coupons
    order(2.true.500); // Output: 200 yuan deposit pre-order, get 50 coupons
    order(1.false.500); // Output: normal purchase, no coupon
Copy the code

The chain of responsibility model is a great way to organize code, but it is not without its drawbacks. First, there is no guarantee that a request will be processed by the nodes in the chain. In this case, we can add a guaranteed recipient node at the end of the chain to handle requests that are about to leave the chain. In addition, the responsibility chain mode makes the program have more node objects, most nodes may not play a substantial role in a request transfer process, their role is just to let the request to pass on, from the performance aspect, we should avoid the performance loss caused by too long responsibility chain.

7. Mediator

In a program, an object may interact with multiple objects, keeping references to it. As the program grows in size, there are more and more objects and their relationships become more complex, leading to a network of cross-references. When we change or delete one of these objects, we will most likely need to notify all objects that reference it. So, like removing a capillary next to the heart, even small changes must be made with great care.

The role of the mediator pattern is to decouple objects from each other. By adding a mediator object, all related objects communicate through the mediator object rather than referring to each other, so when an object changes, the mediator object only needs to be notified.

There are many examples of intermediaries in real life, such as airports directing and dispatching planes and bookmakers calculating odds and winning.

There is now a multiplayer game. Start by defining a Player constructor that has three simple prototype methods player.prototype. win, player.prototype. lose, and player.prototype. die for Player death.

    var players = [];
    function Player(name, teamColor) {
      this.partners = []; // List of teammates
      this.enemies = []; // List of enemies
      this.state = 'live'; // Player state
      this.name = name; // Character name
      this.teamColor = teamColor; // Team color
    };
    Player.prototype.win = function () { // Player team wins
      console.log('winner: ' + this.name);
    };
    Player.prototype.lose = function () { // Player team fails
      console.log('loser: ' + this.name);
    };
    Player.prototype.die = function () { // The player dies
      var all_dead = true;
      this.state = 'dead'; // Set the player state to dead
      for (var i = 0, partner; partner = this.partners[i++];) { // Iterate through the teammate list
        if(partner.state ! = ='dead') { // If only one teammate is still alive, the game is not lost
          all_dead = false;
          break; }}if (all_dead === true) { // If all teammates die
        this.lose(); // Tell yourself the game failed
        for (var i = 0, partner; partner = this.partners[i++];) { // Notify all teammates that the game has failed
          partner.lose();
        }
        for (var i = 0, enemy; enemy = this.enemies[i++];) { // Notify all enemies of game victoryenemy.win(); }}};// Define a factory to create players
    var playerFactory = function (name, teamColor) {
      var newPlayer = new Player(name, teamColor); // Create new players
      for (var i = 0, player; player = players[i++];) { // Inform all players that a new character has been added
        if (player.teamColor === newPlayer.teamColor) { // If the player is on the same team
          player.partners.push(newPlayer); // Add each other to the teammate list
          newPlayer.partners.push(player);
        } else {
          player.enemies.push(newPlayer); // Add each other to the enemies list
          newPlayer.enemies.push(player);
        }
      }
      players.push(newPlayer);
      return newPlayer;
    };

    / / the red team:
    var player1 = playerFactory('preserved egg'.'red'),
      player2 = playerFactory('little girl'.'red'),
      player3 = playerFactory('baby'.'red'),
      player4 = playerFactory('jack'.'red');
    / / team:
    var player5 = playerFactory('dark girl of personages'.'blue'),
      player6 = playerFactory('onion'.'blue'),
      player7 = playerFactory('overweight'.'blue'),
      player8 = playerFactory('the pirates'.'blue');
    // Let all red team players die
    player1.die();
    player2.die();
    player4.die();
    player3.die();
Copy the code

The result is shown below

We can now add players and teams at will, but the problem is that every player is tightly coupled to every other player. When the state of each object changes, such as when the character moves, eats an item, or dies, the other objects must be explicitly traversed to notify them. In a large online game, the situation is more complicated, and there are issues of dropping calls and switching parties.

Now we’re starting to reinvent the game with the intermediary model. The Player is no longer responsible for the specific execution logic, but rather passes the operation to the intermediary object playerDirector.

   function Player(name, teamColor) {
      this.name = name; // Character name
      this.teamColor = teamColor; // Team color
      this.state = 'alive'; // Player state
    };
    Player.prototype.win = function () {
      console.log(this.name + ' won ');
    };
    Player.prototype.lose = function () {
      console.log(this.name + ' lost');
    };
    /******************* Player dies *****************/
    Player.prototype.die = function () {
      this.state = 'dead';
      playerDirector.reciveMessage('playerDead'.this); // Send a message to the broker, the player dies
    };
    /******************* Remove player *****************/
    Player.prototype.remove = function () {
      playerDirector.reciveMessage('removePlayer'.this); // Send a message to the broker to remove a player
    };
    /******************* Players change teams *****************/
    Player.prototype.changeTeam = function (color) {
      playerDirector.reciveMessage('changeTeam'.this, color); // Send a message to the broker and the player changes teams
    };

    var playerFactory = function (name, teamColor) {
      var newPlayer = new Player(name, teamColor); // Create a new player object
      playerDirector.reciveMessage('addPlayer', newPlayer); // Send a message to the broker to add new players
      return newPlayer;
    };

    var playerDirector = (function () {
      var players = {}, // Save all players
        operations = {}; // Actions that the mediator can perform

      operations.addPlayer = function (player) {
        var teamColor = player.teamColor; // The player's team color
        players[teamColor] = players[teamColor] || []; // If the player of this color does not already have a team, create a new team
        players[teamColor].push(player); // Add players to the team
      };
      operations.removePlayer = function (player) {
        var teamColor = player.teamColor, // The player's team color
          teamPlayers = players[teamColor] || []; // All members of the team
        for (var i = teamPlayers.length - 1; i >= 0; i--) { // Iterate over the delete
          if (teamPlayers[i] === player) {
            teamPlayers.splice(i, 1); }}}; operations.changeTeam =function (player, newTeamColor) { // Players change teams
        operations.removePlayer(player); // Delete from the original team
        player.teamColor = newTeamColor; // Change the team color
        operations.addPlayer(player); // Add to a new team
      };
      operations.playerDead = function (player) { // The player dies
        var teamColor = player.teamColor,
          teamPlayers = players[teamColor]; // The player's team
        var all_dead = true;
        for (var i = 0, player; player = teamPlayers[i++];) {
          if(player.state ! = ='dead') {
            all_dead = false;
            break; }}if (all_dead === true) { // All dead
          for (var i = 0, player; player = teamPlayers[i++];) {
            player.lose(); // All players lose
          }
          for (var color in players) {
            if(color ! == teamColor) {var teamPlayers = players[color]; // Players from other teams
              for (var i = 0, player; player = teamPlayers[i++];) {
                player.win(); // All players on other teams win}}}}};var reciveMessage = function () {
        var message = Array.prototype.shift.call(arguments); // arguments The first argument is the message name
        operations[message].apply(this.arguments);
      };
      return {
        reciveMessage: reciveMessage
      }
    })();

    / / the red team:
    var player1 = playerFactory('preserved egg'.'red'),
      player2 = playerFactory('little girl'.'red'),
      player3 = playerFactory('baby'.'red'),
      player4 = playerFactory('jack'.'red');
    / / team:
    var player5 = playerFactory('dark girl of personages'.'blue'),
      player6 = playerFactory('onion'.'blue'),
      player7 = playerFactory('overweight'.'blue'),
      player8 = playerFactory('the pirates'.'blue');
    player1.die();
    player2.die();
    player3.die();
    player4.die();
Copy the code

As you can see, in addition to the broker itself, none of the players know the existence of any other player, the coupling relationship between the players and the players has been entirely removed, a player any operation does not need to notify the other players, and only need to send a message to the broker, mediator after processing the message object will reduce the processing result feedback to other players.

However, there are some drawbacks to the intermediary model. One of the biggest disadvantages is the addition of a mediator object to the system, because the complexity of the interaction between objects is transferred to the complexity of the mediator object, making the mediator object often huge. The mediator object itself is often a difficult object to maintain.

8. State

The key of state mode is to distinguish the internal state of things. The change of internal state of things often brings about the change of behavior of things.

Let’s imagine a situation where there is an electric light with only one switch on it. When the light is on, press the switch at this time, the electric light will switch to off state; Press the switch again and the light will be turned on again. The same switch button behaves differently in different states.

First, the realization of lamp program without state mode is given:

    var Light = function () {
      this.state = 'off'; // Set the lamp to initial state off
      this.button = null; // Light switch button
    };

    Light.prototype.init = function () {
      var button = document.createElement('button'),
        self = this;
      button.innerHTML = 'switch';
      this.button = document.body.appendChild(button);
      this.button.onclick = function () { self.buttonWasPressed(); }}; Light.prototype.buttonWasPressed =function () {
      if (this.state === 'off') {
        console.log('turn on the light');
        this.state = 'on';
      } else if (this.state === 'on') {
        console.log('off');
        this.state = 'off'; }};var light = new Light();
    light.init();
Copy the code

Unfortunately, there is not only one kind of electric light in the world. Many hotels have another kind of electric light, which also has a single switch, but it behaves like this: the first press to turn on the weak light, the second press to turn on the strong light, and the third press to turn off the light. They had to modify the code to make the new lamp.

But writing all the state-changing logic in buttonWasPressed presents the following problems:

  1. Violation of the open-closed principleEach time new or modifiedlightAll of them need to be changedbuttonWasPressedThe code in the.
  2. All behavior related to the state is encapsulated inbuttonWasPressedIn the method, we’re not going to be able to predict thismethodswillinflationTo what extent.
  3. The switching of states is not obvious, just by assigning a value to the state variable, and there is no way to understand how many states the lamp has at a glance.
  4. The switching relationship between states is just going tobuttonWasPressedMethod of stackingif,elseStatement, which makesbuttonWasPressedmoreDifficult to read and maintain.

Now we will learn the procedure for improving the lamp using state mode. State pattern is the key to the things of each state are encapsulated into a separate class, the behavior associated with such a state is encapsulated within the class, so when the button is pressed, need only in context, entrusting the request to the current state of the object, the state object is responsible for rendering its own behavior.

    / / OffLightState:
    var OffLightState = function (light) {
      this.light = light;
    };
    OffLightState.prototype.buttonWasPressed = function () {
      console.log('weak light'); // offLightState Corresponds to the behavior
      this.light.setState(this.light.weakLightState); // Switch the state to weakLightState
    };
    / / WeakLightState:
    var WeakLightState = function (light) {
      this.light = light;
    };
    WeakLightState.prototype.buttonWasPressed = function () {
      console.log('light'); // The corresponding behavior of weakLightState
      this.light.setState(this.light.strongLightState); // Switch the state to strongLightState
    };
    / / StrongLightState:
    var StrongLightState = function (light) {
      this.light = light;
    };
    StrongLightState.prototype.buttonWasPressed = function () {
      console.log('off'); // strongLightState corresponds to the behavior
      this.light.setState(this.light.offLightState); // Switch the state to offLightState
    };

    var Light = function () {
      this.offLightState = new OffLightState(this);
      this.weakLightState = new WeakLightState(this);
      this.strongLightState = new StrongLightState(this);
      this.button = null;
    };
    Light.prototype.init = function () {
      var button = document.createElement('button'),
        self = this;
      this.button = document.body.appendChild(button);
      this.button.innerHTML = 'switch';
      this.currState = this.offLightState; // Set the current state
      this.button.onclick = function () { self.currState.buttonWasPressed(); }}; Light.prototype.setState =function (newState) {
      this.currState = newState;
    };
    var light = new Light();
    light.init();
Copy the code

The result is the same as the previous code, but the benefit of using the state pattern is clear: it can localize the relationship between each state and its corresponding behavior, which is dispersed and encapsulated in its corresponding state class, making it easy to read and manage the code.

In addition, switching between states is distributed within the state class, which eliminates the need to write a lot of if and else conditional branching languages to control switching between states.

The resources

JavaScript Design Patterns and Development Practices Head First Design Patterns