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.
10, Responsibility chain model
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
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
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
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
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
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
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
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