允许多个类处理同一个请求而不需要了解彼此的功能。它在类之间提供了一种松散的耦合:类之间的唯一的联系就是相互间传递的请求。请求在类之间传递,直到其中一个类处理它为止。
特性:一、链的组织是从最特殊到最一般;二、不能保证请求一定能响应。
职责链将程序中每个对象能做什么的内容隔离。它的使用范围:
1、 具有相同方法的几个对象都适合于执行程序请求的操作,但由对象决定谁去完成操作,比把决策建立在调用代码中更合适。
2、 其中某个对象可能最适合处理请求,但你不想通过一些列If-else或switch语句去选择一个特定的对象。
3、 程序执行时,需要向处理选项链中添加的对象。
4、 在多个对象都能执行一个请求的情况下,你不想把这些相互作用的内容放在调用程序里。
C#内部,窗口接受各种事件,如MouseMove事件,然后将事件转发给窗体所包含的控件。C#中只有最后的控件接受消息,而在其他语言里,每个内部控件都能接受消息。这是职责链模式的一种典型实现。我们通常认为,C#的类继承结构本身就是该模式的一个例子。如果在底层的派生类里调用一个方法,则该方法调用沿着继承链向上传递,直到发现包含有该方法的第一个父类为止,而更上层的、包含该方法其它实现的父类不会起作用。如:
public class A
{
public virtual void DoSomething()
{ System.Console.WriteLine("
执行类为:A"
); }
}
public class B : A
{
public override void DoSomething()
{ System.Console.WriteLine("
执行类为:B"
); }
}
public
class C : B { }
//
客户代码:C c =new C(); c.DoSomething();
//
执行的是类B的方法,而非类A的方法。
//
要想调用A的方法唯有:
public interface I { void DoSomething();}
public class A : I
{
public virtual void DoSomething()
{ System.Console.WriteLine("
执行类为:A"
); }
}
//
类B没有直接实现接口I,而是继承实现。所以可以有:
// I i = new B();
//
但i.DoSomething()方法调用的是A类的方法
//
只要是实现了接口,调用的就是由该类向其父类查找第一个实现了该方法的类的方法
public class B : A
{
public new void DoSomething()
{ System.Console.WriteLine("
执行类为:B"
); }
}
public class C : B { }
同其它许多模式一样,该模式的主要目的是减少对象之间的耦合。一个对象只需要知道如何将请求转发给其他对象。
链中的每个C#对象都是“自治”的,它对其它对象一无所知,只需判断它本身能否满足请求。这样,既能独立编写每个对象,又很容易构建链。
你可以决定,链中的最后一个对象使以默认方式处理它收到的所有请求,还是简单地抛弃请求。不管怎样,你必须知道链中最后一个可用的对象是哪一个。
下面是一个职责链模式样例:
/*
键入“Mandrill”,能看到图像Mandrill.jpg的显示;
键入“File”,相应的文件名显示在中央列表框中;
键入“Blue”,对应的颜色显示在县面的中央面板中;
如果键入的是其它的字符,则文本会显示在最右边的列表框中。
*/
public abstract class Chain
{
bool hasLink;
protected Chain chn;
public Chain() { hasLink = false; }
public void AddToChain(Chain c) { chn = c; hasLink = true; }
public Chain GetChain() { return chn; }
public bool HasChain() { return hasLink; }
protected void SendChain(string mesg)
{ if (chn != null) chn.SendToChain(mesg); }
}
//
图像、文件、颜色类ImageChain、FileChain、ColorChain、NoChain均继承自Chain类。
/
如此我们只要在程序初始化时,添加如下代码:
void InitChain()
{
Chain chn;
ColorChain clrChain = new ColorChain();
FileChain flChain = new FileChain();
NoChain noChain = new NoChain();
chn = new ImageChain();
chn.AddToChain(flChain);
flChain.AddToChain(clrChain);
clrChain.AddToChain(noChain);
}
最后,由于C#部提供多继承,有时候需要基类Chain是一个接口。同时职责链也不必须是线性的,可以是树形或其它结构的。
《Thinking in Pattern
》:
职责链模式可以被想象成递归(recursion)的动态泛化(generalization),这种泛化通过使用Strategy对象来完成。被调用的时候,链表里的每个Strategy对象都试图去满足这次调用。当某个策略(strategy)调用成功或者整个strategy链到达末尾的时候,这个过程结束。递归的时候,某个方法不断的调用它自己直到满足某个结束条件;对于职责链,某个方法调用它自己,进而(通过遍历strategy链表)调用它自己的不同实现,直到满足某个结束条件。所谓结束条件,要么是到达链表的末尾,要么是某一个strategy调用成功。对于第一种情况,得返回一个默认对象;如果不能提供一个默认结果,那就必须以某种方式告知调用者链表访问成功与否。 由于strategy链表中可能会有多于一个的方法满足要求,所以职责链似乎有点专家系统(expert system)的味道。因为这一系列Strategy实际上是一个链表,它可以被动态创建,所以你也可以把职责链想象成更一般化的,动态构建的switch语句。
在《Thinking in Pattern》书中的例子中,就将所有的解决方案放置在一个Strategy数组中,for语句遍历询问每一个Strategy对象是否解决了问题。这同《C#设计模式中文版》的解决方法不同。我觉得,将职责链作为一有向图来实现更加的符合实际。查找一个有向图具体节点就是我们需要找的解决方法。将所有的查询、添加职责等操作交给有向图对象,而不是封装在具体的Strategy中,这不更加完美吗?
思考:如何使用职责链实现邮件过滤?
《C#设计模式中文版》
命令模式只将请求转发给一个特定对象。它把一个申请特定操作的请求封装到一个对象中,并给该对象一个众所周知的公共接口,使客户不用了解实际执行的操作就能产生请求,也可以使你改变操作而丝毫不影响客户端程序。例:
//
将申请Execute操作封装在Command类中
//ICommand
就是众所周知的申请操作的接口
public interface ICommand { void Execute(); }
//
命令的实现者
public class ImplementHere : ICommand
{
public void Execute()
{ Console.WriteLine("//
方法的具体实现"
); }
}
//
客户端通过接口调用具体的实现方法
ICommand cmd = new ImplementHere();
cmd.Execute();
上面代码我看不出命令模式的意义所在,为此我为调用客户代码定义了一个方法:
public static void CommandExecute(ICommand cmd) { cmd.Execute(); }
这样我们就可以为指定操作定义不同的实现。显而易见的,这将客户代码和ICommand接口对象紧密的联系在一起。这并不是命令模式本来的意思。为此我们可以将
实现
ICommand
接口的类对象从执行指定操作的类中分离出来,也即实现和调用的分离。例:
//
执行命令操作的类必须实现该接口
public interface ICommandHolder
{ ICommand GetCommand(); void SetCommand(ICommand cmd); }
//
执行命令操作的类必须实现该接口
public interface ICommandHolder
{ ICommand GetCommand(); void SetCommand(ICommand cmd); }
//
命令接口
public interface ICommand { void Execute(); }
//
所有命令都单独定义为一个命令类,如打开文件这一命令:
public class OpenCommand : ICommand
{ public void Execute() { //
打开文件的具体实现
} }
这样,我们就可以在客户端代码指定执行操作,而不是关心操作代码的具体实现。其客户代码为:
CommandNeed cmdObj = new CommandNeed();
cmdObj.SetCommand(new OpenCommand());
cmdObj.GetCommand().Execute();
命令模式提供了一个便捷的存储方法并能完成Undo功能。每个命令对象都记住刚做过的事,并在有Undo请求时,只要计算量和内存需求不太过分,就能恢复到刚才的状态。我们重写Icommand接口使其包含Undo方法:
public
interface ICommand
{ void Execute(); void Undo(); bool IsUndo();}
并且我们还必须让每个命令对象记录上一次的操作,这样才能取消操作。这就要求,首先,必须保存一个一执行命令列表;其次,每个命令必须包含一个自身执行情况的列表。
执行恢复操作关键就在于必须额外的提供一个UndoCommand类,我们通过调用该类的Execute方法而调用指定操作的Undo()方法。
例:自定义一个按钮,要求点击该按钮可以执行取消工作。
public static class CommandCall
{
UndoCommand undoCommand;
public static void CommandClick(object sender, EventArgs e)
{
ICommand cmd = ((ICommandHolder)sender).GetCommand();
cmd.Execute();
undoCommand.Add(cmd);
}
}
//
其中UndoCommand类定义如下:
public class UndoCommand : ICommand
{
ArrayList undoList;
public UndoCommand() { undoList = new ArrayList(); }
public void Add(ICommand cmd) { if (!cmd.IsUndo()) { undoList.Add(cmd); } }
public bool IsUndo() { return true; }
public void Undo(){ //
恢复操作
}
public void Execute()
{
int index = undoList.Count - 1;
if (index >= 0)
{
ICommand cmd = (ICommand)undoList[index];
cmd.Undo();
undoList.RemoveAt(index);
}
}
}
public interface ICommand { void Execute(); void Undo(); bool IsUndo(); }
public interface ICommandHolder
{ ICommand GetCommand(); void SetCommand(ICommand cmd); }
public class UndoButton : Button,ICommandHolder
{
ICommand command;
public ICommand GetCommand() { return command; }
public void SetCommand(ICommand cmd) { command = cmd; }
}
//
客户端代码
UndoButton unBtn = new UndoButton();
unBtn.SetCommand(new UndoCommand());
unBtn.Click += CommandCall.CommandClick;
思考题:1、鼠标单击列表框项目和单选按钮可构成命令,单击多选列表框也可以表示为命令。设计一个包含这些特殊的程序。
2、乐天彩票系统使用一个随机数字生成器将整数限制在1-50的范围内,号码选择的间隔由一个随机定时器选定,选择的每个号码必须是唯一的。设计一个模式来选出每周的中将号码。
《Thinking in Pattern
》
从本质上说,Command就是一个函数对象:一个被封装成对象的方法。通过把方法封装到一个对象,你可以把它当作参数传给其它方法或者对象,让它们在实现你的某个请求(request)的时候完成一些特殊的操作。
Command模式最重要的一点就是,你可以通过它把想要完成的动作(action)交给一个方法或者对象。
《C#
设计模式》
解释器模式:通常描述的是,为了某种语言定义一个文法,并用该文法解释语言中的语句。当一个程序要处理许多不同但又有些类似的问题时,用一种简单的语言描述这些问题,然后让程序解释该语言,是非常便利的方法。
该模式适用性:
1、 当读者需要用一个命令解释分析用户命令时。如用户可以通过输入查徐获得各种答案。
2、 当程序需要分析一个代数串时。如用户输入某个方程式,要求程序根基计算结构执行相应的操作。
3、 当程序要生成各种形式的输出时。如考虑一个程序,它可以按任意顺序显示激烈数据,并能用不同的方法对它们排序。这类程序通常称作表表生成器(Report Generators),它的底层数据可以存在一个关系数据库中,报表程序的用户接口通常比数据库用的Sql语言简单的多。
解释器模式一般有下列参与对象:
1、抽象表达式(AbstractExpression):声明一个抽象的解释操作。
2、终结符表达式(TerminalExpression):解释由文法中终结符组成的表达式。
3、非终结附表达式(NonTerminalExpression):解释文法中所有非终结符表达式。
4、上下文(Context):包含了分析器的一部分全局信息。
5、客户:格局前面的表达式类型构建语法树并调用解释操作。
《Thinking in Pattern
》
如果程序的用户在运行时刻需要更大的灵活性,例如,为了通过创建脚本来描述所期望的系统行为,你就可以使用Interpreter设计模式。这种情况下,你(需要)创建一个语言解释器并把它嵌入到你的程序中。
别忘了,每种设计模式都允许一个或多个可以变化的因素,所以,重要的是首先要知道哪个因素是变化的。有些时候是你的程序的最终用户(而不是程序的编写人员)在他们配置程序的某些方面的时候需要更大的灵活性。也就是说,他们需要做一些简单的编程工作。Interpreter模式通过添加一个语言解释器提供了这种灵活性。 问题是,开发你自己的语言并为它构建一个解释器是一项耗时的工作,而且它会分散你开发应用程序的精力。你必须问问自己是想要写完应用程序呢还是要创造一门新的语言。最好的解决方案就是代码重用:嵌入一个已经构建好并且调试过的解释器。
《C#
设计模式》
迭代器模式允许使用一个标准的接口顺序访问一个数据列表或集合,而又不需要知道数据的内部表示细节。另外,也可以定义专用迭代器,让它完成一些特殊操作并且只返回数据集合中的特定元素。”Design Patterns”中指出,一个恰当的迭代器接口可以是下列形式:
public interface IIterator
{
object First(); object Next();
bool IsDone(); object CurrentItem();
}
迭代器模式的效果:
1、 数据修改。迭代器中最重要的问题是,遍历数据的同时数据正在被修改。如果代码搜索范围较宽,只是偶尔才移向下一个元素,就有可能在访问集合的同时,底层集合正在添加或删除元素,也可能有另一个线程改变了集合。对这一问题没有简单的解决方案。如果读者想使用迭代器顺序访问一系列数据并删除特定的项目,要当心这种操作的后果。添加或删除一个元素意味着可能会遗漏掉某一个元素或对同一元素访问了两次,这和我们使用的存储机制有关。
2、 迭代器类需要对基本容器类的底层数据结构具有特殊的访问权限,这样才能访问数据。
3、 内部迭代器是能顺序访问整个集合的一些方法,不需要用户发出任何明确的请求就能直接对每个元素进行操作。外部迭代器给我们更多的控制权,因为调用程序可直接访问每个元素,并能决定是否对元素执行操作。
《C#设计模式》
中介者作为唯一知道其它类中方法细节的一个类,在类发生变化时通知中介者,中介者再将这些变化传递给其他需要通知的类。
中介者模式的效果:
1、 当一个类的动作要受另一个类的状态影响时,中介者模式可以防止这些类变得混乱。
2、 使用中介者很容易修改一个程序的行为。对多数形式的修改来说,只需要改变或子类化中介者,程序的其它部分保持不表。
3、 可以添加新的控件或类,除了改变中介者外,不用改动任何部分。
4、 中介者解决了每个ICommand对象需要对于接口中的其他对象和方法了解太多的问题。
5、 中介者变成了一个“万能类”,直到太多其它部分程序的信息,这会使它难以修改和维护。有时候可以该上这种状态:将大部分的工能放在各个类中,少部分放在中介者中。每个对象应该完成自己的任务,中介者只需管理对象之间的相互协作。
6、 每个中介者都是一个定制的类,它拥有每个同事要调用的方法,并指导每个同事有那些可用的方法。这使得在不同项目中重用中介者变得困难。从另一方面来说,决大多说中介者都相当简单,编写中介者代码总要比管理复杂的相互协作容易的多。
单接口的中介者:这种中介者模式相当于一种观察者模式,观察每个同事元素的变化,每个元素都对中介者有一个定制的接口。另外一种实现方法是,中介者只有一个接口,通知中介者执行那种操作的对象作为参数传递给该接口方法。在这种方法中,我们不再需要登记运行的组建,只需要创建一个action方法,它针对每个对象具有不同的多态参数。
public void action(MoveButton mv);
public void action(clrButton clr);
这样,就不必登记所有的动作对象,如按钮和资源表框,应为可以将其作为参数传递给每隔action方法。
例:
考虑这样一个程序,它有两个按钮(Copy和Clear按钮)、两个列表框和一个文本输入域。程序开始运行时,Copy和Clear按钮都是不可用的。
1
、当选择左编列表框中的一个名字时,它被拷贝到文本域中进行编辑,此时Copy按钮可用。
2
、单击Copy按钮,文本被加到右边列表况钟,此时Clear按钮可用。
3
、如果单击CLear按钮,右边列表框和文本域被清控,列表框时未选中状态,两个按钮都不可用。
控件间的相互协作很复杂,我们可以用中介者模式简化控件的协作。中介者模式让中介者作为唯一了解系统其它类的一个类。每个和中介者通信的控件都称为同事。每个同事收到用户事件时通知中介者,中介者决定应该将该事件通知给其他那一个类。
//Copy
、Clear等操作封装进Mediator类中
//
从而实现将复制等操作时可能引起的控件协作全封装在该类中
//
但对于操作,如ClearClicked,其关联的控件必须全包含在Mediator类中
public class Mediator
{
CpyButton cpButton; ClrButton clrButton;
TextBox txKids;
ListBox leftList; ListBox klist;
public Mediator(CpyButton cp, ClrButton clr, ListBox pk) { }
public void setText(TextBox tx) { txKids = tx; }
public void clearClicked() { }
public void copyClicked() { }
}
//CpyButton
按钮的Excute方法是通过Mediator类实现的
//
也就是说将其接口和实现分离开,因此也可以说是个桥接模式。
//
作为一个桥接模式,是因为其将实现分离出;
//
而作为一中介者模式则是因为Mediator类的存在
//Mediator
管理所有的交互,而Bridger接口则提供不同的服务。
public class CpyButton : Button, ICommand
{
Mediator med;
public void setMediator(Mediator md) { med = md; }
public void Execute() { med.CopyClicked(); }
}
public class ClrButton : Button, ICommandExecutor
{
Mediator med;
public void setMediator(Mediator md) { med = md; }
public void Execute() { med.clearClicked(); }
}