Series catalog:
- [1] — Creative design Patterns
- JavaScript Design Pattern Parsing [2] — Structural design Pattern
- JavaScript Design Pattern Parsing [3] — Behavioral design Patterns
- JavaScript Design Pattern parsing [4] — Decorator pattern, appearance pattern, mediator pattern
The strategy pattern
The strategy pattern is to define a series of algorithms, encapsulate them one by one, and make them interchangeable.
The core of strategy pattern is the separation of algorithm use and algorithm implementation.
A policy – mode program must have at least two parts:
- A group of policy classes that encapsulate specific algorithms and are responsible for specific computation
- Environment class, which receives a request from a client and then delegates the request to a policy class. To do this, a reference to a policy object is maintained in the environment class.
Implement a policy pattern
Suppose we want to implement a scoring system based on ratings
Const levels = {S: 100, A: 90, B: 80, C: 70, D: 60} // Set of strategieslet gradeBaseOnLevel = {
S: () => {
return'The current score is${levels['S']}`
},
A: () => {
return'The current score is${levels['A']}`
},
B: () => {
return'The current score is${levels['B']}`
},
C: () => {
return'The current score is${levels['C']}`
},
D: () => {
return'The current score is${levels['D']}'},} // call the methodfunction getStudentScore(level) {
return levels[level] ? gradeBaseOnLevel[level]() : 0;
}
console.log(getStudentScore('S')); // The current score is 100 console.log(getStudentScore('A')); // The current score is 90 console.log(getStudentScore('B')); // The current score is 80 console.log(getStudentScore('C')); // The current score is 70 console.log(getStudentScore('D')); // The current score is 60Copy the code
The advantages and disadvantages
- Advantages: Multiple conditional statements can be effectively avoided, and a series of methods are more intuitive and easier to maintain
- Disadvantages: There are often many policy groups and we need to know all defined cases in advance
Iterator pattern
The iterator pattern is a method provided by a module to access the elements of a collection object sequentially without exposing the internal representation of the object. The iterator pattern also separates the iterative process from the business logic, allowing access to each element of an object sequentially, even without caring about its internals.
In fact, we have already used a lot of iterator pattern functions, such as JS array map, and forEach already built-in iterators.
[1, 2, 3]. ForEach (function(item, index, arr) {
console.log(item, index, arr);
});
Copy the code
There are two types of simultaneous iterators: inner iterators and outer iterators
- Inner iterator
An inner iterator is very convenient to call without any concern for its internal implementation. At each call, the rules for the iterator are defined, and the inner iterator will not be very clear if different iteration rules are encountered
- External iterator
The external iterator explicitly requests that the next element (the next method) be iterated over. The external iterator increases the complexity of the call, but increases the flexibility of the iterator, allowing us to manually control the iteration process or order. Like a Generator function
Handwriting implements an iterator
We can implement a simple iterator in code:
// Creator class
class Creator {
constructor(list) {
this.list = list;
}
// Create an iterator to iterate over
creatIterator() {
return new Iterator(this); }}// Iterator class
class Iterator {
constructor(creator) {
this.list = creator.list;
this.index = 0;
}
// Determine if traversal is complete
isDone() {
if (this.index >= this.list.length) {
return true
}
return false
}
// Iterate over the operation backwards
next() {
return this.list[this.index++]
}
}
let arr = [1.2.3.4];
let creator = new Creator(arr);
let iterator = creator.creatIterator();
console.log(iterator.list) / / [1, 2, 3, 4]
while(! iterator.isDone()) {console.log(iterator.next());
}
// the result is 1,2,3,4
Copy the code
Iterators in ES6
Ordered data sets in JavaScript include:
- Array
- Map
- Set
- String
- typeArray
- arguments
- NodeList
!!!!! Note that Object does not belong to an ordered data set
Each of the above ordered data sets deploys the symbol. iterator property, whose value is a function that returns an iterator that deploys the next method to traverse the child elements in order
Take an array object for example:
let array = [1.2.3];
let iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: undefined, done: true}
Copy the code
conclusion
- Simultaneous iterators can be divided into two types: internal iterator and external iterator. The internal iterator is easy to operate, and the external iterator has strong controllability.
- Any deployment
[Symbol.iterator]
The data of the interface can be usedfor of
Cycle. - The iterator pattern separates the target object from the iterator object, conforming to the open and closed principle.
Observer mode (publish-subscribe mode)
The observer pattern, also known as the publisk-subscribe pattern, defines a one-to-many dependency between objects in which dependent objects are notified when an object’s state changes. In JavaScript development, we generally use the event model instead of the traditional publish-subscribe model.
In general, the essence of this pattern is that you can observe the state of an object in the program and be notified when it changes.
There are already many examples of useful observer patterns, such as DOM event binding, which is a very typical publish-subscribe pattern, and two-way data binding in the Vue.js framework, which utilizes the observer pattern.
So the general observer model has two roles:
- Observer (Publisher)
- Observed (subscriber)
Let’s take a concrete example. Suppose there are three newspaper publishers, newspaper 1, newspaper 2, and newspaper 3, and there are two subscribers: Subscriber 1 and Subscriber 2. The publisher is the observed, and the subscriber is the observer.
Let’s first define the newspaper category:
/ / class paper
class Press {
constructor(name) {
this.name = name;
this.subscribers = []; // This is where the subscriber list is stored
}
deliver(news) {
let press = this;
// Loop through all the subscribers in the subscriber list and publish content for them
press.subscribers.map(item= > {
item.getNews(news, press); // Send news to each subscriber
})
// implement chain calls
return this; }}Copy the code
Then we define the subscriber human
// Subscription human
class Subscriber {
constructor(name) {
this.name = name;
}
// Get the news
getNews(news, press) {
console.log(`The ${this.name}To get from${press.name}The news:${news}`)}// Subscribe method
subscribe(press) {
let sub = this;
// Avoid double subscriptions
if(press.subscribers.indexOf(sub) === - 1) {
press.subscribers.push(sub);
}
// implement chain calls
return this;
}
// Unsubscribe method
unsubscribe(press) {
let sub = this;
press.subscribers = press.subscribers.filter((item) = >item ! == sub);return this; }}Copy the code
Then we demonstrated through actual operation:
let press1 = new Press('Newspaper One')
let press2 = new Press('Newspaper Two')
let press3 = new Press('Newspaper Three')
let sub1 = new Subscriber('Subscriber 1')
let sub2 = new Subscriber('Subscriber 2')
// Subscriber 1 subscribes to newspaper 1 and 2
sub1.subscribe(press1).subscribe(press2);
Subscriber 2 subscribes to newspaper 2 and 3
sub2.subscribe(press2).subscribe(press3);
// As soon as the newspaper sends out the news
press1.deliver('It's fine today');
// Subscriber 1 gets the news from newspaper 1: It will be fine today
// Newspaper 2 sends out the news
press2.deliver('Apple event tonight at 12:00.');
// Subscriber 1 gets news from Newspaper 2: Apple event tonight at 12 o 'clock
// Subscriber 2 picks up news from Newspaper 2: Apple event tonight at 12 o 'clock
// Newspaper 3 sends out the news
press3.deliver('Newspaper 2 is closing. Please cancel asap.');
// Subscriber 2 receives news from newspaper 3: Newspaper 2 is going to close, please cancel your subscription as soon as possible
// Subscriber 2 unsubscribe
sub2.unsubscribe(press2);
press2.deliver('The Newspaper is out of business');
// Subscriber 1 receives news from newspaper 2 that our newspaper has closed
Copy the code
As mentioned above, the principle of Vue. Js bidirectional binding is data hijacking and publish and subscribe. We can implement a simple data bidirectional binding by ourselves
First we need to have a page structure
<div id="app"> <h3> Bidirectional binding of data </h3> <div class="cell">
<div class="text" v-text="myText"></div>
<input class="input" type="text" v-model="myText" >
</div>
</div>
Copy the code
Next we create a class, aVue
Class aVue {constructor (options) {// Incoming configuration parameter this.options = options; // The root element this.$el= document.querySelector(options.el); // Data field this.$data= options.data; // Save the data model directives associated with the view. When the model changes, we will trigger the directives in it to update this._directives = {}; // Data hijacking, redefining datasetAnd the get method this._obverse(this.$data); // Update the view this._complie(this) when the data changes.$el); } // Process the data and override itsetAnd get method _obverse(data) {letval; // perform traversalfor(let key inData) {// Determine whether the attribute belongs to itselfif(data.hasOwnProperty(key)) {
this._directives[key] = [];
}
val = data[key];
if( typeof val === 'object') {// recursively traverse this._obverse(val); } // Initializes the execution queue for the current datalet_dir = this._directives[key]; // Redefines the datasetAnd the get method Object.defineProperty(this.$data, key, {// Enumerable:true, // Changes freely:true,
get: () => val,
set: (newVal) => {
if(val ! == newVal) { val = newVal; // Trigger the Watcher class bound by _directives to update _dir.map(item => {item._update(); })}} // Parsers, bind nodes, add data subscribers, update view changes _complie(el) {// child elementslet nodes = el.children;
for(let i = 0; i < nodes.length; i++) {
letnode = nodes[i]; // Recursively iterate over all elementsif(node.children.length) { this._complie(node); } // If there is a V-text command, monitor node values and update themif (node.hasAttribute('v-text')) {
let attrValue = node.getAttribute('v-text'); Cache [attrValue]. Push (new Watcher)'text', node, this, attrValue, 'innerHTML'} // We listen for input events if we have v-model attributes and the element is input or textareaif( node.hasAttribute('v-model') && ( node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')) {
let_this = this; // Add the input event node.addEventListener('input', (function() {let attrValue = node.getAttribute('v-model'); _this. _cache [attrValue]. Push (new Watcher)'input', node, _this, attrValue, 'value'));
return function() {// _this is updated every time.$data[attrValue] = node.value; }})()}}}}Copy the code
The _observe method processes the incoming data and rewrites data’s set and get methods to ensure that data changes can be tracked.
The _compile method is essentially a parser. It parses template instructions, binds the corresponding nodes of each instruction to update functions, and adds subscribers to listen for data changes, and updates view changes as data changes.
Next we define the subscriber class
Class Watcher{/* * name instruction name * THE DOM element corresponding to the EL instruction * the aVue instance of the VM instruction * the value corresponding to the exp instruction, in this example, is"myText"* The attr binding attribute value, in this example, is"innerHTML"*/ constructor(name, el, vm, exp, attr) { this.name = name; this.el = el; this.vm = vm; this.exp = exp; this.attr = attr; // Update this._update(); }_update() {
this.el[this.attr] = this.vm.$data[this.exp]; }}Copy the code
In _compile, we create two instances of Watcher, but the _update operation has different results. Div. InnerHTML = this.data. MyText for input = input.value = this.data. MyText for input. We fire two _update methods to update the contents of the div and input, respectively.
Finally, we successfully implemented a simple bidirectional binding.
The sample Demo source is available at the Observer
conclusion
- The observer pattern can decouple code to meet the open and closed principle.
- When the observer pattern is used too much, if the subscription message is never triggered, the subscriber is still kept in memory.
Command mode
In a software system, the behavior requester and the behavior implementers usually present a tight coupling, but in some situations, such as when the behavior is to be recorded, undo/redo, transaction, etc., such a tight coupling that cannot resist change is not appropriate. In this case, if we want to decouple the behavior requester from the behavior implementer, we need to abstract a set of behaviors as objects to achieve loose coupling between the two, which is the command pattern.
We need to define a command object between the publisher of the command and the receiver. The command object exposes a unified excuse to the publisher of the command and the publisher of the command does not need to know how the receiver executes the command, so as to achieve the decoupling of the publisher and the receiver.
Here’s an example of a page with three buttons, each with a different function:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Command mode</title>
</head>
<body>
<div>
<button id="btn1">Button1</button>
<button id="btn2">Button2</button>
<button id="btn3">Button3/button>
</div>
<script src="./Command.js"></script>
</body>
</html>
Copy the code
Next we define a command publisher (executor) class
class Executor {
setCommand(btn, command) {
btn.onclick = function () { command.execute(); }}}Copy the code
Next we define a command receiver, in this case a menu
// Define a command receiver
class Menu {
refresh() {
console.log('Refresh menu')
}
addSubMenu() {
console.log('Add submenu')
}
deleteMenu() {
console.log('Delete menu')}}Copy the code
We then encapsulated the Menu method execution in a separate class
class RefreshMenu {
constructor(receiver) {
// associate the command object with the receiver
this.receiver = receiver
}
// Expose the unified interface to Excetor
execute() {
this.receiver.refresh()
}
}
// Define a class that accepts a submenu command object
class AddSubMenu {
constructor(receiver) {
// associate the command object with the receiver
this.receiver = receiver
}
// Expose the unified interface to Excetor
execute() {
this.receiver.addSubMenu()
}
}
// Define a class that accepts delete menu objects
class DeleteMenu {
constructor(receiver) {
this.receiver = receiver
}
// Expose the unified interface to Excetor
execute() {
this.receiver.deleteMenu()
}
}
Copy the code
Then we instantiate different objects separately
// Get the button object first
let btn1 = document.getElementById('btn1')
let btn2 = document.getElementById('btn2')
let btn3 = document.getElementById('btn3')
let menu = new Menu()
let executor = new Executor()
let refreshMenu = new RefreshMenu(menu)
// Add refresh function to button 1
executor.setCommand(btn1, refreshMenu) // Click button 1 to display "refresh menu"
let addSubMenu = new AddSubMenu(menu)
// Add a submenu function to button 2
executor.setCommand(btn2, addSubMenu)// Click button 2 to display "Add submenu"
let deleteMenu = new DeleteMenu(menu)
// Add the delete function to button 3
executor.setCommand(btn3, deleteMenu)// Click button 3 to display "Delete menu"
Copy the code
conclusion
- The publisher and receiver achieve understanding coupling in accordance with the single responsibility principle.
- Commands can be extended, and requests can be queued or logged, which complies with the open-closed principle.
- However, additional command objects are added, which has some excess overhead.
The state pattern
The state pattern allows an object to change its behavior when its internal state changes, as if the object changed the class, but it didn’t. The state mode changes the behavior of the studied object when its internal state changes. The state mode needs to create a subclass of state class for each possible state obtained by the system. When the state of the system changes, the system changes the selected subclass.
Suppose we now have an electric lamp, there is a switch on the lamp, press when the lamp is off to turn on, press when the lamp is on to turn off, at this time the behavior is different:
class Light {
constructor() {
this.state = 'off'; // The lights are turned off by default
this.button = null;
}
init() {
let button = document.createElement('button');
let self = this;
button.innerHTML = 'I'm the switch';
this.button = document.body.appendChild(button);
this.button.onclick = (a)= > {
self.buttonWasClicked();
}
}
buttonWasClicked() {
if (this.state === 'off') {
console.log('turn on the light');
this.state = 'on';
} else {
console.log('off');
this.state = 'off'; }}}let light = new Light();
light.init();
Copy the code
At this time, there are only two states, and we can not use the state mode, but when there are many states, for example, when the lamp appears weak light, strong light shift, the above code can not meet the requirements.
### Refactoring using state mode
The key to the state pattern is to encapsulate each state into a separate class, within which the behavior associated with this state is encapsulated. When a button is pressed, simply delegate the request to the current state object in context, which is responsible for rendering its own behavior.
Start by defining three classes in different states
// The light is off
class OffLightState {
constructor(light) {
this.light = light;
}
buttonWasClicked() {
console.log('Switch to low light mode');
this.light.setState(this.light.weakLightState); }}// Low light state
class WeakLightState {
constructor(light) {
this.light = light;
}
buttonWasClicked() {
console.log('Switch to bright light mode');
this.light.setState(this.light.strongLightState); }}// Strong light state
class StrongLightState {
constructor(light) {
this.light = light;
}
buttonWasClicked() {
console.log('off');
this.light.setState(this.light.offLightState); }}Copy the code
Next, we rewrite the Light class to record the current state internally via curState
class Light {
constructor() {
this.offLightState = new OffLightState(this);
this.weakLightState = new WeakLightState(this);
this.strongLightState = new StrongLightState(this);
this.button = null;
}
init() {
let button = document.createElement('button');
let self = this;
button.innerHTML = 'I'm the switch';
this.button = document.body.appendChild(button);
this.curState = this.offLightState;
this.button.onclick = (a)= > {
self.curState.buttonWasClicked();
}
}
setState(state) {
this.curState = state; }}Copy the code
After the object is instantiated, we view it in the page
let light = new Light();
light.init();
Copy the code
conclusion
- The state pattern changes the behavior of objects by defining different state classes.
- Without having to write a lot of logic into the class of the object being manipulated, it’s easy to add new states.
- In line with the open – closed principle.