Definition
Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.
Explanation
In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain. A mechanism also exists for adding new processing objects to the end of this chain.
In a variation of the standard chain-of-responsibility model, some handlers may act as dispatchers, capable of sending commands out in a variety of directions, forming a tree of responsibility. In some cases, this can occur recursively, with processing objects calling higher-up processing objects with commands that attempt to solve some smaller part of the problem; in this case recursion continues until the command is processed, or the entire tree has been explored. An XML interpreter might work in this manner.
Screencast
TypeScript Code
module ChainOfResponsibility { interface ICancellableEvent { add(eventHandler: (sender: any, eventArgs: T) => void): void; remove(eventHandler: (sender: any, eventArgs: T) => void): void; } interface ICancellableEventArgs { cancel: boolean; } class CancellableEvent implements ICancellableEvent { private _eventHandlers = new Array<(sender: any, eventArgs: T) => void>(); public add(eventHandler: (sender: any, eventArgs: T) => void): void { if (this._eventHandlers.indexOf(eventHandler) === -1) { this._eventHandlers.push(eventHandler); } } public remove(eventHandler: (sender: any, eventArgs: T) => void): void { var i = this._eventHandlers.indexOf(eventHandler); if (i !== -1) { this._eventHandlers.splice(i, 1); } } public raise(sender: any, e: T): void { var eventHandlers = this._eventHandlers.slice(0); for (var i = 0, j = eventHandlers.length; i < j && !e.cancel; i++) { try { eventHandlers[i](sender, e); } catch (ex) { Output.WriteLine(ex.message); } } } } class ValidationEventArgs implements ICancellableEventArgs { constructor(public value: T) { } public isValid: boolean = true; public cancel: boolean = false; public result: string; public status() { if (this.isValid) { Output.WriteLine("The value: '" + this.value + "' is valid!"); } else { Output.WriteLine(this.result); } } } class ValidationChain { private _validators = new CancellableEvent<ValidationEventArgs>(); public get validators(): ICancellableEvent<ValidationEventArgs> { return this._validators; } public validate(eventArgs: ValidationEventArgs): void { this._validators.raise(this, eventArgs); } } class StringValidators { public static nullValidator = function (sender: any, eventArgs: ValidationEventArgs) { if (eventArgs.value === null) { eventArgs.result = "String is null."; eventArgs.cancel = true; eventArgs.isValid = false; } } public static emptyValidator = function (sender: any, eventArgs: ValidationEventArgs) { if (eventArgs.value.trim().length <= 0) { eventArgs.result = "String is empty."; eventArgs.cancel = true; eventArgs.isValid = false; } } public static createLengthValidator(maxLength: number) { return function (sender: any, eventArgs: ValidationEventArgs) { if (eventArgs.value.length > maxLength) { eventArgs.result = "The string '" + eventArgs.value + "' is too long!"; eventArgs.cancel = true; eventArgs.isValid = false; } } } } window.addEventListener("load", function () { var valuesToValidate: Array = [null, "", "12345678910", "Valid"]; var validationChain = new ValidationChain(); validationChain.validators.add(StringValidators.nullValidator); validationChain.validators.add(StringValidators.emptyValidator); validationChain.validators.add(StringValidators.createLengthValidator(10)); valuesToValidate.forEach((value) => { var eventArgs = new ValidationEventArgs(value); validationChain.validate(eventArgs); eventArgs.status() }); }); }
Output