preface

Hello everyone, this is the third article about the author’s share of design patterns, the first two can refer to:

  • Write down several JavaScript design patterns (factory, singleton, adapter, Decorator, Builder)

  • JavaScript Design Patterns in League of Legends style (part 1) (Constructor mode, facade mode, proxy mode)

Design patterns are widely used in programming. Each pattern describes a recurring problem around us and the core of how to solve it! A lot of times, it’s more time consuming to actually choose the right design pattern. From the previous articles, there will be one or two examples for each design pattern, which can both help me to develop and recall the design pattern in the future, and hopefully give readers some inspiration.

The strategy pattern

Introduction to the

The policy pattern defines a family of algorithms, encapsulates them separately so that they can be replaced with each other, so that changes to the algorithm do not affect the customers using the algorithm.

That sounds like a lot of stuff, but why does it all involve algorithms? Isn’t it time for me to attack an army of algorithms. Well, it’s not. Here’s a super common example!

Let’s go back to the league of Legends, when we first log into the League of Legends, we need to enter a new name, right? Naming rules must have at least the following:

  • The length of the name
  • Whether the name has invalid characters
  • Whether the nuptial
  • Can’t be empty

The specific Settings are known only to the developer, and as a player they are the only things you notice, so how does the strategy model work here? Let’s start with an example of an obvious feature:

var validator = {
    validate: function (value, type) {
        switch (type) {
            case 'isNonEmpty ':
                {
                    return true; // The name cannot be empty
                }
            case 'isNoNumber ':
                {
                    return true; // Names are not pure numbers
                    break;
                }
            case 'isExist ':
                {
                    return true; // The name already exists
                }
            case 'isLength':
                {
                    return true; // The length is reasonable}}}};Copy the code

The above code can implement a form validation system that validates the functionality of the created role when it is named, simply by passing in the appropriate parameters.

Validator. Validate (' test name ', 'isNumber') // false

Although ideal results can be obtained, this writing method has serious disadvantages. The most important one is that every time rules are added or modified, the entire validate function needs to be modified, which does not conform to the open and closed principle and increases logic, making the function more complex and uncontrollable.

So what’s the proper code to write?

var validator = {
    // Where all validation rule handlers are stored
    types: {},
    validate: function (str, types) {
        this.messages = [];
        var checker, result, msg, i;
        for (i in types) {
            var type = types[i];
            checker = this.types[type]; // Get the validation class of the validation rule
            if(! checker) {// If the validation rule class does not exist, throw an exception
                throw {
                    name: "ValidationError".message: "No handler to validate type " + type
                };
            }

            result = checker.validate(str); // Use a single validation class found for validation
            if(! result) { msg ="Invalid value for *" + type + "*." + checker.instructions;
                this.messages.push(msg); }}return this.hasErrors();
    },

    // Whether there is a message error message
    hasErrors: function () {
        return this.messages.length ! = =0; }};Copy the code

The above code defines the Validator object and the validate function, which internally processes the passed string and the array of check types. If there is a rule, judge and send an error message to this.message. If there is no rule, naturally there is no need to continue, just throw an error.

// Verify that the given value is not null
validator.types.isNonEmpty = {
    validate: function (value) {
        returnvalue ! = ="";
    },
    instructions: "The value passed in cannot be null."
};

// Verify that the given value is not a pure number
validator.types.isNoNumber = {
    validate: function (value) {
        return isNaN(value); // Pseudo-notation, because isNaN will misjudge booleans, empty strings, etc., is not really a good way to judge pure numbers
    },
    instructions: "The value passed in cannot be pure numbers."
};

// Verify that the given value exists
validator.types.isExist = {
    validate: function (value) {
        // $.ajax() ...
        return true;
    },
    instructions: "The given value already exists"
};

// Verify that the given value length is reasonable
validator.types.isLength = {
    validate: function (value) {
        var l = value.toString().length
        if ( l > 2 && l < 10) {
            return true;
        } else {
            return false; }},instructions: "Length is not reasonable. Please be within 2-10 characters."
};
Copy the code

The types rule is supplemented above by defining several rules, so the simple setting for name validation is done. The next thing you need to prepare is a name that makes sense in League of Legends:

var types = ['isExist'.'isLength'.'isNoNumber'.'isNonEmpty']; // Determine the rule you want. The original function does not need to be changed, whether it is increased or decreasedfunction check (name, types) {
    validator.validate(name, types);
    if (validator.hasErrors()) {
        console.log(validator.messages.join("\n"));
    } else {
        console.log('Verified! ')
    }
}
check('okckokckokck', types) // The length is not reasonable, please length within 2-10 characters check('the old faker', types) // true
check('00001', types) // Values passed in cannot be pure numbersCopy the code

First, set the desired rule, including it with a types array, then define a check function, encapsulate the result processing, and finally pass in the parameters. Whatever rule you want to check, you don’t need to change the original function. Now whether I want to check whether faker can be registered or an empty string, I can pass in the rule and use it. If you want to add new rules, you just need to extend the object to Validator.types for clarity and clarity.

The idea is to encapsulate the complex structure of the algorithm so that they can be replaced with each other, and the code above is a good example of that, because no matter how MUCH I change the rules I want, I don’t have to change the original code.

The bridge model

Introduction to the

The system has been decoupled while varying along multiple dimensions without increasing its complexity. Separate the abstract part from its implementation part so that they can all change independently. To put it simply: The main feature of the bridge pattern is the decoupling of the implementation layer (such as events bound to elements) from the abstraction layer (such as the logic that decorates the page UI).

Here’s another example:

If we were still in the League of Legends world, every game would eventually have an ending, and whether it was Victory or Defeat, a window would pop up telling you what Victory or Defeat was.

function GameMessage (type) { // A bridge between abstraction and implementation
    this.fn = type ? new Victory() : new Defeat()
}
GameMessage.prototype.show = function() {
    this.fn.show()
}

function Defeat() { / / abstraction layer
    this.show = function() {
        console.log('im loser')}}function Victory() { / / abstraction layer
    this.show = function() {
        console.log('im winner')}}/ / implementation layer
function getResult() {
    var switchVD = Math.ceil(Math.random()*10) > 5 // The battle is 50-50
    return new GameMessage(switchVD)
}
var result1 = getResult()
var result2 = getResult()
var result3 = getResult()
result1.show()
result2.show()
result3.show()

Copy the code

First we create a GameMessage function. We all know that winning or losing has a 50/50 chance, so we define the switchVD variable, simulate a random event, and call the getResult function once for each result to get the latest result.

The bridge pattern is embodied in the GameMessage function, which decouples the abstract Victory() and Defeat() from the getResult() where we get the result. Functions don’t mix logic with each other, but they are connected by bridge functions.

The advantage of writing this way is that both can change independently without disturbing each other. After all, if you put them all together, the logic might be as follows:

function Defeat() { / / abstraction layer
    this.show = function() {
        console.log('im loser')}}function Victory() { / / abstraction layer
    this.show = function() {
        console.log('im winner')}}var switchVD = Math.ceil(Math.random()*10) > 5
if (switchVD) {
    var result =  new Victory()
} else {
    var result =  new Defeat()
}

result.show() // loser or winner
Copy the code

The code above makes it easy to see that, without the bridge mode, blending the implementation layer and rendering layer directly together is context-dependent. If you don’t get the context, it’s easy to have problems.

summary

The bridge pattern is often used inadvertently in daily development to make the code structure clear and decouple the different logic of the code from each other. Easy to maintain in the future, development is also more able to distinguish modules, comfortable to see, natural efficiency is also high.

The key to the bridge pattern is to understand the separation of the abstract and the implementation so that they can change independently without being stuck in the formal. Flexible changes and changeable scenarios are very suitable for using this mode to achieve. The bridge pattern is all about finding different latitudes of variation in your code.

The state pattern

Introduction to the

The State mode allows an object to change its behavior when its internal State changes. The object appears to modify its class. In fact, it is an object or array to record a set of states, each state corresponds to an implementation, implementation according to the state to run the implementation.

Advantages:

  1. A state corresponds to a behavior, intuitive and clear, easy to change.
  2. States and actions are independent of each other.
  3. Avoid excessive object condition statements.
  4. Do not execute unnecessary judgment statements.

Disadvantages:

  1. The need to separate out the different states of things and their corresponding behaviors can sometimes lead to over-design.
  2. It would increase the number of object classes and action classes, and the action classes would be divided into several classes according to a single principle, which would confuse the code.

For example, let’s define A hero’s state, named Yasuo, in which Yasuo may have several states at the same time, such as walking and attacking — commonly known as “walking A”, and may release skills and then follow A “B key home” operation. Of course, the most likely operation is eQW flash R smoothly and obtain A head. Another CTRL + F6 and so on.

Not only would it be ugly to have multiple if-else or switches for each of these operations, but the implementation would be more redundant when combined actions are encountered. So here we have complex operations that can be implemented using state patterns.

The idea of the state pattern is to create a state object or array and store the array or objects that need to be manipulated inside the object. Then the state object provides some interface for changing the state and performing actions.

Now there’s a hero named Yasuo! The following code, we use yasso’s state to achieve the legendary state mode:

function YasuoState() {
    // Store the current state of the action to be performed!
    this.currentstate = [];

    this.Actions = {
        walk : function(){
            console.log('walk');
        },
        attack : function(){
            console.log('attack');
        },
        magic : function(){
            console.log('magic');
        },
        backhome : function(){
            console.log('backhome'); }}; } YasuoState.prototype.changeState =function() {
    // Clear the current action
    this.currentstate = [];
    Object.keys(arguments).forEach((i) = > this.currentstate.push(arguments[i]))
    return this;
}
YasuoState.prototype.YasuoActions = function() {
    // The actions in the current action set are executed sequentially
    this.currentstate.forEach((k) = > this.Actions[k] && this.Actions[k]())
    return this;
}


  
var yasuoState = new YasuoState();
  
yasuoState.changeState('walk'.'attack').YasuoActions().changeState('walk').YasuoActions().YasuoActions();
Copy the code

The code above successfully implements Yasso’s state mode. Let’s assume that he has several states: walking, attacking, releasing skills, and returning home. These states can be entered at the same time, otherwise the professional player’s highlight operation will appear in the skill convergence and die.

The most common example of state mode is the everyday example of a traffic light, which performs an action every time it switches states.

As for League of Legends, the most common is the walking attack, after entering the command, first changes the state of our object yasuostate.changestate (‘magic’,’backhome’), and then because in the code there is return this; , we can chain-call the following behavior, so we let it execute the entered state in turn. Next, the state changeState(‘walk’) is changed again and executed. As you can see, it’s executed twice, and since the state doesn’t change again, it only needs to be repeated to keep our hero moving forward.

Hopefully state mode will help you with the vast majority of operations that require state switching. When you encounter similar problems, you can quickly come up with a mature and reliable state mode to solve them.

conclusion

All three modes mentioned in this post can be found in League of Legends, and since I love this game, it is easy to find the design modes used:

  • Policy pattern — Behavior pattern, which is used to facilitate scale-up when the business requires many kinds of judgment, or even composite calculations.
  • Bridge pattern — Structural pattern, often encountered when building objects that require frequent extension of functionality.
  • State patterns — Behavior patterns that expect business to change in response to state changes. The most common real-world example is traffic lights.

Design patterns can help us solve the design problems of code in development, so how do we find the right objects and apply the right design patterns?

Borrow a few hints from the book:

  • Find the right match

  • Determines the granularity of the object

  • Decide on the interface for this object design

  • Implement the specific functions that the object needs

  • Use code reuse mechanisms properly

  • Code should be designed to support and anticipate change

These are probably the few scenarios that involve compilation in javascript, so I won’t go into them.

Design pattern is a solution to the common problems faced by software developers during software development. These solutions have been developed by numerous software developers over a long period of trial and error.

According to the above rules, most code design problems can be avoided by paying attention when developing interfaces and functions, which will be a huge help in future maintenance. The next person who accepts the code will also be very grateful to you, reading code is actually like reading a book, you may be lazy to write code now, the person who takes over will be crazy jokes. On the contrary, if you elegant implementation, like me, will be heartfelt admiration, see neat function, annotation clear function, have to say, master is really master ah, just 200 lines, let a person kneel, highlight a word – elegant.

Relevant reference

  • Uncle Tom blog
  • Design Reusable Design Patterns