ghytred.com


NewsLook for Outlook

Patterns and OO
ChainOfResponsibility
The Web Site you seek
Cannot be located, so
Waste your time here

Contents

Chain of Responsibility

From GoF:Avoid coupling the sender of a request to its receiver by giving more than object a chance to handle the request. Chain the receiving objects and pass the request along the chain until one of them handles it.

Places where Chains might be used:

  • GUI Event Handlers: a mouse click is sent to a button; if it has a listener for the event it sends the event otherwise it passes the event to its parent control and so on.
  • A Security Hierarchy: an object representing a user is asked whether the user has permission for some action. If this object doesn�t know, it passes the request to its parent - a user department, say - and so on through division, subsidiary, etc.

Chains can be seen as Composites where each leaf and node knows its parent node; however there is also the difference that the initial �entry� to the chain is from the bottom of the structure, not the top. A composite will delegate - pass requests down; a Chain of Responsibility passes requests up the chain and for this reason the pattern is also known as Bureaucrat.

The GoF example implementation shows each class involved in the chain having a common ancestor, which defines the handler and parent methods. This might not always be possible or desirable as in the help chain shown below for controls on a Form. If the objects to be chained already exist, as in that case, a parallel hierarchy must be built. If they don�t exist, or do but can be changed, a common ancestor might still not be convenient; in this case adding an Interface might be the best solution.

In the two usage suggestions above, there is a difference in the number of objects at each level in the Chain. In the first one, there would be one at the 'top' level, corresponding to the Form. This would be the parent of others, each corresponding to the number of directly contained controls of the Form. Each of these in turn has one member in the chain for each contained control, and so on. This is a genuine composite.

In the second example, if the user object represents the currently logged-on user then there will only be one user object at the bottom of the chain, whose parent is a department etc. This object is probably always the object to which security requests are initially sent, and this could usefully be represented as a Singleton. In this example whilst each object in the chain must know its parent, does it have to know its children? Is calling the successor object a �parent� correct? It is a composite in any sense at all - and does it matter?

It is also imaginable that at for instance Department level the handler knows that security requests for a particular access must go to a different structure altogether, and so instead of forwarding it to its parent it forwards it to some other structure altogether. Is this still a Chain? It�s starting to look like a Composite where the request comes in at the top and each level handles it, has one child to delegate it to, or knows which of its children to delegate it to.

A further difference in the two examples is that in the first there has to be a relationship between each control and the handler in the Chain so that the Event Dispatcher (i.e. what detects the mouse click in the first place) can find the appropriate handler to start the chain off. In the second, access is so fixed that a Singleton could be used. One of the basic problems when implementing Chain is that of accessing it: how is the system to know which member of the Chain to start with?

Example of a Chain

This chain has a 1-1 correspondance with controls on a WinForm. Asking for help on any control enters the chain at the node related to the control and searches upwards until help is found.

First, the Chain nodes:
namespace GofPatterns.ChainOfResponsibility {
	/// 
	/// The Help example in the book requires subclassing each widget
	/// (button, window etc) to add the HandleHelp method and associated data. 
	/// I'm not going to do this, as I don't think it's a realistic
	/// thing to expect *every* control to be subclassed in this way.
	/// This implementation defines a HelpHandler object in the form, and 
	/// creates a Chain with an entry for each control in the form. When F1
	/// is hit, the current control is passed to the Chain which looks for the
	/// entry for that control; if it finds a help entry it calls it;
	/// if it doesn't, it passed up to the next control in the chain, and so on.
	/// The Chain is very like a Composite; the difference is that while in a 
	/// Composite each node knows its children but not it's parent in a chain
	/// each node knows its parent but not its children. 
	/// The search enters the Chain at some point, and continues up the chain, 
	/// parent by parent, untill a topic is discovered and displayed.
	/// At the top (parent == null) a disconsolate message is shown.
	/// 
	/// 
	public class Chain {
		private Chain parent;
		private Control control;
		private string helpTopic = "";

		public Chain(Control control, Chain parent) {
			this.control = control;
         this.parent = parent;
		}
		public string HelpTopic {
			get { return helpTopic; }
			set { helpTopic = value; }
		}
		public void HandleHelp() {
			Console.WriteLine("Looking in Chain for {0}", control.Name);
			if (this.Handles) {
				// show control responding
				Color save = control.BackColor;
				control.BackColor = Color.Goldenrod;
				MessageBox.Show(string.Format("{0} says:\r\n{1}\r\n"
							+ "Hope that helps!", 
								control.Name, 
								helpTopic));
				control.BackColor = save;
			} else if (parent != null) {
				parent.HandleHelp();
			} else {
				MessageBox.Show("No help can be found\r\n"
					+"We wish to hold the whole sky,\r\n"
					+ "but we never will.");
			}
		}
		public bool Handles {
			get { return (helpTopic != ""); }
		}
		public override string ToString() {
			return control.Name;
		}
	}
}
Now setting it up in the form and displaying the help:
		private Hashtable helpChain;
		private void LoadChain() {
			helpChain = new Hashtable();
			LoadChainControl(this, null);
		}
		private void LoadChainControl(Control toAdd, Chain parent) {
			Chain chain = new Chain(toAdd, parent);
			helpChain.Add(chain.ToString(), chain);
			foreach(Control c in toAdd.Controls) {
				LoadChainControl(c, chain);
			}
		}
		private void AddHelpTopics() {
			Chain c = helpChain[panel1.Name] as Chain;
			c.HelpTopic = "A file that big?\r\nIt might be very useful.\r\n"
				+ "But now it is gone.";
			c = helpChain[button1.Name] as Chain;
			c.HelpTopic = "The web site you seek\r\nCannot be located but\r\n"
				+ "Countless more exist.";
			c = helpChain[button2.Name] as Chain;
			c.HelpTopic = "Having been erased,\r\nThe document youre seeking\r\n"
				+ "Must now be retyped.";
			c = helpChain[tabControl1.TabPages[0].Name] as Chain;
			c.HelpTopic = "A crash reduces \r\nyour expensive computer\r\n"
				+ "To a simple stone.";
		}

		private void ChainForm_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e) {
			if (e.KeyCode == Keys.F1) {
				Chain c = helpChain[this.ActiveControl.Name] as Chain;
				if (c != null) {
					c.HandleHelp();
				}
			}
		}

Return to top