职责链模式允许多个类尝试着去处理一个请求,在这种情况下,这些类中的任何一个都不了解其他类的功能。该模式在这些类中提供一个松散耦合,唯有的共同联系是他们直接传递的请求。请求一直沿着链传递,知道有一个类能处理位置。
典型的例子就是应用软件的帮助系统,如果某个按钮有帮助信息,就会显示信息,否则则把帮助请求传递给下一模块,最终的情况是,消息被发送个一个广泛的帮助类,显示一个通用的帮助信息。这个数据结构非常像数据结构里的单链表,请求信息一直沿着链表传递,直到有能处理信息的类为止。
在职责链模式中,链的组织方式是从最具体的情形到最一般的情形;并不保证所有的请求都会产生反映。
职责链是一个很好的模式例子,有助于隔离程序中的每个对象所能完成的工作内容,降低了对象之间的耦合度。
第一个例子,一个命令解析程序,根据用户输入的命令,或者显示图像,或者显示颜色,或者显示文件, 或者直接显示用户输入的不可识别的命令。
用户界面如图:
这个应用中,用户可以输入任何命令,然后软件解析命令,如果发现用书输入"mandrill"则显示图像,如果输入的是"任何一个文件名的形式"则在中间的列表框显示当前目录下所有的文件,如果输入的是颜色,直接在下面的图像里显示颜色,如果输入的命令不可识别,则在最右侧的列表框里直接显示用户输入的不可识别的命令。
我们需要一个链表来处理这个用户输入的命令:Command--->Image File--->Color Name--->File Name--->General
为了编写职责链程序,我们从一个抽象的Chain开始:
using System;
namespace Chain
{
public abstract class Chain {
//describes how all chains work
private bool hasLink;
protected Chain chn;
public Chain() {
hasLink = false;
}
//you must implement this in derived classes
public abstract void sendToChain(string mesg);
//-----
public void addToChain(Chain c) {
//add new element to chain
chn = c;
hasLink = true; //flag existence
}
//-----
public Chain getChain() {
return chn; //get the chain link
}
//-----
public bool hasChain() {
return hasLink; //true if linked to another
}
//-----
protected void sendChain(string mesg) {
//send message on down the chain
if(chn != null)
chn.sendToChain (mesg);
}
}
}
然后按照消息在链表中的传递顺序,编写ImageChain:
using System;
using System.Windows.Forms;
using System.Drawing ;
using CsharpPats;
namespace Chain
{
/// <summary>
/// Summary description for ImageChain.
/// </summary>
public class ImageChain :Chain {
PictureBox picBox; //image goes here
//-----
public ImageChain(PictureBox pc) {
picBox = pc; //save reference
}
//-----
public override void sendToChain(string mesg) {
//put image in picture box
string fname = mesg + ".jpg"; //assume jpg filename
csFile fl = new csFile(fname);
if(fl.exists())
picBox.Image = new Bitmap(fname);
else{
if (hasChain()){ //send off down chain
chn.sendToChain(mesg);
}
}
}
}
}
接下来是ColorChain:
using System;
using System.Collections ;
using System.Drawing ;
using System.Windows.Forms ;
namespace Chain
{
/// <summary>
/// receives color names in chain
/// </summary>
public class ColorChain : Chain {
private Hashtable colHash; //color list kept here
private Panel panel; //color goes here
//-----
public ColorChain(Panel pnl) {
panel = pnl; //save reference
//create Hash table to correlate color names
//with actual Color objects
colHash = new Hashtable ();
colHash.Add ("red", Color.Red);
colHash.Add ("green", Color.Green);
colHash.Add ("blue", Color.Blue);
}
//-----
public override void sendToChain(string mesg) {
mesg = mesg.ToLower ();
try {
Color c = (Color)colHash[mesg];
//if this is a color, put it in the panel
panel.BackColor =c;
}
catch (NullReferenceException e) {
//send on if this doesn't work
sendChain(mesg);
}
}
}
}
接下来是FileChain:
using System;
using System.Windows.Forms ;
using System.IO ;
using System.IO.IsolatedStorage ;
using CsharpPats;
namespace Chain {
/// <summary>
/// Summary description for FileChain.
/// </summary>
public class FileChain : Chain {
ListBox flist;
public FileChain(ListBox lb) {
flist = lb;
}
//-----
public override void sendToChain( string mesg) {
//if the string matches any part of a filename
//put those filenames in the file list box
string[] files;
string fname = mesg + "*.*";
files = Directory.GetFiles(Directory.GetCurrentDirectory(), fname);
//add them all to the listbox
if (files.Length > 0){
for (int i = 0; i< files.Length; i++) {
csFile vbf = new csFile(files[i]);
flist.Items.Add(vbf.getRootName());
}
}
else {
if ( hasChain()) {
chn.sendToChain(mesg);
}
}
}
}
}
最后是通用的类型:
using System;
using System.Windows.Forms;
namespace Chain
{
/// <summary>
/// handles command that is not otherwise legal
/// </summary>
public class NoCmd :Chain {
private ListBox lsNocmd; //commands go here
//-----
public NoCmd(ListBox lb) {
lsNocmd = lb; //copy reference
}
//-----
public override void sendToChain(string mesg) {
//adds unknown commands to list box
lsNocmd.Items.Add (mesg);
}
}
}
在主程序中,我们就可以构造这样一个可以沿着链传递消息的链表:
private System.ComponentModel.Container components = null;
private Chain chn;
public Form1() {
InitializeComponent();
init();
}
private void init() {
//set up chains
ColorChain clrChain = new ColorChain(pnlColor);
FileChain flChain = new FileChain(lsFiles);
NoCmd noChain = new NoCmd(lsNocmd);
//create chain links
chn = new ImageChain(picImage);
chn.addToChain(clrChain);
clrChain.addToChain(flChain);
flChain.addToChain(noChain);
}
private void btSend_Click(object sender, System.EventArgs e) {
chn.sendToChain (txCommand.Text );
}
这样,就可以让消息沿着构造的链表传递,直到找到一个可以处理这个消息的类实例。
在这个例子中的类结构图如图:
另外一个例子,应用软件的帮助系统,当用户按下F1以后显示帮助信息,因为每个控件都有焦点,我们这个可以通过判断每个控件是否获得焦点来根据需要显示帮助信息。
这里处理的是空间而不是消息。
用户界面如图:
当有一个空间获得焦点以后,按下F1将显示这个控件的帮助信息,这个帮助请求将会在我们构建的链表中传递,直到有一个类发现自己的控件获得焦点,则显示相应的帮助信息。
using System;
using System.Windows.Forms;
namespace HelpChain
{
public abstract class Chain {
//describes how all chains work
private bool hasLink;
protected Control control;
protected Chain chn;
protected string message;
public Chain(Control c, string mesg) {
hasLink = false;
control = c; //save the control
message = mesg;
}
public abstract void sendToChain();
//-----
public void addToChain(Chain c) {
//add new element to chain
chn = c;
hasLink = true; //flag existence
}
//-----
public Chain getChain() {
return chn; //get the chain link
}
//-----
public bool hasChain() {
return hasLink; //true if linked to another
}
//-----
protected void sendChain() {
//send message on down the chain
if(chn != null)
chn.sendToChain ();
}
}
}
using System;
using System.Windows.Forms;
namespace HelpChain
{
/// <summary>
/// Summary description for ControlChain.
/// </summary>
public class ControlChain:Chain {
public ControlChain(Control c, string mesg):base(c, mesg) {
}
public override void sendToChain() {
//if it has the focus display the message
if (control.Focused ) {
MessageBox.Show (message);
}
else
//otherweise pass on down the chain
sendChain();
}
}
}
using System;
using System.Windows.Forms;
namespace HelpChain
{
/// <summary>
/// Summary description for EndChain.
/// </summary>
public class EndChain:Chain {
public EndChain(Control c, string mesg):base(c, mesg){}
public override void sendToChain() {
MessageBox.Show (message);
}
}
}
然后在主程序中构建链表:
private System.ComponentModel.Container components = null;
private Chain chn;
public Form1() {
InitializeComponent();
init();
}
private void init() {
chn = new ControlChain(btNew, "Create new files");
Chain fl =new ControlChain (btFile, "Select a file");
chn.addToChain (fl);
Chain bq = new ControlChain (btQuit, "Exit from program");
fl.addToChain (bq);
Chain cb =new ControlChain (ckBinary, "Use binary files");
bq.addToChain (cb);
Chain ct = new ControlChain (ckText, "Use text files");
cb.addToChain (ct);
Chain ce = new EndChain (this, "General message");
ct.addToChain (ce);
KeyEventHandler keyev = new KeyEventHandler(Form1_KeyDown);
btNew.KeyDown += keyev;
btFile.KeyDown += keyev;
btQuit.KeyDown += keyev;
ckBinary.KeyDown += keyev;
ckText.KeyDown += keyev;
}
private void Form1_KeyDown(object sender, KeyEventArgs e) {
if(e.KeyCode == Keys.F1 )
chn.sendToChain ();
}
同许多其他模式一样,职责链模式的主要目的是降低对象之间的耦合度,一个对象只需要知道如何把请求传递给另一个对象。
链中的每个对象都是自包含的,完全不了解其他的对象,并且只需要确定自身是否满足请求,这使得每个对象的编写和构建都变得非常的容易。