What is the publish-subscribe model
- The publis-subscribe pattern, also known as the observer pattern, defines a one-to-many dependency between objects. When an object’s state changes, all dependent objects are notified.
- For example, the sales office to sell, then the sales office to release the room type information, then it is the publisher, intermediary attention to the room type, so the intermediary is a subscriber, when the sales office after the release of the news or after the room type information update, the intermediary will receive the message. Then he went to inform the client. The advantage of doing this is that the customer does not have to care about the room type, do not have to keep close contact with any sales office, only need to contact with an intermediary, but he can know all the changes of room type through the intermediary. All customers are not coupled to the sales office.
- The diagram below:
And again, we can apply it to programming,Reduce code coupling.
We tried to do this using a publish-subscribe model.
let houseObj = {}; Houseobj. list = []; Housepigeon.listen = function(fn){// add subscription messages to the cache list this.list.push(fn); Housepigeon.trigger = function(){for(let I = 0,fn; fn = this.list[i++];) { fn.apply(this,arguments); Listen (function(size){console.log(' +size+'); }) // houseObj. Listen (function(size){console.log(' houseObj: MY house is '+size+' m '); }) // Execute housej.trigger (100); houseObj.trigger(150);Copy the code
Existing problems
-
Once you’ve written this code and run it, you’ll see that it prints four times, ideally two times, because houseObj sends all the messages to one subscriber.
-
This is very unfriendly to the user, who only wants to see the message he subscribed to. So let’s optimize.
let houseObj = {}; // publish houseobj. list = {}; HouseObj. Listen = function(key,fn){// add a unique key to this message // create a cache list for this message if it has not been subscribed (enclosing a list [key] | | (enclosing a list [key] = [])), push (fn)} / / Traverse the list houseObj. Trigger = function () {/ / remove the message type name let key = Array.prototype.shift.call(arguments); Let FNS = this.list[key]; let FNS = this.list[key]; if(! fns || fns.length === 0){ return; } for(let i = 0,fn; fn = fns[i++];) { fn.apply(this,arguments); Listen ('big',function(size){console.log(' big' +size+' square '); }) // houseObj. Listen ('small',function(size){console.log(' small: house is '+size+' m '); }) // Execute housej.trigger ('big',100); houseObj.trigger('small',150);Copy the code
-
So we’ll see that the console only prints twice, passing a unique key.
-
We know that for the above code, we can subscribe to houseObj for different users to buy a house, but what if we need to subscribe to fruit and shoes and other objects in the future, wouldn’t it be too much trouble to write this every time?
-
We need to copy the code above and change the object code below. This is very troublesome. To do this we need code encapsulation:
Let event = {list:{}, listen: Function (key, fn) {/ / add a unique identifier key / / if you are not subscribed to this message Give the message to create a cache list (enclosing a list [key] | | (enclosing a list [key] = [])), push (fn)}, Trigger: function () {/ / remove the message type name let key = Array. The prototype. The shift. The call (the arguments); let fns = this.list[key]; if(! fns || fns.length === 0){ return; } for(let i = 0,fn; fn = fns[i++];) { fn.apply(this,arguments); // arguments are the arguments that are sent to the message}}} // Define an initEvent function, Let initEvent = function(obj){for(let I in event){obj[I] = event[I]; }}; let houseObj = {}; // The publisher object initEvent(houseObj); HouseObj. Listen ('big',function(size){console.log(' houseObj: house is '+size+' m2 '); }) // houseObj. Listen ('small',function(size){console.log(' small: house is '+size+' m2 '); }) houseObj.trigger('big',100); houseObj.trigger('small',150);Copy the code
As mentioned above, all we need to do is call initEvent to make all objects have a publish-and-subscribe pattern.
So, next, if a user doesn’t want to subscribe
How do I unsubscribe?
Event.remove = function(key,fn){let FNS = this.list[key]; // Return false if(! fns){ return false; } // Cancel all subscriptions for the key message if(! fn){ fn && (fns.length = 0); }else{ for(let i = fns.length - 1; i >= 0; i-- ){ let _fn = fns[i]; _fn === fn && (fns.splice(i,1)); HouseObj. Listen ('big',fn1 = function(size){console.log(' houseObj ',fn1 = function(size){console.log(' houseObj ',fn1 = function(size){console.log(' houseObj ',fn1 = function(size)) The first house I want is '+size+' square meters '); }) houseObj. Listen ('big',fn2 = function(size){console.log(' +size+'); }) // delete houseObj. Remove ('big',fn2); houseObj.trigger('big',100);Copy the code
In this way, the console will only have one print, and only the first message will be left.
Continue deep decoupling
1. We added listen and Trigger methods to every publisher object, as well as a cache list, which is a waste of resources. 2. There is a certain coupling between Xiao Ming and the object of the sales office. Xiao Ming should at least know that the name of the object of the sales office is houseObj and that the house type is big, small or normal in order to subscribe to the event smoothly. So let’s go ahead and encapsulate a global publy-subscribe pattern object
Encapsulates the global publishing-subscribe pattern object
let Event = (function(){ let list = {}, listen, trigger, remove; listen = function(key,fn){ (list[key] || (list[key] = [])).push(fn); }; The trigger = function () {let key = Array. The prototype. The shift. The call (the arguments), / / remove the message corresponding to the callback function collection of FNS = list [key]; if(! fns || fns.length === 0){ return false; } for(let i = 0,fn; fn = fns[i++];) { fn.apply(this,arguments); // arguments are the arguments that are sent with the message}}; remove = function(key,fn){ let fns = list[key]; // Return false if(! fns){ return false; } // If no callback function is passed in, you need to cancel all subscriptions for the message corresponding to the key. // If xiao Ming doesn't buy the message at the sales office, he will cancel it. fn){ fn && (fns.length = 0); }else{ for(let i = fns.length - 1; i >= 0; i-- ){ let _fn = fns[i]; _fn === fn && (fns.splice(i,1)); // Delete the subscriber callback function}}}; return { listen:listen, trigger:trigger, remove:remove } })(); Event. Listen ('big',function(size){console.log(' +size+' SQM '); }) Event.trigger('big',100);Copy the code
In this way, the user even sales office is where all need not care, a high degree of decoupling
FAQ
1. Creating a subscriber itself takes time and memory, and when you subscribe to a message, the message may never happen, but the subscriber is always in memory. In addition, the publish-subscribe pattern can weaken the links between objects, but if overused, the necessary links between objects can be buried in the background, making it difficult to keep track of, maintain, and understand the application. Tracking a bug is not easy, especially when there are multiple publishers and subscribers nested together. 2. Don’t abuse