上篇博客我们对状态模式进行了分析,状态模式消除了庞大的条件分支,它把对象状态的转移放到了子类中,减少了相互间的依赖,使得软件维护简单,更易于后期的扩展。这种职能似乎类似于我们今天要说的职责链模式,职责链模式侧重于类的行为,每个类都有自己的请求范围,在客户端执行请求时是按照链中制定的顺序进行验证,直到找到适合自己的请求。
从功能实现上它们两者有着相似之处,但是它们两者的结构却有着本质的不同。首先职责链模式(链子、链子是一种很活的东西,可以随意的进行组合)中对象的传递是不固定的在客户端使用时我们可以随意指定链的起始端和结束端,中间执行的过程我们都可以随意指定,这样使得请求更加灵活,但是状态模式的内部执行的顺序是固定的,它请求的方式已经固定。
下图为两种模式的粗略比较:
一、理论精解
职责链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
何为链?
从上图我们可以看出链有两个重要的性质,①链是一系列节点的集合;②链的各节点可灵活拆分重组。
使用场合
1.有多个对象处理请求,到底怎么处理在运行时确定。
2.希望在不明确指定接收者的情况下,向多个对象中的一个提交请求。
3.可处理一个请求的对象集合应该被动态指定。
UML图很简单,主要是继承和聚合关系,下面以“要打仗了”为例,实现链式请求。
在军队中,打仗是需要得到上级许可的,我们把现实的情况下战局的形势分为三级,分别以1-10个数字来代替,1-4属于一级战报,假设师长就可下命令;5-7属于二级战报,假设军长就可以下达命令;8-10属于三级战报,假设司令官可根据情况下达战争命令;大于10的需要向更高的层次汇报。具体代码如下:
Handler处理请示的抽象接口,没有具体请示的实现,它是师长、军长、司令官的抽象类,用来设置具体请求类的下一继任者。
using System; using System.Collections.Generic; using System.Linq; /// <summary> /// 处理请示的抽象接口,用来设置下一继任者 /// </summary> abstract class Handler { protected Handler successor; /// <summary> /// 抽象请求方法,在子类中覆盖实现 /// </summary> /// <param name="Request"></param> /// <returns></returns> public abstract Boolean HandleRequest(int Request); /// <summary> /// 设置继任者 /// </summary> /// <param name="successor">继任者</param> public void SetSuccessor(Handler successor){ this.successor = successor; } }//end Handler
ConcreteHandlerA,师长类,对形式进行判断,在一级形式内的情况发布战争命令,形式不能把控的向上级汇报。
using System; using System.Collections.Generic; using System.Linq; /// <summary> /// 师长职责类 /// </summary> class ConcreteHandlerA : Handler { /// <summary> /// 师长执行请求,在师长执行范围内的话将执行命令,超出权力的话向上请求 /// </summary> /// <param name="Request"></param> /// <returns></returns> public override Boolean HandleRequest(int Request) { if (Request <4 && Request >1) { Console.WriteLine("我是师长,打吧不怕他们:{0}", Request ); //在师长执行权利内的话,执行该内容 } else if (successor !=null ) { Console.WriteLine("我是师长,不能够执行请求,向上级汇报:{0}", Request); //没有在师长执行权力内的话,执行该内容 successor.HandleRequest(Request); //师长向上请求 } return true; } }//end ConcreteStateA
ConcreteHandlerB,军长类,再次对形式进行判断,如果形式级别在可控范围内,则执行战争命令,超出自己把控将向上级报告。
using System; using System.Collections.Generic; using System.Linq; /// <summary> /// 军长职责类 /// </summary> class ConcreteHandlerB : Handler { /// <summary> /// 军长执行命令,在军长执行范围内的话将执行命令,超出权力的话向上请求 /// </summary> /// <param name="Request"></param> /// <returns></returns> public override Boolean HandleRequest(int Request) { if (Request >4 && Request <7) { Console.WriteLine("我是军长,打吧不怕他们:{0}", Request); //请求在军长职责范围内,军长执行 } else if (successor !=null ) { Console.WriteLine("我是军长,不能够执行请求,向上级汇报:{0}", Request); //权力超出了军长范围,军长执行不了 successor.HandleRequest(Request); //继任者,军长继续向上请求 } return true; } }//end ConcreteStateB
ConcreteHandlerC,司令官类,对形式进行评判,决定是否大举进攻,如果无法评判,将向更高级别汇报。
using System; using System.Collections.Generic; using System.Linq; /// <summary> /// 司令官职责类 /// </summary> class ConcreteHandlerC : Handler { /// <summary> /// 司令官执行请求,在司令官执行范围内的话将执行命令,超出权力的话向上请求 /// </summary> /// <param name="Request">大于20且小于30的执行</param> /// <returns></returns> public override Boolean HandleRequest(int Request) { if (Request > 7 && Request < 10) { Console.WriteLine("我是司令官,打吧不怕他们:{0}", Request); //请求在司令官职责范围内,司令官执行 } else { Console.WriteLine("我是司令官,要打仗,需要军委主席发话!"); //权力超出了军长范围,军长执行不了 } return true; } }//end ConcreteStateC
Facade类,即客户端类,将程序的入口进行封装,在使用职责链时,调用Facade类传递级别参数即可。
using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; namespace System { static class Facade { /// <summary> /// 应用程序的主入口点。 /// </summary> [STAThread] static void Main(string[] args) { ConcreteHandlerA h1 = new ConcreteHandlerA(); //实例化职责A对象 ConcreteHandlerB h2 = new ConcreteHandlerB (); //创建职责B对象 ConcreteHandlerC h3 = new ConcreteHandlerC (); //创建职责C对象 h1.SetSuccessor(h2); //为职责A设置下一继任者B h2.SetSuccessor(h3); //为职责B设置下一继任者C int[] requests = { 2, 6, 9,12 }; //传递需要请求的对象 //循环执行请求 foreach (int request in requests) { h1.HandleRequest(request); } Console.Read(); } } } //程序运行结果 //我是师长,打吧不怕他们:2 //我是师长,不能够执行请求,向上级汇报:6 //我是军长,打吧不怕他们:6 //我是师长,不能够执行请求,向上级汇报:9 //我是军长,不能够执行请求,向上级汇报:9 //我是司令官,打吧不怕他们:9 //我是师长,不能够执行请求,向上级汇报:12 //我是军长,不能够执行请求,向上级汇报:12 //我是司令官,要打仗,需要军委主席发话!
经过了上面的职责请求,战争行为就变得很鲜明。这种程序执行的过程就是典型的链状模式(但是职责链模式并不只是简单的链状模式,它可以有多重分支),请求的发送者和接受者都没有对方明确的信息,且链中对象自己也不知道链的结构。结果是职责链简化了对象的相互连接,它们仅需保持一个指向后继者的引用,而不需保持它所有的候选接受者的引用。同时我们也可以随时的增加或修改处理一个请求的结构,是软件结构更易于扩展,提高了系统的灵活性。
二、应用实例
下面我们以收费系统的学生上下机为例,来说明下职责链模式的应用。
学生上机业务说明:学生需要在学校的机房上网,上网的时候需要刷卡上机。对于刷卡上机首先需要我们对信息进行验证,确定该学生卡号是否已被注册,其次要对上网的学生进行身份判断分为两种情况,临时学生和会员上网,临时学生的上网过程有他独立的职责过程,会员有一套上网的职责,对于会员来说在上机时的余额要充足,如果余额不足要提示进行充值。
如果我们不使用设计模式来实现学生上网的话,需要对学生的信息进行链式的判断,并进行选择的上机,这样写出来的代码可以想象有多么的复杂。复杂的逻辑判断,一长串的代码,可维护性、扩展性极差。如果因为系统升级需要对上机业务进行修改,那这时我们可真的要哭鼻子了。
那如果使用职责链模式呢?我们把上机过程分为三个具体的职责类,验证学生信息类、验证余额是否充足类、会员上网类和临时用户上网类。职责细分后对类的职责进行了分割,不仅符合了设计原则中的迪米特法则,同时也很好符合了单一职责。
学生上网UML结构图:
类的时序图
描述类之间的相互调用关系,在客户端首先实例化具体的职责类,然后设置链的上家和下家,最后执行链的开始请求即可。
登陆代码实现
LogStuBLL类,抽象职责类,定义了实现上机操作的抽象接口,并为职责设置下一继任者。
Imports Entity.ChargeSystem.Entity Imports Factory.ChargeSystem.Factory Imports IDAL.ChargeSystem.IDAL Namespace ChargeSystem.BLL ''' <summary> ''' 学生上机 ''' </summary> Public Class LogStuBLL ''' <summary> ''' 声明条件 ''' </summary> Protected successor As LogStuBLL ''' <summary> ''' 设置继承者 ''' </summary> ''' <param name="successor1"></param> Public Sub SetSuccessor(ByVal successor1 As LogStuBLL) successor = successor1 End Sub ''' <summary> ''' 被重写的具体实现类,学生上机请求 ''' </summary> ''' <param name="Cardentity"></param> Public Overridable Function HandleRequest(ByVal Cardentity As CardEntity) As StudentEntity Return Nothing End Function End Class ' LogStuBLL End Namespace ' BLL
TestCardInfoBLL类,检验学生信息类,输入上机的卡号后对卡号进行检验,验证卡号是否已经上机,如果已上机则将抛出异常供程序的高层模块捕获处理。
Imports Entity.ChargeSystem.Entity Imports Factory.ChargeSystem.Factory Imports IDAL.ChargeSystem.IDAL Namespace ChargeSystem.BLL ''' <summary> ''' 检验输入的卡号,如果不存在是否以临时用户的身份上机 ''' </summary> Public Class TestCardIinfoBLL Inherits ChargeSystem.BLL.LogStuBLL Dim DataAccess As DataAccess '定义抽象工厂,用来实例化返回具体的DAL类 '构造函数,实例化DataAccess Sub New() DataAccess = New DataAccess End Sub ''' <summary> ''' 被重写的具体实现类,判断卡号是否正在上机 ''' </summary> ''' <param name="Cardentity"></param> Public Overrides Function HandleRequest(ByVal Cardentity As CardEntity) As StudentEntity Dim OnlineDal As IOnlineIDAL = DataAccess.CreateOnline() '定义正在上机接口类,并为类赋值 Dim TmpStuDAL As ITempStuIDAL = DataAccess.CreateTmpStu() '定义临时上机信息表,并为其赋值 Dim enOnline As OnlineEntity = Nothing '定义正在上机信息实体类 Dim enLinelog As LinelogEntity = Nothing '定义上机记录实体类 '---------------------------------------------------------------------------------------------- '一般用户判断卡号是否正在上机 enOnline = OnlineDal.SelectByCID(Cardentity) '为卡实体类赋值 '判断该卡号是否正在上机,如果不存在的话抛出异常,判断是否以临时用户的身份上机 If Not enOnline Is Nothing Then Throw New Exception("该卡号正在上机!") End If '----------------------------------------------------------------------------------------------- '---------------------------------------------------------------------------------------------- '临时判断卡号是否正在上机 enLinelog = TmpStuDAL.SelectByCardPart(Cardentity) '为正在上机实体类赋值 '如果enLinelog不为空则正在上机,此时抛出异常 If Not enLinelog Is Nothing Then Throw New Exception("该卡号正在上机!") End If '--------------------------------------------------------------------------------------------- '跳出下一职责 If Not successor Is Nothing Then successor.HandleRequest(Cardentity) '请求下一职责 End If Return Nothing End Function End Class ' TestCardIinfoBLL End Namespace ' BLL
LeastBalanceBLL类,最少余额类,它的职责主要是对会员的卡号余额进行查询,并与系统限制的最少上网金额进行比较,处理余额不足的学生,确保学生余额足够上网。
Imports Entity.ChargeSystem.Entity Imports Factory.ChargeSystem.Factory Imports IDAL.ChargeSystem.IDAL Namespace ChargeSystem.BLL ''' <summary> ''' 最少余额,少于该余额时显示不能上机 ''' </summary> Public Class LeastBalanceBLL Inherits ChargeSystem.BLL.LogStuBLL Dim DataAccess As DataAccess '抽象工厂 '实例化抽象工厂 Sub New() DataAccess = New DataAccess End Sub ''' <summary> ''' 被重写的具体实现类 ''' </summary> ''' <param name="Cardentity"></param> Public Overrides Function HandleRequest(ByVal Cardentity As CardEntity) As StudentEntity Dim enSetCahrge As SetChargeEntity '定义收费信息设置实体类 Dim dalSetcharge As ISetChargeIDAL = DataAccess.CreateSetCharge() '实例化收费系统设置DAL类 Dim dalCard As ICardIDAL = DataAccess.CreateCard() '定义卡表DAL类 enSetCahrge = dalSetcharge.SelectNew() '获取最新的收费金额 Cardentity = dalCard.SelectByCID(Cardentity) '查询获取该卡号的卡号信息 '判断用户的余额是否充足 If Cardentity.dblBalance < enSetCahrge.dblLeastCash Then Throw New Exception("该学生余额不足" & enSetCahrge.dblLeastCash & "元,请充值后再进行上机!") End If '转移到下一位,跳入正常上机 If Not successor Is Nothing Then '转移到下一位,跳入正常上机 successor.HandleRequest(Cardentity) End If Return Nothing End Function End Class ' LeastBalanceBLL End Namespace ' BLL
TmpStuBLL类,临时用户上网类,在上网时可能会有临时用户和会员两种情况,该类的职责主要是对用户的身份进行评判,保证临时用户上机。
Imports Entity.ChargeSystem.Entity Imports Factory.ChargeSystem.Factory Imports IDAL.ChargeSystem.IDAL Namespace ChargeSystem.BLL ''' <summary> ''' 临时用户上机 ''' </summary> Public Class TmpStuBLL Inherits ChargeSystem.BLL.LogStuBLL Dim DataAccess As DataAccess '定义抽象工厂,用来实例化返回具体的DAL类 '构造函数,实例化DataAccess Sub New() DataAccess = New DataAccess End Sub ''' <summary> ''' 被重写的具体实现类 ''' </summary> ''' <param name="Cardentity"></param> Public Overrides Function HandleRequest(ByVal Cardentity As CardEntity) As StudentEntity Dim enCardEntity As CardEntity = Nothing '定义卡信息实体类 Dim dalCard As ICardIDAL = DataAccess.CreateCard '定义卡DAL类,并未其赋值 Dim dalTempstu As ITempStuIDAL = DataAccess.CreateTmpStu() '定义临时用户DAL类 enCardEntity = dalCard.SelectByCID(Cardentity) '为卡实体类赋值 '判断该卡号是否为已注册卡号,如果不是将按照临时用户上机信息为准 If enCardEntity Is Nothing Then dalTempstu.Insert(Cardentity) '执行上机操作,将信息写入T_TempStudent表中 Else successor.HandleRequest(Cardentity) '转移到下一处 End If Return Nothing '返回值 End Function End Class ' TmpStuBLL End Namespace ' BLL
NormalStuBLL类,会员上网类,该类的职责是实现会员上网的请求,当请求在该类的职责内时该类处理功能,保证系统会员正常上网。
Imports Entity.ChargeSystem.Entity Imports Factory.ChargeSystem.Factory Imports IDAL.ChargeSystem.IDAL Namespace ChargeSystem.BLL ''' <summary> ''' 正常学生用户上机 ''' </summary> Public Class NormalStuBLL Inherits ChargeSystem.BLL.LogStuBLL Dim DataAccess As DataAccess '定义抽象工厂,用来实例化返回具体的DAL类 '构造函数,实例化DataAccess Sub New() DataAccess = New DataAccess End Sub ''' <summary> ''' 被重写的具体实现类,学生上机,向Online表中写入上机信息 ''' </summary> ''' <param name="Cardentity"></param> Public Overrides Function HandleRequest(ByVal Cardentity As CardEntity) As StudentEntity Dim dalOnline As IOnlineIDAL = DataAccess.CreateOnline() '定义正在上机表 dalOnline.Insert(Cardentity) '插入学生上机 Return Nothing '返回值 End Function End Class ' NormalStuBLL End Namespace ' BLL
三、对比升华
职责链模式和装饰模式
装饰模式的UML图结构和职责链模式的大致相同,同样使用装饰模式实现的功能,职责链模式大致也能实现,只是在实现上哪个更有优势而已。装饰模式属于结构型设计模式,它更倾向于对象之间的布局和关系。它提供了一种动态添加类的功能,有效地把类的核心职责和装饰功能区分开,去除了相关类中重复的装饰逻辑。它很好的符合了开放-封闭原则,同时它也提供了一种比继承更好的扩展对象行为的方法,那就是组合,就增加功能来说,装饰模式相比继承生成子类来说更灵活。
它主要适用于:
1.需要扩展一个类的功能,或给一个类添加附加职责;
2.需要动态的给一个对象添加功能,这些功能可以再动态的撤销;
3.需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
4.当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
但职责链模式属于行为设计模式,侧重于行为,它的使用能够使对象的行为更加清晰,提高了对象之间的协作效率。
就上网功能来说,它更侧重于类和类之间的行为,使用职责链模式恰到好处,提高了执行学生上网复杂请求的效率,使的上网的行为更加清晰。装饰模式在此处就不适用,如果使用装饰模式在原类的基础上动态添加功能不但没有使处理过程简单化,反而还降低了学生上网的效率。