Chain of Responsibility Design Pattern
Chain of Responsibility Design Pattern

Features a request sender and one or more request handlers that form a chain

  • the request sender passes the request to the first handler in the chain
  • the handler then acts on the request, or passes the request to the next handler in the event that it cannot
    • this process is repeated until the request is acted upon or it reaches the end of the chain
    • another variation is for all handlers to handle the request at the exact same time
  • the purpose of this pattern is to decouple the request sender from the request handler
  • it is not a common design pattern implemented in JavaScript, but is a good exercise in object oriented programming in JS

We will be creating a request sender that is a message (text, email, etc.) and request handlers that can interperet each type of message

Step 1: Page: handler.js


// JavaScript Document - This will be the base class for the individual handlers
define(function() {
	'use strict';
	
	var CommunicationHandler = function (communicationType, handler, nextHandler) { //this constructor will accept potentially three arguments.  First two are required and are type of communication and the actual handler to process the communication.  The next argument is the next handler in the chain, if there is one.  These are set as properties of the handler object:
	
		this.communicationType = communicationType;
		this.handler = handler;
		this.nextHandler = nextHandler;
		
	};
	
	CommunicationHandler.prototype.handleCommunication = function (communication) { //add a handle request method to prototype for all handlers to inherit
		if (communication.type !== this.communicationType) { //this checks whether communcation type of the request is equal to the communication type for the handler, so the handler knows whether it can process the request or not
			(this.nextHandler) ? this.nextHandler.handleCommunication(communication) : //if the handler can't process the communication type, we must check if there is a next handler to pass the request to.  We us a JS terniary operator to see if this.nextHandler is falsy.  If not, we invoke the handle communication method of next handler and pass it the communication.  If so (which means there is no nextHandler specified), return the log statement below
				console.log('Communication type', communication.type, 'could not be handled');
			return;
		}
		this.handler(communication); //if the handler can handle communcation type, pass the communcation to the handler.
	};
	
	return CommunicationHandler;
});

Step 2 – Page: email.js


// JavaScript Document -
define(function(require) { 
	'use strict';
	
	var Handler = require('COR/handler'), //use require function to load the base handler
		emailHandler;
		
	emailHandler = new Handler('email', handleEmail, null); //create email handler by created new instance of base handler class.  Has a type of email, and the handler will be a function called handleEmail.  This will be last stop in the chain, so the nextHandler is set to null
	
	function handleEmail(email) { //now define handleEmail function as simple console log statement
		console.log('Email sent to', email.recipient, 'message: ', email.message);	
	}
	
	return emailHandler; //return instance of emailHandler, not the constructor
});

Step 3 – Page: text.js


// JavaScript Document -
define(function(require) { 
	'use strict';
	
	var Handler = require('COR/handler'), //use require function to load the base handler
		emailHandler = require('COR/handlers/email'), //also load in the email handler, that is the next step in the chain from this.
		smsHandler;
		
	smsHandler = new Handler('sms', handleSMS, emailHandler); //set up the handler, takes sms data type, passes to handleSMS function, with emailHandler the next stop in the chain
	
	function handleSMS(sms) { //set up the function to receive the message and log it to the console
		console.log('SMS sent to number', sms.number, 'message: ', sms.message);	
	}
	
	return smsHandler; //return the instance
});

Step 4 – Page: call.js


// JavaScript Document -
define(function(require) { 
	'use strict';
	
	var Handler = require('COR/handler'), //use require function to load the base handler
		smsHandler = require('COR/handlers/text'), //load text handler as it is the next step in the chain
		callHandler;
		
	callHandler = new Handler('call', handleCall, smsHandler); //set up the handler, takes call as data type, passes to handleCall function, with smsHandler as the next stop in the chain
	
	function handleCall(call) { //set up the function to receive the message and log it to the console
		console.log('Call placed to number', call.number, 'from number ', call.ownNumber);	
	}
	
	return callHandler; //return the instance
});

Step 5 – Page: init.js


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

	return {
		init: function() {
			
			var call, sms, email, handler, telepathy, //create communication object variables
				Handler = require('COR/handler'), //capitalize the constructor
				callHandler = require('COR/handlers/call'); //we load the first handler in the chain, the call handler.  In real world application, first handler will always be the most common communication type
				
			call = {
				type: 'call',
				number: '0123456789',
				ownNumber: '987654321'
			}; //set up a call
			
			sms = {
				type: 'sms',
				number: '02468357',
				message: 'Hey Guy'	
			}; //set up a text message
			
			email = {
				type: 'email',
				recipient: 'james@jamesportis.com',
				message: 'Hey Guy'	
			}; //set up an email
			
			telepathy = {
				type: 'esp',
				target: 'Anyone in the world',
				message: 'I am in your head'	
			}; //this will not display at all is there is no handler for this data type
			
			handler = new Handler(null, null, callHandler); //to set up an empty handler, create instance of handler with no type and no handler, then start the chain by passing it the first step, or the call handler
			handler.handleCommunication(email); //now start the process using handlers handleRequest method.
			handler.handleCommunication(sms);
			handler.handleCommunication(call);
			handler.handleCommunication(telepathy);
		}
	};
});

Step 6 – Page: main.js


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

Each handler is only processing one type of data. This makes it easy to add new handlers, as each handler is only coupled to one other handler (next in chain), and the client is only coupled to first handler.

This method is useful if we have a variety of unique data types that all have to be handled in individual ways

  • great for loose coupling
  • makes extending the system very easy