您的位置:首页 > 其它

模式使用详解 -- 手拉手就是职责链吗?

2008-02-18 08:03 330 查看
模式到今天已经20年历史了, 尤其是GoF模式, 更广泛被大家熟知, 这些模式就像武侠小说里的招式, 什么时候该使用什么招式,
师傅们只能泛泛而谈, 正确的判断必须由我自己的做出. 以后会介绍些自己的浅薄经验, 并和大家讨论, 只是不知道如何写起; 无论你是存在问题,
还是已经有了自己的答案, 都可以给我留言; 对于前者我不见得会马上回复, 但是对我如何去写有很大启发.

在这里先整理一些有疑问的使用方式, 作为未来的素材.

职责链使用情景一: 原帖

在表达我的想法之前, 我们先优化一下作者原来的代码中如下部分, 虽然这些优化中有一些对于真正的职责链模式是错误的, 而在这情况下使用链表是存在很大疑问的, 但是我们如果不拘泥于职责链称号, 也不考虑使用链表的合理性, 则这些优化可以当作一次重构的展示:

对象一:

if (变量 == "信息一") 某操作 else if (存在下一个对象) 下一对象.执行() else 默认操作

对象二:

if (变量 == "信息二") 某操作 else if (存在下一个对象) 下一对象.执行() else 默认操作

...

...

这是什么? 这就是重复, 这种重复如果混进代码里往往没有那么明显; 先把最明显的部分搞掉吧:

public class BasicHttpBindingConstraint:BindingConstraint

XxxBindingConstraint x = new XxxBindingConstraint();

x.SetParent(previousBindingConstraint);

很显然, 如果这样修改原文的例子, 虽然每个对象都能响应请求了, 却不能正确执行: 但因为其设计目的缘故, 比如Basic对应的实现只并不负责处理WS, 只能等着抛出异常, 根本不符合这个意义:

DP95第五章开头

Chain of Responsibility(5.1)....,根据运行时刻情况任一候选者都可以响应相应的请求.

关于从链条中间开始接收请求倒是不需要改, 只是这一行为根本无法做出: 因为这个设计从需求即根本上讲只能够从头部遍历到那个属于他的真命天女, 而并非由运行时输入所指定的那个开始找一个最合适的对象. 后者作为职责链的一个需求, 本身就最小化了传递的代价: 从接收者到头部之间的对象, 是不参与判断的.

所以我们可以判断, 这个例子只是徒具链表的形式, 却根本没有考虑链表更适合哪些情况, 在那些情况下如何发挥正面作用的; 无论这一设计实现的多么完美, 它相比其它选择, 更多的是担负了不该有的负面效果. 抛开是不是明白或误解了什么设计模式这样毫无重要性的事情, 这在我看来就像在过去经常提到的, 是缺乏对事物的正确分析的缘故. 这一前提, 才是做出良好设计的唯一前提, 哪怕你都不知道设计模式是啥, 甚至连面向对象几个字怎么写都不知道.

多说一句, 对于GoF的例子, 其实也存在着很多粗陋之处(过于简单就不算了); 比如是不是HasHelp, 由基类提供一个方法判断,
但是如果HasHelp()传回false, 由咱们实现的子类来转发. 这个模型产生了一个不一致性:
如果基类的HasHelp()认为没有Help, 但其实你根本没有用基类这个逻辑, 按照子类的逻辑是有Help的所以你就直接响应了呢?
于是为了消除这个不一致性, 我们实现子类时就不得不给基类提供信息, 这就可能会增加不必要的繁文缛节. 只是这些不完美,
可以理解为示例的不严谨, 总体来说是没有任何大的漏洞的.

再回顾一下原帖作者的例子为什么不符合对职责链产生的需求与职责链自身特征: 每一个传入的对象, 有一个明确的接收者; 链上除这个明确接收者之外的其它对象, 绝不会处理请求; 而这个明确的接收者的类别,
是否处理某请求这一要素是固定的. 相反该模式要求链上的所有接收者们在运行时刻根据具体情况判断是否响应(而不是单纯的接收和传递)这个请求, 从而改变响应请求(无论带有什么信息)所执行的行为. 这样的用法, 半点职责链的独有好处也得不到, 只能说形似职责链, 实则表模式, 而且由于是链表, 就变成了负担职责链开销的策略模式.

怪怪设计模式第一反律: 可以找出对应关系的, 不要使用链表, 如果硬以传递的方式成链, 也不能叫做职责链, 只能叫做对象链表.
职责链是一根链上每一环节皆可响应, 但不一定响应; 不是只有一个固定响应, 其它负责传递; 归根结底, 是整条链有机的负责一个响应一个请求,
而不是一个链表中某一对象一个人在战斗, 职责是链的职责而不是某一对象的职责. 否则这条链除了因为传统链表的传递而增加了负担又有何意义?

什么时候该用职责链, 请自己思考, 但我们完全可以做出什么时候不要使用职责链模式的判断, 即和原文作者正好相反:

1、当一个方法的传入参数将成为分支语句的判断条件时;

2、当每一个分支的职责相对独立,且逻辑较为复杂时;

3、当分支条件存在扩展的可能时。

总而言之, 只要你看见"分支"二字, 请不要使用职责链模式. 职责链上的所有相同类别不同类别的对象, 都具有相似的职责, 而不是A类别的对象负责处理A信息, B类别的对象负责处理B信息. 作为一个行为模式, 该模式处理的是对象间, 而不是类别间的职责分配. 这两者的区别是, 前者是根据对象状态而确定是否中彩, 而每一个对象从原则上来说都会花钱, 无论彩票兑换的是美元英镑还是人民币.

我们深入一下这个例子的实质的话, 就会发现, 因为谁对谁响应这个对应关系, 是在运行之前就确定的, 所以最适合的方式是使用表驱动. 由此我们可以得出:

怪怪设计模式第一推广: 可以找出对应关系的, 优先考虑表驱动; 看见"分支"二字, 优先考虑策略模式.

最后我们再看一下原作者的最佳实践:

引用(错误! 见后)1、应尽量将职责链模式的抽象定义为抽象类,而不要定义为接口。这样有利于一些公共逻辑的重用。

2、应在实现职责链模式的同时,提供创建职责链的工厂类。

关于1, GoF当初是抽象类, 但是其实接口并不是不可以. 如果你想细究这个问题, 可以看上面关于GoF例子瑕疵的讨论, 来思考父类集中操作有多大意义. 但记住请先翻DP95, 了解清楚那个例子. 有没有抽象类, 关键还在于抽象类合不合适:

怪怪第一哲学定律: 一切决定都是交换.

如果你公共操作很多, 那你就付出抽象类站位的代价(提示:DP95的代码是C++, 可以多继承); 如果你的其它设计需要基类, 就要付出打字的代价. 这两者可以兼得吗? 答案见文章末尾注释一.

关于2, 完全不靠谱. DP95上说的非常清楚, 职责链要解决的一个问题就是, 链条上单元的动态改变; 同时在DP95的模式关系图上, 职责链和工厂完全没有联系. 拿上面的Widget的例子来说,
难道我们应该为每种不同排列顺序, 都搞个工厂方法? 不同类别的Widget们只要能互相嵌套, 其排列组合有多少是可以预期的. 另外, 真正的职责链, 接收请求的对象可以是链条中的任何一个具体子类, 而用了Factory, 我们只能从Factory返回的头部传入信息, 职责链原本具有的行为就不正常了, 讨论见上面Widget那部分.

在文章末尾, 我想严肃的说两句多余的话: 我一直在呼吁的是, 如果作者们不确定自己的理解, 请考虑对博文读者的影响有多大可能性是坏的; 写东西时, 也不要太过自信, 如金科玉律; 不如以提问的方法说, 比如: "我是这样想的, 大家看对不对? "; 更不要着急在得到验证前, 随便将自己的文章放传播到更广泛的地方上去, 把炸弹变成原子弹.

不过我仍然觉得, 今天这块板砖有点太大了, 因为我拿一本设计模式书籍的作者当作了例子.
我想这篇文章是dudu都不愿看到的(小人之心度君子之腹), 毕竟该作者的大作属于博客园系列. 我其实非常犹豫发不发该文, 因为说实在的,
别人对设计模式会不会误解, 和我一样的初学者会不会受误导, 跟我有屁关系? 我又不是以技术宣传为生存手段的, 况且如果我打算写书,
恐怕这篇文章连出版商都不愿意看到; 恐怕他们更愿意的是别人自己写自己的, 别涉及他们的作者的文章; 哪个出版商会对没事得罪人的人感兴趣? 我之所以意识到这些问题, 却没有去换个例子, 也有考虑: 我一直提倡的是, 无论谁写的是黑纸白字几十块一本, 还是随便讨论瞎说几句, 大家最好都自己思考后再说; 从这个意义上讲, 原文作者的情况作为例子倒是最合适的.

且不说招一大片人反感的问题, 作为一个软件从业人员和潜在竞争对手, 对我来说, 别人走越多弯路, 是不是对我越有益呢? 我偶尔会思考这个问题, 也希望得到大家的答案.

----------------------------------------

注释一: 我的答案是可以; 这需要额外的学习和外部条件, 和引入一个新模式, Gasket, 即垫片模式;
这个模式也要交换从而付出很大的代价:该模式有扩散的特性, 导致你写程序方方面面有彻底的变化, 甚至是没有接触过的方式;
所以也要下功夫学很多新东西.

P.S. 不用Baidu和Google了, 该模式是我自娱自乐的一个野路子, 在经过我自己严格检验前, 不打算发表(而且对于很多固执的人而言肯定不愿意付出某些代价去交换), 先卖个关子吧.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐