Observer Pattern

  • closely related to pubsub pattern, in fact, many people think of pubsub as a special use of the observer pattern
  • difference is that pubsub create global message buss for communication between components, while observer is more specific and requires that observes subscibe directly to object being observed
  • this pattern promotes loose coupling between objects, and is very common in javascript

Initializing the Design Pattern

  • we will need a object called subject, which is the object be observed, and we need an observer
  • subject will maintain list of observes and provides an api to allow for observation and notification of changes
  • observer list is entity in its own right that handles adding and removing observers

Step 1: Page – observer.js


// JavaScript Document
define(function() {
	'use strict';
	
	var Observers = function() { //constructor for the observers object
		this.observers = []; //we set a single property called observers, and initialize it with an empty array
	};
	
	Observers.prototype.add = function (observer) { //this is the method to add an observer
		this.observers.push(observer); //receives the observer as an argument, then pushes that observer to the array
	};
	
	Observers.prototype.remove = function (observerToRemove) { //method for removing observer
		this.observers = this.observers.filter(function(observer) { //javascripts filter array method is used to test each object in array.  Filter method doesn't alter array its called upon, but it does create new array, so the new array is then set to the value of the old array.  The callback function passed to filter method is passed current observer and is invoked for each member of array
			return observer !== observerToRemove; //this method returns true if item should be included, and false if not.  In this case, when observer mathes observerToRemove, returns false and observerToRemove is not included in returned array.
		});
	};
	
	Observers.prototype.get = function () { //method to get list of observers
		return this.observers; //just returns list of observers
	};
	
	return Observers; //then return the constructor
});

Step 2: Page – subject.js


// JavaScript Document
define(function(require) {
	'use strict';
	
	var Observers = require('observer/observers'); //use require module to bring in observer.js we just created.  Capitalize the object because it takes in a constructor
	
	var Collection = function (items) { //the subject will hold of list of items the observer is subscribed to.  Subject is based on array and observers are notified whenever an item is added or removed from this array
		this.observers = new Observers(); //subject maintains list of observers
		this.collection = items || []; //the array being observed is under property called collection, and is initalized as empty array
	};
	
	Collection.prototype.observe = function (observer) { //observe method receive observer to add
		this.observers.add(observer); //and passes the observer to the array with the add method
	};
	
	Collection.prototype.unObserve = function (observer) {//unObserve method receive observer
		this.observers.remove(observer);	//and passes data to the remove method to be taken from the array
	};
	
	Collection.prototype.notify = function (event, data) { //this method will notify observer of a change in state.  Receives event and data to pass to observers
		this.observers.get().forEach(function (observer) { //inside method, must get the list of observers.  Then hook into the array with forEach method and create callback that is passed each observer from array of observers
			observer.notify(event, data); //inside callback we use notify method and pass through name of event, and the data
		});
	};
	
	Collection.prototype.add = function (item) { //create subjects add method receive the new item to add to the array
		this.collection.push(item); //then pushes the new item into the collection array
		this.notify('added', item); //then invokes notify method and specifies the event name as added, then passes item that was added to collection
	};
	
	Collection.prototype.remove = function (itemToRemove) { //remove method for collection object
		this.collection = this.collection.filter(function (item) { //receive the item to remove and uses filter method of override collection array
			if (item !== itemToRemove) { //if the item is not the item to remove, returns true
				return true;
			}
			this.notify('removed', item); //if it is the item to remove, we invoke notify method first and specify the event name as removed, then pass the item for removal
			return false;
		}, this);
	};
	
	return Collection; //return the constructor
});

Step 3: Page – observer.js


// JavaScript Document
define(function() {
	'use strict';
	
	var Observer = function (name) { //add simple observer class so we can create new observers.  The constructor will accept a name property so we know which observer is which
		this.name = name;
	};
	
	Observer.prototype.notify = function (event, data) { //observers will need a notify method
		console.log('The event was ', '"' + event + '",', 'the data was', data, 'and I am ', this.name);//add log statement for example
	};
	
	return Observer;
});

Step 4: Page – main.js


// JavaScript Document
require (
	['factory/init', 'pubSub/init', 'strategy/init', 'observer/init'],
	function (factory, pubSub, strategy, observer) {
			'use strict';
	
			var examples = {
					factory: factory,
					pubSub: pubSub,
					strategy: strategy,
					observer: observer
				};
			
			window.runExample = function (example) {
				examples[example].init();
			};
	}
);

Step 5: Page – init.js


define(function(require) {
	'use strict';

	return {
		init: function() {
			var subject, observer, otherObserver, data, moreData, //add variables
				Subject = require('observer/subject'), //oad subject module
				Observer = require('observer/observer'); //load observer module
				
			subject = new Subject(); //instantiate a subject
			observer = new Observer('observer1'); //instantiate observer
			otherObserver = new Observer('observer2'); //initialize other observer
			
			data = { //create data objects to pass into items being observed
				prop: 'something'
			};
			moreData = {
				prop: 'something else'
			};
			
			subject.observe(observer); //add our observers to observer collection
			subject.observe(otherObserver);
			
			subject.add(data); //add our data to the subjects collection
			subject.add(moreData);
			
			subject.unObserve(observer);
			
			subject.remove(data);
		}
	};
});

In this situation, the observers will be coupled to the subjects. This isn’t inherently bad as some coupling is okay, and indeed, data in your applications will often necessarily be coupled. However, we want to avoid any unnecessary, unintentional or excessive coupling