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();
}
}
}