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