Design patterns in js

reference resources:

https://www.cnblogs.com/imwtr/p/9451129.html (15 common design modes)

Design principles

Single responsibility principle (SRP)

An object or method does only one thing. If a method assumes too many responsibilities, the more likely it is to rewrite the method in the process of demand change.

Objects or methods should be divided into smaller granularity

Least knowledge principle (LKP)

A software entity should interact as little as possible with other entities

Interaction between objects should be minimized. If two objects do not need to communicate with each other directly, the two objects do not need to be directly related to each other and can be handed over to a third party for processing

Open closed principle (OCP)

Software entities (classes, modules, functions, etc.) should be extensible, but not modifiable

When you need to change the function of a program or add new functions to the program, you can use the way of adding code to avoid changing the source code of the program as far as possible and prevent affecting the stability of the original system

 

What is a design pattern

The author's explanation is very good

Suppose there is an empty room, we should put something in it day after day. Of course, the simplest way is to throw these things directly, but over time, you will find it difficult to find what you want from this house, and it is not easy to adjust the position of some things. So it may be a better choice to make some cabinets in the room. Although the cabinet will increase our cost, it can bring benefits to us in the maintenance stage. The rule of using these cabinets to store things may be a model

Learning design patterns is helpful to write reusable and maintainable programs

The principle of design pattern is to "find out the changes in the program and encapsulate the changes". Its key is intention, not structure.

However, it should be noted that improper use may get twice the result with half the effort.

 

1, Singleton mode

2, Strategy mode

3, Agent mode

4, Iterator mode

5, Publish subscribe mode

6, Command mode

7, Combination mode

8, Template method mode

9, Sharing mode

10, Responsibility chain model

11, Intermediary model

12, Decorator mode

13, State mode

14, Adapter mode

15, Appearance mode

 

1, Singleton mode

1. Definitions

Ensure that a class has only one instance and provide a global access point to access it

2. Core

Ensure that there is only one instance and provide global access

3. Realization

Suppose you want to set an administrator and only set it once for multiple calls. We can use closure cache to cache an internal variable to implement this singleton

function SetManager(name) {
    this.manager = name;
}

SetManager.prototype.getName = function() {
    console.log(this.manager);
};

var SingletonSetManager = (function() {
    var manager = null;

    return function(name) {
        if (!manager) {
            manager = new SetManager(name);
        }

        return manager;
    } 
})();

SingletonSetManager('a').getName(); // a
SingletonSetManager('b').getName(); // a
SingletonSetManager('c').getName(); // a

This is a relatively simple approach, but what if we need to set up an HR? You have to copy the code

Therefore, you can rewrite the inner of a singleton to make it more general

// Extract the general single example
function getSingleton(fn) {
    var instance = null;

    return function() {
        if (!instance) {
            instance = fn.apply(this, arguments);
        }

        return instance;
    }
}

Call again, and the result is the same

// Get singleton
var managerSingleton = getSingleton(function(name) {
    var manager = new SetManager(name);
    return manager;
});

managerSingleton('a').getName(); // a
managerSingleton('b').getName(); // a
managerSingleton('c').getName(); // a

At this time, when we add HR, we don't need to change the internal implementation of the acquisition singleton. We just need to implement what needs to be done to add HR and call it again

function SetHr(name) {
    this.hr = name;
}

SetHr.prototype.getName = function() {
    console.log(this.hr);
};

var hrSingleton = getSingleton(function(name) {
    var hr = new SetHr(name);
    return hr;
});

hrSingleton('aa').getName(); // aa
hrSingleton('bb').getName(); // aa
hrSingleton('cc').getName(); // aa

Or, if you just want to create a div layer, you don't need to instantiate the object and call the function directly

The result is that there is only the first div created in the page

function createPopup(html) {
    var div = document.createElement('div');
    div.innerHTML = html;
    document.body.append(div);

    return div;
}

var popupSingleton = getSingleton(function() {
    var div = createPopup.apply(this, arguments);
    return div;
});

console.log(
    popupSingleton('aaa').innerHTML,
    popupSingleton('bbb').innerHTML,
    popupSingleton('bbb').innerHTML
); // aaa  aaa  aaa

 

2, Strategy mode

1. Definitions

Define a series of algorithms, encapsulate them one by one, and make them interchangeable.

2. Core

Separate the use of the algorithm from the implementation of the algorithm.

A policy based program consists of at least two parts:

The first part is a group of policy classes, which encapsulates the specific algorithm and is responsible for the specific calculation process.

The second part is the environment class Context, which accepts the customer's request and then delegates the request to a policy class. To achieve this, it is necessary to maintain a reference to a policy object in the Context

3. Realization

The policy pattern can be used to combine a series of algorithms or a series of business rules

Suppose that the final score of students needs to be calculated through the grade, and each grade has a corresponding weighted value. We can directly define this group policy in the form of object literals

// Weighted mapping relation
var levelMap = {
    S: 10,
    A: 8,
    B: 6,
    C: 4
};

// Group Policy
var scoreLevel = {
    basicScore: 80,

    S: function() {
        return this.basicScore + levelMap['S']; 
    },

    A: function() {
        return this.basicScore + levelMap['A']; 
    },

    B: function() {
        return this.basicScore + levelMap['B']; 
    },

    C: function() {
        return this.basicScore + levelMap['C']; 
    }
}

// call
function getScore(level) {
    return scoreLevel[level] ? scoreLevel[level]() : 0;
}

console.log(
    getScore('S'),
    getScore('A'),
    getScore('B'),
    getScore('C'),
    getScore('D')
); // 90 88 86 84 0

In the aspect of combining business rules, the classic method is the form verification method. Here are the key parts

// Error prompt
var errorMsgs = {
    default: 'The input data format is incorrect',
    minLength: 'Insufficient length of input data',
    isNumber: 'please enter a number',
    required: 'Content cannot be empty'
};

// Rule set
var rules = {
    minLength: function(value, length, errorMsg) {
        if (value.length < length) {
            return errorMsg || errorMsgs['minLength']
        }
    },
    isNumber: function(value, errorMsg) {
        if (!/\d+/.test(value)) {
            return errorMsg || errorMsgs['isNumber'];
        }
    },
    required: function(value, errorMsg) {
        if (value === '') {
            return errorMsg || errorMsgs['required'];
        }
    }
};

// Calibrator
function Validator() {
    this.items = [];
};

Validator.prototype = {
    constructor: Validator,
    
    // Add verification rule
    add: function(value, rule, errorMsg) {
        var arg = [value];

        if (rule.indexOf('minLength') !== -1) {
            var temp = rule.split(':');
            arg.push(temp[1]);
            rule = temp[0];
        }

        arg.push(errorMsg);

        this.items.push(function() {
            // Check
            return rules[rule].apply(this, arg);
        });
    },
    
    // Start verification
    start: function() {
        for (var i = 0; i < this.items.length; ++i) {
            var ret = this.items[i]();
            
            if (ret) {
                console.log(ret);
                // return ret;
            }
        }
    }
};

// test data
function testTel(val) {
    return val;
}

var validate = new Validator();

validate.add(testTel('ccc'), 'isNumber', 'Can only be numbers'); // Can only be numbers
validate.add(testTel(''), 'required'); // Content cannot be empty
validate.add(testTel('123'), 'minLength:5', 'Minimum 5 digits'); // Minimum 5 digits
validate.add(testTel('12345'), 'minLength:5', 'Minimum 5 digits');

var ret = validate.start();

console.log(ret);

4. Advantages and disadvantages

advantage

It can effectively avoid multiple conditional statements and encapsulate a series of methods, which is more intuitive and convenient for maintenance

shortcoming

There are often many strategy meetings. We need to know and define all the situations in advance

 

3, Agent mode

1. Definitions

Provide a substitute or placeholder for an object to control access to it

2. core

When the customer is inconvenient to directly access an object or does not meet the needs, a surrogate object is provided to control the access to the object. In fact, the customer accesses the surrogate object.

After the avatar object does some processing to the request, it transfers the request to the ontology object

The interface between agent and ontology is consistent. Ontology defines key functions, and agent provides or denies access to it, or does some additional things before accessing ontology

3. Realization

There are three main proxy modes: protection proxy, virtual proxy and cache proxy

The protection agent mainly realizes the restriction behavior of access subject, and takes filtering characters as a simple example

// Body, send message
function sendMsg(msg) {
    console.log(msg);
}

// Agent to filter messages
function proxySendMsg(msg) {
    // Return directly if there is no message
    if (typeof msg === 'undefined') {
        console.log('deny');
        return;
    }
    
    // If there is a message, filter it
    msg = ('' + msg).replace(/mud\s*coal/g, '');

    sendMsg(msg);
}


sendMsg('Peat! Peat'); // Peat! Peat
proxySendMsg('Peat! Peat'); // ah
proxySendMsg(); // deny

Its intention is obvious. It controls before accessing the main body. When there is no message, it returns directly in the agent and refuses to access the main body. This is in the form of data protection agent

Sensitive characters are processed when there is a message, which belongs to the mode of virtual agent

 

The virtual agent adds some additional operations when controlling the access to the principal

When rolling events are triggered, it may not need to be triggered frequently. We can introduce function throttling, which is an implementation of virtual agent

// The function is anti shake. It is not processed in frequent operations until the operation is completed (after the delay time)
function debounce(fn, delay) {
    delay = delay || 200;
    
    var timer = null;

    return function() {
        var arg = arguments;
          
        // Clear the last timer at each operation
        clearTimeout(timer);
        timer = null;
        
        // Define a new timer and operate it after a period of time
        timer = setTimeout(function() {
            fn.apply(this, arg);
        }, delay);
    }
};

var count = 0;

// subject
function scrollHandle(e) {
    console.log(e.type, ++count); // scroll
}

// agent
var proxyScrollHandle = (function() {
    return debounce(scrollHandle, 500);
})();

window.onscroll = proxyScrollHandle;

 

The overhead of cache can improve the efficiency of proxy operation

Let's have a chestnut and cache the addition operation

// subject
function add() {
    var arg = [].slice.call(arguments);

    return arg.reduce(function(a, b) {
        return a + b;
    });
}

// agent
var proxyAdd = (function() {
    var cache = [];

    return function() {
        var arg = [].slice.call(arguments).join(',');
        
        // If so, it is returned directly from the cache
        if (cache[arg]) {
            return cache[arg];
        } else {
            var ret = add.apply(this, arguments);
            return ret;
        }
    };
})();

console.log(
    add(1, 2, 3, 4),
    add(1, 2, 3, 4),

    proxyAdd(10, 20, 30, 40),
    proxyAdd(10, 20, 30, 40)
); // 10 10 100 100

 

4, Iterator mode

1. Definitions

The iterator pattern refers to providing a way to access the elements of an aggregate object sequentially without exposing the internal representation of the object.

2. core

After using the iterator pattern, you can access each element in order, even if you don't care about the internal structure of the object

3. Realization

The map forEach of the array in JS has built-in iterators

[1, 2, 3].forEach(function(item, index, arr) {
    console.log(item, index, arr);
});

However, for object traversal, it is often impossible to use the same traversal code as array

We can encapsulate it

function each(obj, cb) {
    var value;

    if (Array.isArray(obj)) {
        for (var i = 0; i < obj.length; ++i) {
            value = cb.call(obj[i], i, obj[i]);

            if (value === false) {
                break;
            }
        }
    } else {
        for (var i in obj) {
            value = cb.call(obj[i], i, obj[i]);

            if (value === false) {
                break;
            }
        }
    }
}

each([1, 2, 3], function(index, value) {
    console.log(index, value);
});

each({a: 1, b: 2}, function(index, value) {
    console.log(index, value);
});

// 0 1
// 1 2
// 2 3

// a 1
// b 2

Let's look at another example, forcibly using iterators, to see that iterators can also replace frequent conditional statements

Although the example is not very good, it is also worth considering in the case of other responsible branch judgments

function getManager() {
    var year = new Date().getFullYear();

    if (year <= 2000) {
        console.log('A');
    } else if (year >= 2100) {
        console.log('C');
    } else {
        console.log('B');
    }
}

getManager(); // B

Split each conditional statement into logical functions and put them into iterators for iteration

function year2000() {
    var year = new Date().getFullYear();

    if (year <= 2000) {
        console.log('A');
    }

    return false;
}

function year2100() {
    var year = new Date().getFullYear();

    if (year >= 2100) {
        console.log('C');
    }

    return false;
}

function year() {
    var year = new Date().getFullYear();

    if (year > 2000 && year < 2100) {
        console.log('B');
    }

    return false;
}

function iteratorYear() {
    for (var i = 0; i < arguments.length; ++i) {
        var ret = arguments[i]();

        if (ret !== false) {
            return ret;
        }
    }
}

var manager = iteratorYear(year2000, year2100, year); // B

 

5, Publish subscribe mode

1. Definitions

Also known as observer pattern, it defines a one to many dependency between objects. When the state of an object changes, all objects that depend on it will be notified

2. core

Instead of the hard coded notification mechanism between objects, an object does not explicitly call an interface of another object.

Different from the traditional implementation of publish subscribe mode (the subscriber itself is passed into the publisher as a reference), JS usually uses the form of registered callback function to subscribe

3. Realization

Events in JS are the implementation of the classic publish subscribe mode

// subscribe
document.body.addEventListener('click', function() {
    console.log('click1');
}, false);

document.body.addEventListener('click', function() {
    console.log('click2');
}, false);

// release
document.body.click(); // click1  click2

Realize it yourself

Small A completed the written examination and interview in company C, and small B also completed the written examination in company C. They anxiously wait for the result and call company C every half day, which makes company C very impatient.

One solution is that AB directly leaves the contact information to C. if there is any result, C will naturally notify AB

"Query" here belongs to display call, "leave" belongs to subscription, and "notification" belongs to publication

// Observer
var observer = {
    // Subscription collection
    subscribes: [],

    // subscribe
    subscribe: function(type, fn) {
        if (!this.subscribes[type]) {
            this.subscribes[type] = [];
        }
        
        // Collect subscriber processing
        typeof fn === 'function' && this.subscribes[type].push(fn);
    },

    // Publishing may carry some information to publish
    publish: function() {
        var type = [].shift.call(arguments),
            fns = this.subscribes[type];
        
        // The subscription type that does not exist, and the number of processing callbacks that were not passed in at the time of subscription
        if (!fns || !fns.length) {
            return;
        }
        
        // Process calls one by one
        for (var i = 0; i < fns.length; ++i) {
            fns[i].apply(this, arguments);
        }
    },
    
    // Delete subscription
    remove: function(type, fn) {
        // Delete all
        if (typeof type === 'undefined') {
            this.subscribes = [];
            return;
        }

        var fns = this.subscribes[type];

        // The subscription type that does not exist, and the number of processing callbacks that were not passed in at the time of subscription
        if (!fns || !fns.length) {
            return;
        }

        if (typeof fn === 'undefined') {
            fns.length = 0;
            return;
        }

        // Process deletion one by one
        for (var i = 0; i < fns.length; ++i) {
            if (fns[i] === fn) {
                fns.splice(i, 1);
            }
        }
    }
};

// Subscribe to job list
function jobListForA(jobs) {
    console.log('A', jobs);
}

function jobListForB(jobs) {
    console.log('B', jobs);
}

// A subscribed to the written test results
observer.subscribe('job', jobListForA);
// B subscribed to the written test results
observer.subscribe('job', jobListForB);


// A subscribed to the written test results
observer.subscribe('examinationA', function(score) {
    console.log(score);
});

// B subscribed to the written test results
observer.subscribe('examinationB', function(score) {
    console.log(score);
});

// A subscribed to the interview results
observer.subscribe('interviewA', function(result) {
    console.log(result);
});

observer.publish('examinationA', 100); // 100
observer.publish('examinationB', 80); // 80
observer.publish('interviewA', 'spare'); // spare

observer.publish('job', ['front end', 'back-end', 'test']); // Posts exporting A and B


// B unsubscribed from the written examination results
observer.remove('examinationB');
// A has cancelled his subscription
observer.remove('job', jobListForA);

observer.publish('examinationB', 80); // No matching subscriptions, no output
observer.publish('job', ['front end', 'back-end', 'test']); // Post of output B

4. Advantages and disadvantages

advantage

One is decoupling in time, and the other is decoupling between objects. It can be used in asynchronous programming and MV * framework

shortcoming

Creating a subscriber itself consumes a certain amount of time and memory. The subscription processing function may not be executed, and the resident memory has performance overhead

It weakens the relationship between objects, which may make the program difficult to track, maintain and understand in complex situations

 

6, Command mode

1. Definitions

The program is designed in a loose coupling way, so that the request sender and the request receiver can eliminate the coupling relationship between each other

A command is an instruction that performs certain things

2. core

The command contains execute execute, undo undo, redo redo and other related command methods. It is recommended to indicate the names of these methods

3. Realization

Simple command mode implementation can directly define a command in the form of object literals

var incrementCommand = {
    execute: function() {
        // something
    }
};

However, the following example is a self increment command, which provides the functions of execution, revocation and redo

This self increment is defined in the way of object creation and processing

// Self increasing
function IncrementCommand() {
    // Current value
    this.val = 0;
    // Command stack
    this.stack = [];
    // Stack pointer position
    this.stackPosition = -1;
};

IncrementCommand.prototype = {
    constructor: IncrementCommand,

    // implement
    execute: function() {
        this._clearRedo();
        
        // Define the processing to be performed
        var command = function() {
            this.val += 2;
        }.bind(this);
        
        // Execute and cache
        command();
        
        this.stack.push(command);

        this.stackPosition++;

        this.getValue();
    },
    
    canUndo: function() {
        return this.stackPosition >= 0;
    },
    
    canRedo: function() {
        return this.stackPosition < this.stack.length - 1;
    },

    // revoke
    undo: function() {
        if (!this.canUndo()) {
            return;
        }
        
        this.stackPosition--;

        // The revocation of a command is the opposite of the processing performed
        var command = function() {
            this.val -= 2;
        }.bind(this);
        
        // Cache is not required after revocation
        command();

        this.getValue();
    },
    
    // redo
    redo: function() {
        if (!this.canRedo()) {
            return;
        }
        
        // Execute the command at the top of the stack
        this.stack[++this.stackPosition]();

        this.getValue();
    },
    
    // During execution, the revoked part cannot be redone
    _clearRedo: function() {
        this.stack = this.stack.slice(0, this.stackPosition + 1);
    },
    
    // Get current value
    getValue: function() {
        console.log(this.val);
    }
};

Then instantiate for testing, and simulate the operations of execution, revocation and redo

var incrementCommand = new IncrementCommand();

// Simulate the event trigger and execute the command
var eventTrigger = {
    // In the processing of an event, the processing method of the command is called directly
    increment: function() {
        incrementCommand.execute();
    },

    incrementUndo: function() {
        incrementCommand.undo();
    },

    incrementRedo: function() {
        incrementCommand.redo();
    }
};


eventTrigger['increment'](); // 2
eventTrigger['increment'](); // 4

eventTrigger['incrementUndo'](); // 2

eventTrigger['increment'](); // 4

eventTrigger['incrementUndo'](); // 2
eventTrigger['incrementUndo'](); // 0
eventTrigger['incrementUndo'](); // No output

eventTrigger['incrementRedo'](); // 2
eventTrigger['incrementRedo'](); // 4
eventTrigger['incrementRedo'](); // No output

eventTrigger['increment'](); // 6

 

In addition, you can implement simple macro commands (a set of commands)

var MacroCommand = {
    commands: [],

    add: function(command) {
        this.commands.push(command);

        return this;
    },

    remove: function(command) {
        if (!command) {
            this.commands = [];
            return;
        }

        for (var i = 0; i < this.commands.length; ++i) {
            if (this.commands[i] === command) {
                this.commands.splice(i, 1);
            }
        }
    },

    execute: function() {
        for (var i = 0; i < this.commands.length; ++i) {
            this.commands[i].execute();
        }
    }
};

var showTime = {
    execute: function() {
        console.log('time');
    }
};

var showName = {
    execute: function() {
        console.log('name');
    }
};

var showAge = {
    execute: function() {
        console.log('age');
    }
};

MacroCommand.add(showTime).add(showName).add(showAge);

MacroCommand.remove(showName);

MacroCommand.execute(); // time age

 

7, Combination mode

1. Definitions

Is to use small sub objects to build larger objects, and these small sub objects themselves may be composed of smaller "grandchildren".

2. core

This "part whole" hierarchy can be represented by tree structure.

Call the execute method of the composite object, and the program will recursively call the execute method of the leaf object under the composite object

However, it should be noted that the combination mode is not a parent-child relationship. It is an HAS-A (aggregation) relationship that delegates the request to all leaf objects it contains. The combination of objects and leaves needs to have the same interface

In addition, ensure that each leaf object in the list is treated in a consistent way, that is, the leaf objects belong to the same class and do not require too many special additional operations

3. Realization

Use combination mode to scan files in folders

// Folder group object
function Folder(name) {
    this.name = name;
    this.parent = null;
    this.files = [];
}

Folder.prototype = {
    constructor: Folder,

    add: function(file) {
        file.parent = this;
        this.files.push(file);

        return this;
    },

    scan: function() {
        // Delegate to leaf object processing
        for (var i = 0; i < this.files.length; ++i) {
            this.files[i].scan();
        }
    },

    remove: function(file) {
        if (typeof file === 'undefined') {
            this.files = [];
            return;
        }

        for (var i = 0; i < this.files.length; ++i) {
            if (this.files[i] === file) {
                this.files.splice(i, 1);
            }
        }
    }
};

// File leaf object
function File(name) {
    this.name = name;
    this.parent = null;
}

File.prototype = {
    constructor: File,

    add: function() {
        console.log('Files cannot be added to the file');
    },

    scan: function() {
        var name = [this.name];
        var parent = this.parent;

        while (parent) {
            name.unshift(parent.name);
            parent = parent.parent;
        }

        console.log(name.join(' / '));
    }
};

After constructing the relationship between the composite object and the leaf object, instantiate and insert the composite or leaf object into the composite object

var web = new Folder('Web');
var fe = new Folder('front end');
var css = new Folder('CSS');
var js = new Folder('js');
var rd = new Folder('back-end');

web.add(fe).add(rd);

var file1 = new File('HTML Authoritative guide.pdf');
var file2 = new File('CSS Authoritative guide.pdf');
var file3 = new File('JavaScript Authoritative guide.pdf');
var file4 = new File('MySQL Basics.pdf');
var file5 = new File('Web security.pdf');
var file6 = new File('Linux green hand.pdf');

css.add(file2);
fe.add(file1).add(file3).add(css).add(js);
rd.add(file4).add(file5);
web.add(file6);

rd.remove(file4);

// scanning
web.scan();

The scanning result is

 

 4. Advantages and disadvantages

advantage

It is convenient to construct a tree to represent the partial overall structure of the object. After the construction of the tree is finally completed, the whole tree can be operated uniformly only by requesting the top-level object of the tree.

shortcoming

The created objects are all similar in length, which may make the code difficult to understand. Creating too many objects will also have some impact on performance

 

8, Template method mode

1. Definitions

The template method pattern consists of two parts. The first part is the abstract parent class and the second part is the concrete implementation subclass.

2. core

The algorithm framework encapsulating subclasses in the abstract parent class. Its init method can be used as an algorithm template to guide subclasses to execute which methods in what order.

The public part is separated from the parent class, and the child class is required to override the (changeable) abstract methods of some parent classes

3. Realization

The general implementation of template method pattern is inheritance

Taking motion as an example, motion has some general processing, which can be extracted and implemented in the parent class. The particularity of a specific sport has its own class to rewrite and implement.

Finally, the subclass directly calls the template function of the parent class to execute

// athletic sports
function Sport() {

}

Sport.prototype = {
    constructor: Sport,
    
    // Templates, in order
    init: function() {
        this.stretch();
        this.jog();
        this.deepBreath();
        this.start();

        var free = this.end();
        
        // If you have time after exercise, stretch it
        if (free !== false) {
            this.stretch();
        }
        
    },
    
    // stretching
    stretch: function() {
        console.log('stretching');
    },
    
    // jogging
    jog: function() {
        console.log('jogging');
    },
    
    // deep breathing
    deepBreath: function() {
        console.log('deep breathing');
    },

    // Start moving
    start: function() {
        throw new Error('Subclasses must override this method');
    },

    // End movement
    end: function() {
        console.log('End of movement');
    }
};

// Basketball
function Basketball() {

}

Basketball.prototype = new Sport();

// Rewrite related methods
Basketball.prototype.start = function() {
    console.log('Throw a few three points first');
};

Basketball.prototype.end = function() {
    console.log('The exercise is over. If you have something to do, take a step first');
    return false;
};


// marathon
function Marathon() {

}

Marathon.prototype = new Sport();

var basketball = new Basketball();
var marathon = new Marathon();

// Subclass calls will eventually be executed in the order defined by the parent class
basketball.init();
marathon.init();

 

9, Sharing mode

1. Definitions

flyweight mode is a mode for performance optimization. Its goal is to minimize the number of shared objects

2. core

Using sharing technology to effectively support a large number of fine-grained objects.

It is emphasized that the attributes of objects are divided into internal state (attribute) and external state (attribute). Internal state is used for object sharing, which is usually unchanged; The external state is stripped away and determined by the specific scene.

3. Realization

When a large number of similar objects are used in the program, the sharing mode can be used to optimize and reduce the number of objects

Take chestnuts for example. We should measure the physical quality of a class, and judge it only by measuring height and weight

// Health measurement
function Fitness(name, sex, age, height, weight) {
    this.name = name;
    this.sex = sex;
    this.age = age;
    this.height = height;
    this.weight = weight;
}

// Start judging
Fitness.prototype.judge = function() {
    var ret = this.name + ': ';

    if (this.sex === 'male') {
        ret += this.judgeMale();
    } else {
        ret += this.judgeFemale();
    }

    console.log(ret);
};

// Male judgment rules
Fitness.prototype.judgeMale = function() {
    var ratio = this.height / this.weight;

    return this.age > 20 ? (ratio > 3.5) : (ratio > 2.8);
};

// Women's judgment rules
Fitness.prototype.judgeFemale = function() {
    var ratio = this.height / this.weight;
    
    return this.age > 20 ? (ratio > 4) : (ratio > 3);
};


var a = new Fitness('A', 'male', 18, 160, 80);
var b = new Fitness('B', 'male', 21, 180, 70);
var c = new Fitness('C', 'female', 28, 160, 80);
var d = new Fitness('D', 'male', 18, 170, 60);
var e = new Fitness('E', 'female', 18, 160, 40);

// Start judging
a.judge(); // A: false
b.judge(); // B: false
c.judge(); // C: false
d.judge(); // D: true
e.judge(); // E: true

To judge five people, you need to create five objects, and a class has dozens of objects

You can extract the common part (internal state) of an object, which is independent of the external state. Gender can be regarded as an internal state, and other attributes belong to an external state.

In this way, we only need to maintain the male and female objects (using the factory object), while the other changed parts are maintained externally (using the manager object)

// Health measurement
function Fitness(sex) {
    this.sex = sex;
}

// Factory, creating shareable objects
var FitnessFactory = {
    objs: [],

    create: function(sex) {
        if (!this.objs[sex]) {
            this.objs[sex] = new Fitness(sex);
        }

        return this.objs[sex];
    }
};

// Manager, managing unshared parts
var FitnessManager = {
    fitnessData: {},
    
    // Add an item
    add: function(name, sex, age, height, weight) {
        var fitness = FitnessFactory.create(sex);
        
        // Store changing data
        this.fitnessData[name] = {
            age: age,
            height: height,
            weight: weight
        };

        return fitness;
    },
    
    // Get from the stored data and update it to the object currently in use
    updateFitnessData: function(name, obj) {
        var fitnessData = this.fitnessData[name];

        for (var item in fitnessData) {
            if (fitnessData.hasOwnProperty(item)) {
                obj[item] = fitnessData[item];
            }
        }
    }
};

// Start judging
Fitness.prototype.judge = function(name) {
    // Update the current status before operation (obtained from the external status manager)
    FitnessManager.updateFitnessData(name, this);

    var ret = name + ': ';

    if (this.sex === 'male') {
        ret += this.judgeMale();
    } else {
        ret += this.judgeFemale();
    }

    console.log(ret);
};

// Male judgment rules
Fitness.prototype.judgeMale = function() {
    var ratio = this.height / this.weight;

    return this.age > 20 ? (ratio > 3.5) : (ratio > 2.8);
};

// Women's judgment rules
Fitness.prototype.judgeFemale = function() {
    var ratio = this.height / this.weight;
    
    return this.age > 20 ? (ratio > 4) : (ratio > 3);
};


var a = FitnessManager.add('A', 'male', 18, 160, 80);
var b = FitnessManager.add('B', 'male', 21, 180, 70);
var c = FitnessManager.add('C', 'female', 28, 160, 80);
var d = FitnessManager.add('D', 'male', 18, 170, 60);
var e = FitnessManager.add('E', 'female', 18, 160, 40);

// Start judging
a.judge('A'); // A: false
b.judge('B'); // B: false
c.judge('C'); // C: false
d.judge('D'); // D: true
e.judge('E'); // E: true

However, the code may be more complex. This example may not be sufficient. It just shows how to implement the meta pattern. It saves many similar objects, but some operations.

The factory object is a bit like the singleton mode, except that there is an additional sex parameter. If there is no internal state, the factory object without parameters is closer to the singleton mode

 

10, Responsibility chain model

1. Definitions

Make multiple objects have the opportunity to process the request, so as to avoid the coupling relationship between the sender and receiver of the request. Connect these objects into a chain and pass the request along the chain until one object processes it

2. core

The request sender only needs to know the first node in the chain, weaken the strong connection between the sender and a group of receivers, and can easily add or delete a node in the responsibility chain. Similarly, it is also convenient to specify who is the first node

3. Realization

Taking showing different types of variables as an example, setting a responsibility chain can eliminate multiple if conditional branches

// Define an item in the chain
function ChainItem(fn) {
    this.fn = fn;
    this.next = null;
}

ChainItem.prototype = {
    constructor: ChainItem,
    
    // Set next
    setNext: function(next) {
        this.next = next;
        return next;
    },
    
    // Start execution
    start: function() {
        this.fn.apply(this, arguments);
    },
    
    // Go to the next execution in the chain
    toNext: function() {
        if (this.next) {
            this.start.apply(this.next, arguments);
        } else {
            console.log('No matching execution items');
        }
    }
};

// Show numbers
function showNumber(num) {
    if (typeof num === 'number') {
        console.log('number', num);
    } else {
        // Move to next item
        this.toNext(num);
    }
}

// Display string
function showString(str) {
    if (typeof str === 'string') {
        console.log('string', str);
    } else {
        this.toNext(str);
    }
}

// Display object
function showObject(obj) {
    if (typeof obj === 'object') {
        console.log('object', obj);
    } else {
        this.toNext(obj);
    }
}

var chainNumber = new ChainItem(showNumber);
var chainString = new ChainItem(showString);
var chainObject = new ChainItem(showObject);

// Set chain
chainObject.setNext(chainNumber).setNext(chainString);

chainString.start('12'); // string 12
chainNumber.start({}); // No matching execution items
chainObject.start({}); // object {}
chainObject.start(123); // number 123

At this time, if you want to judge whether it is undefined, you can directly add it to the chain

// Display undefined
function showUndefined(obj) {
    if (typeof obj === 'undefined') {
        console.log('undefined');
    } else {
        this.toNext(obj);
    }
}

var chainUndefined = new ChainItem(showUndefined);
chainString.setNext(chainUndefined);

chainNumber.start(); // undefined

As can be seen from the example, after using the responsibility chain, the original conditional branches are replaced with many objects. Although the structure is clearer, it may affect the performance to a certain extent. Therefore, pay attention to avoid a long responsibility chain.

 

11, Intermediary model

1. Definitions

All related objects communicate through the mediator object rather than reference each other, so when an object changes, you only need to notify the mediator object

2. core

Make the network many to many relationship into a relatively simple one to many relationship (the complex scheduling processing is handed over to the intermediary)

After using the mediator

 

 

 

 

3. Realization

Multiple objects do not necessarily refer to instantiated objects, but can also be understood as multiple items that are independent of each other. When these items are being processed, they need to be known and processed through the data of other items.

If each item is processed directly, the program will be very complex. If you modify a certain place, you have to modify it within multiple items

We take this process out and encapsulate it into an intermediary. When all items need to be processed, we can notify the intermediary.

var A = {
    score: 10,

    changeTo: function(score) {
        this.score = score;

        // Own acquisition
        this.getRank();
    },
    
    // Direct acquisition
    getRank: function() {
        var scores = [this.score, B.score, C.score].sort(function(a, b) {
            return a < b;
        });

        console.log(scores.indexOf(this.score) + 1);
    }
};

var B = {
    score: 20,

    changeTo: function(score) {
        this.score = score;

        // Obtained through intermediaries
        rankMediator(B);
    }
};

var C = {
    score: 30,

    changeTo: function(score) {
        this.score = score;

        rankMediator(C);
    }
};

// Intermediaries, calculate ranking
function rankMediator(person) {
    var scores = [A.score, B.score, C.score].sort(function(a, b) {
        return a < b;
    });

    console.log(scores.indexOf(person.score) + 1);
}

// A handles it by itself
A.changeTo(100); // 1

// B and C are left to the intermediary
B.changeTo(200); // 1
C.changeTo(50); // 3

After the scores of ABC three people changed, they wanted to know their ranking and handled it by themselves in A, while B and C used intermediaries. B and C will be easier and the overall code will be simpler

Finally, although the mediator has achieved the decoupling of modules and objects, sometimes the relationship between objects does not have to be decoupled. Forcing the mediator to integrate may make the code more cumbersome, which needs attention.

 

12, Decorator mode

1. Definitions

To dynamically add some additional responsibilities to an object without affecting other objects derived from this class.
It is a "pay as you go" method, which can dynamically add responsibilities to objects during program operation without changing the object itself

2. core

It is to dynamically add behavior for objects. After multiple packaging, a decorative chain can be formed

3. Realization

The simplest decorator is to rewrite the properties of an object

var A = {
    score: 10
};

A.score = 'fraction:' + A.score;

Traditional object-oriented methods can be used to realize decoration and add skills

function Person() {}

Person.prototype.skill = function() {
    console.log('mathematics');
};

// Ornaments and music
function MusicDecorator(person) {
    this.person = person;
}

MusicDecorator.prototype.skill = function() {
    this.person.skill();
    console.log('music');
};

// Decorators and running
function RunDecorator(person) {
    this.person = person;
}

RunDecorator.prototype.skill = function() {
    this.person.skill();
    console.log('run');
};

var person = new Person();

// Decorate it
var person1 = new MusicDecorator(person);
person1 = new RunDecorator(person1);

person.skill(); // mathematics
person1.skill(); // Math music running

In JS, functions are first-class objects, so we can also use more general decoration functions

// Decorator, which executes another function before the current function
function decoratorBefore(fn, beforeFn) {
    return function() {
        var ret = beforeFn.apply(this, arguments);
        
        // Judging from the previous function, the current function does not need to be executed
        if (ret !== false) {
            fn.apply(this, arguments);
        }
    };
}


function skill() {
    console.log('mathematics');
}

function skillMusic() {
    console.log('music');
}

function skillRun() {
    console.log('run');
}

var skillDecorator = decoratorBefore(skill, skillMusic);
skillDecorator = decoratorBefore(skillDecorator, skillRun);

skillDecorator(); // Running music mathematics

 

13, State mode

1. Definitions

Changes in the internal state of things often lead to changes in the behavior of things. When processing, you can delegate the processing to the current state object, which will be responsible for rendering its own behavior

2. core

The internal state of each kind of thing is related to the encapsulated state

3. Realization

Take a person's working state as an example, switching between just waking up, spirit and fatigue

// working condition 
function Work(name) {
    this.name = name;
    this.currentState = null;

    // Working status, saved as the corresponding status object
    this.wakeUpState = new WakeUpState(this);
    // spirited
    this.energeticState = new EnergeticState(this);
    // tired
    this.tiredState = new TiredState(this);

    this.init();
}

Work.prototype.init = function() {
    this.currentState = this.wakeUpState;
    
    // Click event to trigger update status
    document.body.onclick = () => {
        this.currentState.behaviour();
    };
};

// Update working status
Work.prototype.setState = function(state) {
    this.currentState = state;
}

// Just woke up
function WakeUpState(work) {
    this.work = work;
}

// Waking behavior
WakeUpState.prototype.behaviour = function() {
    console.log(this.work.name, ':', 'Just woke up, sleep in first');
    
    // After only 2 seconds of sleep, I feel refreshed
    setTimeout(() => {
        this.work.setState(this.work.energeticState);
    }, 2 * 1000);
}

// spirited
function EnergeticState(work) {
    this.work = work;
}

EnergeticState.prototype.behaviour = function() {
    console.log(this.work.name, ':', 'Super spiritual');
    
    // I was sleepy in just one second
    setTimeout(() => {
        this.work.setState(this.work.tiredState);
    }, 1000);
};

// tired
function TiredState(work) {
    this.work = work;
}

TiredState.prototype.behaviour = function() {
    console.log(this.work.name, ':', 'What's the matter? I'm so sleepy');
    
    // Unconsciously, it has just become a waking state Keep cycling
    setTimeout(() => {
        this.work.setState(this.work.wakeUpState);
    }, 1000);
};


var work = new Work('Cao Cao');

Click on the page to trigger the operation of updating the status

 

4. Advantages and disadvantages

advantage

The logic of state switching is distributed in the state class, which is easy to maintain

shortcoming

Multiple state classes are also a disadvantage for performance. This disadvantage can be further optimized by using the meta mode

By dispersing the logic in the state class, it may not be easy to see the change logic of the state machine

 

14, Adapter mode

1. Definitions

It is to solve the problem of incompatible interfaces between two software entities and adapt the incompatible parts

2. core

Solve the problem of mismatch between two existing interfaces

3. Realization

For example, a simple data format conversion adapter

// The format of rendering data is limited to array
function renderData(data) {
    data.forEach(function(item) {
        console.log(item);
    });
}

// Convert and adapt non array
function arrayAdapter(data) {
    if (typeof data !== 'object') {
        return [];
    }

    if (Object.prototype.toString.call(data) === '[object Array]') {
        return data;
    }

    var temp = [];

    for (var item in data) {
        if (data.hasOwnProperty(item)) {
            temp.push(data[item]);
        }
    }

    return temp;
}

var data = {
    0: 'A',
    1: 'B',
    2: 'C'
};

renderData(arrayAdapter(data)); // A B C

 

15, Appearance mode

1. Definitions

Provide a consistent interface for a group of interfaces in the subsystem and define a high-level interface, which makes the subsystem easier to use

2. core

You can access the subsystem by requesting the appearance interface, or you can choose to access the subsystem directly beyond the appearance

3. Realization

In JS, appearance mode can be regarded as a set of functions

// Three processing functions
function start() {
    console.log('start');
}

function doing() {
    console.log('doing');
}

function end() {
    console.log('end');
}

// Appearance function, which unifies some processing to facilitate calling
function execute() {
    start();
    doing();
    end();
}


// Call init to start execution
function init() {
    // The high-level function is called directly here, or you can choose to call the relevant function directly over it
    execute();
}

init(); // start doing end

Tags: Javascript

Posted by tastro on Sun, 17 Apr 2022 19:41:47 +0930