“This is the second day of my participation in the Gwen Challenge in November. See details: The Last Gwen Challenge in 2021”
series
Summary of “JavaScript Design Patterns and Development Practices” (Basics)
Design Patterns (I)
The singleton pattern
Ensure that a class has only one instance and provide a global access point to access it
Implement the singleton pattern
For the most common example, when we log in, there will be a login float window in the page, but it is unique, no matter how many times we click the login button, the float window will only be created once, not every time we click a new instance.
The simple singleton pattern is not complicated to implement. It simply uses a variable to record whether an object has been created for the class, and if so, returns the previously created object the next time an instance of the class is fetched.
var Singleton = function( name ){
this.name = name;
this.instance = null;
};
Singleton.prototype.getName = function(){
alert ( this.name );
};
Singleton.getInstance = function( name ){
if(!this.instance ){
this.instance = new Singleton( name );
}
return this.instance;
};
var a = Singleton.getInstance( 'sven1' );
var b = Singleton.getInstance( 'sven2' );
console.log( a === b ); // true
Copy the code
The code is simple, but it doesn’t make much sense because the class is “opaque” and the user must know that this is a Singleton class and that the singleton.getInstance is used to get the object.
Transparent singleton pattern (proxy)
What we need is to be able to implement a transparent singleton class that works just like any other normal class and is flexible enough.
var CreateDiv = function( html ) {
this.html = html
this.init()
}
CreateDiv.prototype.init = function() {
var div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
/ / the proxy class
var ProxySingletonCreateDiv = (function() {
var instance
return function(html) {
if(! instance) { instance =new CreateDiv(html)
}
return instance
}
})()
var a = new ProxySingletonCreateDiv('a')
var b = new ProxySingletonCreateDiv('b')
console.log(a === b)
Copy the code
In this code, we singleton CreateDiv (ProxySingletonCreateDiv, described below) using the ProxySingletonCreateDiv proxy class. Instead, CreateDiv becomes a generic class. If multiple instances need to be created in a business scenario, We can create it directly using CreateDiv. The combination of them achieves the effect of singleton pattern.
Inert singleton
As the name implies, lazy singletons create object instances only when needed. In the beginning, a variable is used to indicate whether an object has been created, and if so, the created object is returned.
var getSingle = function(fn) {
var result
return function() {
return result || (result = fn.apply(this.arguments))}}Copy the code
Combining much of what we learned in the basics, we can pass in any method, then let getSingle return a new function, and save fn’s calculation with Result, because result is in the closure, it will never be destroyed, and it will return that value if result exists on future requests.
The strategy pattern
Define a set of algorithms, encapsulate them one by one, and make them interchangeable. The purpose of the policy pattern is to separate the use of algorithms from the implementation of algorithms.
Calculate the bonus
The book gives an example of calculating bonuses.
Many companies pay bonuses based on employees’ salary base and year-end performance. For example, the person performing at S gets 4 times his salary, the person performing at A gets 3 times his salary, and the person performing at B gets 2 times his salary. Suppose the finance department asks us for a code to help them calculate employees’ year-end bonuses.
In this example, the algorithm is used in the same way to obtain the calculated bonus, but the implementation of the algorithm may not be the same, and each performance corresponds to different calculation rules.
The program based on policy pattern consists of two parts. The first part is the policy class, which encapsulates the specific algorithm and is responsible for the specific calculation process. The second part is the environment class, which accepts customer requests and delegates them to a policy class.
var strategies = {
'S': function(num) {
return num * 4
},
'A': function(num) {
return num * 3
},
'B': function(num) {
return num * 2}},//
var calculate = function(level , num) {
return strategies[level](num)
}
console.log(calculate('S' , 4));
console.log(calculate('A' , 3));
Copy the code
The embodiment of polymorphism in strategy pattern
By refactoring the code using the policy pattern, we eliminated a large number of conditional branch statements from the original program. All the logic associated with calculating bonuses is no longer placed in the Context, but is distributed among the policy objects. The Context does not have the ability to calculate bonuses, but delegates this responsibility to some policy object. The algorithm responsible for each policy object has been encapsulated within the object. When we ask these policy objects to “calculate the bonus”, they will return different results, which is the object polymorphism and the purpose of “they can be interchangeable”. By replacing the currently saved policy object in the Context, we can perform different algorithms to get the desired result.
Advantages and disadvantages of strategic pattern
-
advantages
- Strategy mode uses combination, delegation, polymorphism and other techniques and ideas to avoid multiple conditional selection statements.
- The strategy mode is perfectly supportedOpen – close principle, encapsulate the algorithm in strategy, which is easy to switch, understand and expand.
- Algorithms in the policy pattern can also be reused elsewhere.
- The policy pattern uses composition and delegation to give the Context the ability to execute the algorithm, which is also a lighter alternative to inheritance.
-
disadvantages
- Add many policy classes or policy objects to your program
- All strategies must be understood and the differences between them must be understood in order to choose a suitable strategy.
The proxy pattern
The proxy pattern provides an object with a proxy object that controls references to the original object. Generally speaking, the agency model is a common intermediary in our life.
Sending flowers to each question
On A sunny April morning, Xiao Ming met his 100% girl, whom we will call A. Two days later, Xiao Ming decided to send A A bunch of flowers to express his love. It happened that Xiaoming learned that A and he have A common friend B, so the introverted Xiaoming decided to let B to complete the flowers instead of him. When A received flowers in A good mood, xiaoming’s confession success rate was 60%, and when A received flowers in A bad mood, Xiaoming’s confession success rate approached 0.
At this time, we need to use the proxy mode. Xiaoming cannot know A’s mood, but B does. If you give the flower to B, B will monitor A’s mood change and transfer it when it is in A good mood.
var Flower = function(){}
var xiaoming = {
sendFlower: function(target) {
var flower = new Flower()
target.receiveFlower(flower)
}
}
var B = {
receiveFlower: function(flower) {
A.listenGoodMood(function() {
A.receiveFlower(flower)
})
}
}
var A = {
receiveFlower: function(flower) {
console.log('Received flowers', flower)
},
listenGoodMood: function( fn ) {
setTimeout(function() {
fn()
},1000)
}
}
xiaoming.sendFlower(B)
Copy the code
Protect agents and virtual agents
Protecting agents: Agent B can help agent A filter out requests that do not meet the requirements, as in the example above.
Virtual agent: The operation is handed over to agent B, who decides when to perform the operation. Virtualisation delays the creation of some expensive objects until they are really needed.
var B = {
receiveFlower: function(flower) {
A.listenGoodMood(function() {
var flower = new Flower()
A.receiveFlower(flower)
})
}
}
Copy the code
The caching proxy
The cache proxy can provide temporary storage for some expensive operation results, and can return the previously stored operation results directly on the next operation if the parameters passed in are the same as before.
var mult = function(){
console.log( 'Let's compute the product.' );
var a = 1;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
return a;
};
mult( 2.3 ); // Output: 6
mult( 2.3.4 ); // Output: 24
var proxyMult = (function(){
var cache = {};
return function(){
var args = Array.prototype.join.call( arguments.', ' );
if ( args in cache ){
return cache[ args ];
}
return cache[ args ] = mult.apply( this.arguments );
}
})();
proxyMult( 1.2.3.4 ); // Output: 24
proxyMult( 1.2.3.4 ); // Output: 24
Copy the code
High-order functions create proxies on the fly
/**************** computes the product *****************/
var mult = function(){
var a = 1;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
return a;
};
/**************** calculates plus and *****************/
var plus = function(){
var a = 0;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a + arguments[i];
}
return a;
};
/**************** Create a factory for caching proxies *****************/
var createProxyFactory = function( fn ){
var cache = {};
return function(){
var args = Array.prototype.join.call( arguments.', ' );
if ( args in cache ){
return cache[ args ];
}
return cache[ args ] = fn.apply( this.arguments); }};var proxyMult = createProxyFactory( mult ),
proxyPlus = createProxyFactory( plus );
console.log( proxyMult( 1.2.3.4));// Output: 24
console.log( proxyMult( 1.2.3.4));// Output: 24
console.log( proxyPlus( 1.2.3.4));// Output: 10
console.log( proxyPlus( 1.2.3.4));// Output: 10
Copy the code
Iterator pattern
Provides a way to access elements in an aggregate object sequentially without exposing the internal representation of the object.
The iterator pattern separates the process of iterating from the business logic. After using the iterator pattern, each element of an object can be accessed sequentially, even without caring about its internal construction. Simply put, it is the traversal interface of the unified “collection” type data structure, which can achieve the cyclic traversal of various data items in the collection (regardless of the data structure of the data items).
Implement your own iterators
Takes two parameters: the array to be looped, and the callback to be triggered after each step of the loop
var each = function(ary, callback) {
for(var i = 0,l = ary.length; i< l; i++) { callback.call(ary[i] , i , ary[i]) } }var arr = [1.2.3]
each(arr , function(i , n)) {
console.log([i , n])
}
Copy the code
Inner iterators and outer iterators
- Inner iterator
The each function we just wrote is an inner iterator. Each has defined the iteration rules inside, and it takes over the entire iteration process completely, with only one initial call outside. Internal iterators are very convenient to call, the outside world does not care about the implementation of the internal iterator, the interaction with the iterator is only an initial call, but this is also the disadvantage of internal iterators. Since the rules for iterating inner iterators were specified in advance, the each function above cannot iterate over two arrays at the same time. For example, if we have a requirement to determine whether the values of the elements in two arrays are exactly the same, the only place we can start seems to be the callback for each without rewriting the code itself.
var compare = function( ary1, ary2 ){
if( ary1.length ! == ary2.length ){throw new Error ( 'ary1 is not equal to ary2 ' );
}
each( ary1, function( i, n ){
if( n ! == ary2[ i ] ){throw new Error ( 'ary1 is not equal to ary2 '); }}); alert ('ary1 is equal to ary2 ' );
};
compare( [ 1.2.3 ], [ 1.2.4]);// throw new Error ('ary1 and ary2 are not equal ');
Copy the code
- External iterator
An external iterator must explicitly request iteration of the next element. External iterators add some complexity to the calls, but they also increase the flexibility of the iterators, allowing us to manually control the iteration process or sequence. Although external iterators are called in a complex way, they are more applicable to a wide range of applications and can better meet changing needs.
var Iterator = function( obj ){
var current = 0;
var next = function(){
current += 1;
};
var isDone = function(){
return current >= obj.length;
};
var getCurrItem = function(){
return obj[ current ];
};
return {
next: next,
isDone: isDone,
getCurrItem: getCurrItem
}
};
// Rewrite the compare function
var compare = function( iterator1, iterator2 ){
while(! iterator1.isDone() && ! iterator2.isDone() ){if( iterator1.getCurrItem() ! == iterator2.getCurrItem() ){throw new Error ( Iterator1 and iterator2 are not equal );
}
iterator1.next();
iterator2.next();
}
alert ( Iterator1 is equal to iterator2 );
}
var iterator1 = Iterator( [ 1.2.3]);var iterator2 = Iterator( [ 1.2.3]); compare( iterator1, iterator2 );Iterator1 is equal to iterator2
Copy the code
Iterating over array-like objects and literals
The iterator pattern can iterate not only over arrays, but also over some array-like objects. Arguments {“0″:’a’,”1”:’b’}, etc. As you can see from the above code, either an inner iterator or an outer iterator can be iterated over as long as the iterated aggregate object has the length attribute and can be accessed with subscripts. In JavaScript, a for in statement can be used to iterate over properties of ordinary literal objects.
Inverse-order iterators
var reverseEach = function(ary , callback) {
for(var l = ary.length - 1; l >=0; l--) {
callback(l,ary[l])
}
}
reverseEach([0.1.2].function(i, n) {
console.log(n)
})
Copy the code
Abort iterator
var each = function( ary, callback ){
for ( var i = 0, l = ary.length; i < l; i++ ){
if ( callback( i, ary[ i ] ) === false) {// Callback returns false, prematurely terminating the iteration
break; }}}; each( [1.2.3.4.5].function( i, n ){
if ( n > 3) {// terminates the loop when n is greater than 3
return false;
}
console.log( n ); // Outputs 1, 2, 3 respectively
});
Copy the code
Publish and subscribe
It defines a one-to-many dependency between objects, and all dependent objects are notified when an object’s state changes.
/* Universal publish subscription */
// Time decoupling and object decoupling consume time and memory
var Event = (function () {
var clientList = {},
listen, trigger, remove
listen = function (key, fn) {
if(! clientList[key]) { clientList[key] = [] } clientList[key].push(fn) } trigger =function () {
var key = Array.prototype.shift.call(arguments)
var fns = clientList[key]
if(! fns || fns.length ===0) {
return false
}
for (var i = 0, fn; fn = fns[i++];) {
fn.apply(this.arguments)
}
}
remove = function (key, fn) {
var fns = clientList[key]
if(! fns) {return false
}
if(! fn) { fns && (fns.length =0)}else {
for (var i = fns.length - 1; i >= 0; i--) {
var _fn = fns[i]
if (_fn === fn) {
fns.splice(i, 1)}}}}return {
listen, trigger, remove
}
})()
Event.listen('square88' , function(price) {
console.log('price:' , price);
})
Event.trigger('square88' , 2000000)
Copy the code
Publish-subscribe applications
I don’t want to say more about what’s in the book, but what’s not in the book.
For a more in-depth study of the publish-subscribe pattern, it is recommended to implement a VUE of your own, which is how vue2’s responsiveness is implemented. Let’s see how this feature is used.
Vue responsive principle
var v = new Vue({
data() {
return {
a:'a'}}})Copy the code
There’s a picture of responsiveness on the website, so let’s analyze it with that picture.
The data was hijacked
As we all know, the core of data hijacking is object.defineProperty converting a property into a corresponding getter\setter (vue does not support ie8 for the following reasons). When data changes are passed, they are processed in Dep and Watcher.
walk(obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; ++i) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
Copy the code
Hijack related functions and subscribe publications
/** * Define a reactive property on an Object. */
export function defineReactive(
obj: Object, key: string, val: any, customSetter? :Function
) {
/* Define a dep object in the closure */
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
/* If the object already has a getter and setter function, it is taken out. The getter/setter is executed in the new definition, ensuring that the getter/setter is not overwritten. * /
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
/* The child of the object recursively observes and returns the Observer of the child node */
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get: function reactiveGetter() {
*/ if the original object has a getter method
const value = getter ? getter.call(obj) : val
if (Dep.target) {
/* Do dependency collection */
dep.depend()
if (childOb) {
/* Child dependencies are collected by placing the same Watcher observer instance into two Depends: the depend in the closure itself and the Depend */ of the child element
childOb.dep.depend()
}
if (Array.isArray(value)) {
/* An array requires a dependency collection on each member. If the members of an array are still arrays, recurse. * /
dependArray(value)
}
}
return value
},
set: function reactiveSetter(newVal) {
/* Compares the current value with the new value through the getter method. If the value is consistent, the following operations are not required */
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if(newVal === value || (newVal ! == newVal && value ! == value)) {return
}
/* eslint-enable no-self-compare */
if(process.env.NODE_ENV ! = ='production' && customSetter) {
customSetter()
}
if (setter) {
/* Execute setter*/ if the original object has setter methods
setter.call(obj, newVal)
} else {
val = newVal
}
/* The new value must be observed again to ensure that the data is responsive */
childOb = observe(newVal)
/* The dep object notifies all observers */
dep.notify()
}
})
}
Copy the code
During initialization, the listener is hijacked for the data in the data. Observe is called during initialization and an Observer instance is returned
/** * Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. */
/* Attempt to create an Observer instance (__ob__) and return a new Observer instance if it is successfully created or an existing Observer instance if it already exists. * /
export function observe(value: any, asRootData: ? boolean) :Observer | void {
/* Check if it is an object */
if(! isObject(value)) {return
}
let ob: Observer | void
/* The __ob__ attribute is used to determine whether an Observer instance exists. If no Observer instance exists, a new Observer instance is created and assigned to __ob__. If an Observer instance exists, the Observer instance is returned
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
/* This is done to ensure that value is a pure object, not a function or Regexp. * /observerState.shouldConvert && ! isServerRendering() && (Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) && ! value._isVue ) { ob =new Observer(value)
}
if (asRootData && ob) {
Observeobserve's asRootData is not true*/
ob.vmCount++
}
return ob
}
Copy the code
Dep and Watcher
In data hijacking, data is obtained and modified accordingly. The purpose of the operation is to notify the “hub”, which is primarily a place for notifying data changes and storing dependent data.
Dep
Dep is used to collect dependencies, notify corresponding subscribers, and let them perform their actions.
/** * A dep is an observable that can have multiple * directives subscribing to it. */
export default class Dep {
statictarget: ? Watcherid: number
subs: Array<Watcher>
constructor() {
this.id = uid++
this.subs = []
}
/* Add an observer object */
addSub(sub: Watcher) {
this.subs.push(sub)
}
/* Removes an observer object */
removeSub(sub: Watcher) {
remove(this.subs, sub)
}
/* Rely on collection, add observer object */ when dep.target is present
depend() {
if (Dep.target) {
Dep.target.addDep(this)}}/* Notify all subscribers */
notify() {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
/* Set dep. target to null after collecting dependencies. * /
const targetStack = []
export function pushTarget(_target: Watcher) {
if (Dep.target) targetStack.push(Dep.target)
// Change the target direction
Dep.target = _target
}
export function popTarget() {
// Delete the current target and recalculate the pointer
Dep.target = targetStack.pop()
}
Copy the code
The code above does two main things:
- Define subs array to collect subscriber Watcher.
- When hijacking data changes, notify Watcher to update them.
Watcher
The Watcher is a subscriber and subscribes to the Dep. When the Dep sends the notify message, all the Watcher subscribes to the Dep to perform its update operation.
export default class Watcher {
vm: Component
expression: string
cb: Function
id: number
deep: boolean
user: boolean
lazy: boolean
sync: boolean
dirty: boolean
active: boolean
deps: Array<Dep>
newDeps: Array<Dep>
depIds: ISet
newDepIds: ISet
getter: Function
value: any
constructor(
vm: Component,
expOrFn: string | Function,
cb: Function, options? :Object
) {
this.vm = vm
/*_watchers store subscriber instance */
vm._watchers.push(this)
// options
if (options) {
this.deep = !! options.deepthis.user = !! options.userthis.lazy = !! options.lazythis.sync = !! options.sync }else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set(a)this.newDepIds = new Set(a)this.expression = process.env.NODE_ENV ! = ='production' ? expOrFn.toString() : ' '
// parse expression for getter
ExpOrFn = expOrFn; /* expOrFn = expOrFn; /* expOrFn = expOrFn
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function() {} process.env.NODE_ENV ! = ='production' &&
warn(
`Failed watching path: "${expOrFn}"` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy ? undefined : this.get()
}
/** * Evaluate the getter, and re-collect dependencies. */
/* Get the getter value and re-collect the dependency */
get() {
/* Set its own Watcher observer instance to dep. target to rely on the collection. * /
pushTarget(this)
let value
const vm = this.vm
/* Perform the getter, which looks like a render operation, but actually performs a dependency collection. After setting dep. target to the spontaneous observer instance, perform the getter operation. For example, there may be a, B, and C data in the current data, and the getter rendering needs to rely on A and C, so the getter function of a and C data will be triggered when the getter is executed. In the getter function, the existence of dep. target can be determined and the dependency collection can be completed. Put the observer object into the subs of the Dep in the closure. * /
if (this.user) {
try {
value = this.getter.call(vm, vm)
} catch (e) {
handleError(e, vm, `getter for watcher "The ${this.expression}"`)}}else {
value = this.getter.call(vm, vm)
}
// "touch" every property so they are all tracked as
// dependencies for deep watching
/* If deep is present, the dependency of each deep object is triggered to track its changes */
if (this.deep) {
/* Recurse to each object or array, triggering its getter so that each member of the object or array is collected by dependency, forming a "deep" dependency */
traverse(value)
}
/* Remove the observer instance from the target stack and set it to dep.target */
popTarget()
this.cleanupDeps()
return value
}
/** * Add a dependency to this directive. */
/* Add a dependency to the Deps collection */
addDep(dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)}}}/** * Clean up for dependency collection. */
/* Clean up the dependency collection */
cleanupDeps() {
/* Remove all observer objects */
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)}}let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
/** * Subscriber interface. * Will be called when a dependency changes. */
/* Scheduler interface to call back when a dependency changes. * /
update() {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
/* Execute run to render the view directly
this.run()
} else {
/* Asynchronously pushed to observer queue, called by scheduler. * /
queueWatcher(this)}}/** * Scheduler job interface. * Will be called by the scheduler. */
/* Dispatcher work interface that will be called back by the dispatcher. * /
run() {
if (this.active) {
const value = this.get()
if( value ! = =this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
/* Even if the values are the same, observers with the Deep attribute and observers on the object/array should be triggered to update because their values may change. * /
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
/* Sets the new value */
this.value = value
/* Triggers a callback to render view */
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "The ${this.expression}"`)}}else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */
/* Gets the observer's value */
evaluate() {
this.value = this.get()
this.dirty = false
}
/** * Depend on all deps collected by this watcher. */
/* Collect all dePS dependencies for this watcher */
depend() {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/** * Remove self from all dependencies' subscriber list. */
/* Remove itself from all dependent collection subscriptions */
teardown() {
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
/* Remove itself from the observer list of the VM instance, skip this step if the VM instance is being destroyed because this operation is expensive. * /
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)}let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)}this.active = false}}}Copy the code
Command mode
Command mode is one of the simplest and most elegant modes. A command in command mode refers to an instruction that does something specific.
The command pattern is a loose-coupling approach that decouple request senders and receivers from each other.
/ * is the essence of command encapsulation, the responsibility of the split orders and execute the command Advantages: lower object coupling, easy extension combined command, call the same method to realize different functions * /
/ / command
var CreateCommand = function (receiver) {
this.receiver = receiver
}
CreateCommand.prototype.execute = function () {
this.receiver.action()
}
/ / the recipient
var TVOn = function () { }
TVOn.prototype.action = function () {
console.log('TV on now');
}
var TVOff = function () { }
TVOff.prototype.action = function () {
console.log('TV off now');
}
/ / the caller
var Invoker = function (tvOnCommand, tvOffCommand) {
this.tvOnCommand = tvOnCommand
this.tvOffCommand = tvOffCommand
}
Invoker.prototype.tvOn = function () {
this.tvOnCommand.execute()
}
Invoker.prototype.tvOff = function () {
this.tvOffCommand.execute()
}
var tvOnCommand = new CreateCommand(new TVOn())
var tvOffCommand = new CreateCommand(new TVOff())
var myInvoker = new Invoker(tvOnCommand, tvOffCommand)
myInvoker.tvOn()
myInvoker.tvOff()
Copy the code
Portfolio model
The composite pattern is to build larger objects from smaller children, which themselves may be made up of smaller grandchildren.
Combined mode usage
The composition pattern combines objects into a tree structure to represent a partial-whole hierarchy. Another benefit of the composite pattern is that it enables consistent use of individual and composite objects through the polymorphic representation of objects.
In the case of a macro command, requests are passed down from the object at the top of the tree. If the object currently processing the request is a leaf object (a common subcommand), the leaf itself will process the request accordingly. If the object currently processing the request is a composite object (macro command), the composite object iterates through its children, passing the request on to those children.
Requests are sent from top to bottom, and the user only needs to care about the topmost composite object, and requests for that object are sent down.
Roles: (1) child objects (2) Composite objects (3) Abstract classes: Mainly defines the public interface of the objects participating in the composition, or can be directly defined in the composition object
For example,
Original example on Github
Scenario: An organization has employees with different names and salaries. You can add employees.
// Take an employee as an example. We have different types of employees here
/ / developer
class Developer {
constructor(name, salary) {
this.name = name
this.salary = salary
}
getName() {
return this.name
}
setSalary(salary) {
this.salary = salary
}
getSalary() {
return this.salary
}
getRoles() {
return this.roles
}
develop() {
/ * * /}}/ / designer
class Designer {
constructor(name, salary) {
this.name = name
this.salary = salary
}
getName() {
return this.name
}
setSalary(salary) {
this.salary = salary
}
getSalary() {
return this.salary
}
getRoles() {
return this.roles
}
design() {
/ * * /}}// An organization made up of several different types of employees
class Organization {
constructor(){
this.employees = []
}
// Appends elements
addEmployee(employee) {
this.employees.push(employee)
}
// All leaf objects have the same getSalary method. The leaf.execute pattern can be used to invoke methods of the object while the root object is executing.
getNetSalaries() {
let netSalary = 0
this.employees.forEach(employee= > {
netSalary += employee.getSalary()
})
return netSalary
}
}
/ / call
// Prepare the employees
const john = new Developer('John Doe'.12000)
const jane = new Designer('Jane'.10000)
// Add them to organization Advantage: call the entire composite object only once, no matter how many employee types
const organization = new Organization()
organization.addEmployee(john)
organization.addEmployee(jane)
console.log("Net salaries: " , organization.getNetSalaries()) // Net Salaries: 22000
Copy the code
Something to watch out for
- Combinatorial patterns are not parent-child relationships
- Consistency of operations on leaf objects
- Bidirectional mapping
- Using responsibility chain pattern to improve composite pattern performance
Usage scenarios
- A collection of objects with some kind of hierarchical structure (the exact structure cannot be determined during development)
- You want to perform some kind of operation on these objects or some of them
Disadvantages: Because any operation on a composite object calls the same operation on all child objects, there can be performance problems when the composite structure is large.