怪怪 | Nothing, Everything

"有过一个发疯的时刻,有感觉的钢琴以为它是世界上仅有的一架钢琴,宇宙的全部和谐都发生在它身上." - 狄德罗
随笔 - 99, 文章 - 3, 评论 - 1919, 引用 - 42
数据加载中……

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

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

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

职责链使用情景一: 原帖

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

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

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

    public class BasicHttpBindingConstraint:BindingConstraint
    
{
        
public override bool Constraint(ServiceEndpoint endpoint)
        
{
            
if (endpoint.Binding.Name == "BasicHttpBinding")
            
{
                
//原操作
            }

           
return base.
Constraint(endpoint);
        }

    }

判断是否有下一个等等写入基类, 还是原来那些代码. 这样重构的好处除了消除了代码重复, 原文中两个protected的字段, 也不用再向实现类暴露, 做到了更好的信息隐藏. 但是真的没有重复了吗? 看看这句: if (endpoint.Binding.Name == "BasicHttpBinding"), 其中变化的只有引号内部的部分, 其余的还是重复. 同时, 每一个实现类, 虽然不用写else了但是base.Constraint(endpoint)却得Copy&Paste一遍.

接下来我们这么干一下基类:

    public abstract class BindingConstraint//:IEndpointConstraint
    {
        
#region protected fields
        
//只要是field, 最好连protected也不要.
        #endregion

        
private BindingConstraint m_bindingConstraint; //与原来相比, private做到了更好的信息隐藏
        private bool m_hasNextConstraint = false;        

        
#region public methods

        
public void AddConstraint(BindingConstraint constraint)
        {
            m_bindingConstraint 
= constraint;
            m_hasNextConstraint 
= true;
        }

        
public bool Constraint(ServiceEndpoint endpoint) //注意, 不再abstract了
        {
            
if (BindingConstraintName == endpoint.Binding.Name)
            {
                
return DoConstraint(endpoint);//注意, 这么优化是不对的, 见后面关于职责链的讨论
            }
            
else
            {
                
if (m_hasNextConstraint)
                {
                    
return m_bindingConstraint.Constraint(endpoint);
                }
                
else
                {
                    
return false;
                }
            }
        }               

        
#endregion        

        
#region public abstract methods
        
//新增了两个
        protected abstract bool DoConstraint(ServiceEndpoint endpoint); 
        
protected abstract String BindingConstraintName
        {
            
get;
        }

        
#endregion

    }

根据传说中的单一职责, 作为实现的子类, 无需再负责链表的相关工作, 只要管自己干嘛, 并提供自己负责事情的代号, 就可以了. 但是我要说明的是, GoF的代码例子, 是我和当前例子的作者的那个中间值: 第一次优化后那个结果; 为什么呢?

第一次优化后, base.Constraint(endpoint)这个, 还可以不执行, 这样我们就有了一个机会, 定义默认操作, 甚至将职责链原本的链条改变, 引导到另外一个职责链上去. 请大家注意这个事实: 使用base.Constraint(endpoint)的方法, 增加了重复, 但换到了灵活性; 也就是说, 只要是损失的, 必须换来其它的东西, 否则回家咱老婆也不干啊不是.

只是针对这个例子, 把去优化它两次没啥问题, 不过仍然可以看作吃饱了撑的没事干, 唯一的作用就是顺便就问这么一个事情:

信息隐藏完美, 没有重复, 使用优雅, 对扩展开放, 一切符合教科书上各种原则的代码. 就一定是正确的代码吗?

该文作者使用链表作设计, 无可厚非, 毕竟, 他达到了他想要的目的; 但是其实这种用法, 存在着一个的问题: 链表上每一个对象, 都要保存着上一个对象的引用, 从接到请求, 到有一个对象响应, 中间有多少个对象, 就有多少个废开销. 有开销没有问题, 问题是开销换来的是什么? 该文作者总结到:

引用(错误!见后)使用职责链模式必须恰到好处,否则会成为模式滥用的反面教材。根据我对职责链模式的理解,可以认定只要同时符合下列三个条件,就可以引入职责链模式:
1、当一个方法的传入参数将成为分支语句的判断条件时;
2、当每一个分支的职责相对独立,且逻辑较为复杂时;
3、当分支条件存在扩展的可能时。

我的看法是, 基本上打到靶子外去了. 那些可能产生的开销, 其实是白白牺牲了还被扣了个"废"的帽子; 勤奋工作的CPU, 被我一个初学者硬生生的钉上了毛主席"贪污和浪费, 是极大的犯罪"的历史的耻辱柱. 我们这次付出, 不像上面GoF的老大们选择的不优化, 反而像谈恋爱, 大把的白花花的银子花出去, 最后只等到一张好人卡~

比如用Strategy模式实现, 照样可以得到这些好处. 比如该文作者的例子:

1. 创建一个类作为执行者, 执行者中持有一个保寸字符串和IBindingConstraint的字典(比如Dictionary, HashTable);
2. 这个字典的初始化, 既可以用原来Add的方式, 也可以通过一个配置文件初始化;
3. 实现一个Execute, 根据endpoint.Binding.Name从字典取出IBindingConstraint, 并执行Constraint;
4. 使用时调用: 推荐者.Execute(endpoint).

很显然, 这样的做法, 不但有更小的开销, 也有更少的代码, 同时拥有更合理的结构, 只是看起来不那么炫. 在这里我要隆重的提醒大家注意使用字典, 这种方式虽然不入GoF 23种模式大雅之堂, 却是n多大牛/大嘴推荐的万灵丹. 对字典的使用也有一个广泛学名, 叫做"表模式"(还是表驱动来着? 记性不好...). 当然就针对当前的讨论所针对的问题来说, 我们也可以把它理解为策略模式的一个应用.

为什么说这种方式更合理呢? 让我们来看一下原文作者所说的三条, 其模型到底是什么样的:

信息1 -> 过程或对象一
信息2 -> 过程或对象二
....

SELECT 对象 FROM 某处 WHERE 信息 = @变量, 查表, 赤裸裸的查表! 职责链呢? 作为一个设计模式, 仅仅是针对查表然后执行算法, 那策略模式早就对这个抢饭碗的提反对意见了. 事实上职责链到底是干嘛地的呢? 搞清楚这个问题, 就可以判断真正的职责链在这里是不是合适, 或者原文作者使用链表的方式, 是否是职责链所覆盖的范畴了.

DP955.1 CHAIN OF RESPONSIBILITY
1. 意图
使多个对象都有机会处理请求......
2. 动机
考虑一个图形界面中上下文有关的帮助机制.....如果对于那一部分界面没有特定的帮助信息, 那么显示一个关于当前上下文的较一般的帮助信息, 比如说.....
因此很自然地, 应根据普遍性即从最特殊的到最普通的顺序来组织..
3. 适用性
  • 有多个对象可以处理一个请求, 哪个对象处理一个请求运行时刻自动确定.
  • ...
  • ...

找啊找啊找朋友, 找到一个女朋友~ 没错, 职责链说的就是这个事: 你认识一个特别出色的女人, 你跟她说, 做我马子吧, 如果她同意了, 自然好; 如果她说, 你就像我哥哥, 于是你说了, 既然我是你哥哥, 你得帮哥哥介绍一个吧? 她为了尽快摆脱你, 把一个比她差点的朋友介绍给了你, 然后你说, 做我马子吧... 如此下去, 直到有一个人接受你为止.

有一个问题我们要想到: 假设那个特别出色的女人是A, 她那天正好想到的朋友是B, 你就去给B花钱了; 如果想到的是C, 那你就是给C花钱; 同时, B如果不同意, 也许会给你C的电话, 也许会给你D或者E的电话. 而BCDE都有可能接受你. 也就是说, 谁把你接收了是不一定的, 但总有一个能接收你, 当然这里假设你是个女人就行, 换个说法, 你是无要求的, 响应者是不固定的. 将Gof的例子形象化, 想像所有的东西都是卡片, 叠在一起 如下:

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

从正上方看,  是一个窗口平面,  最顶层的那个区域是某种Widget1, 下面一种是 Widget2, 再下面一种是Widget3, 最底下是4, 记住, 可以是相同或不同的class, 继承自一个Handler. 当我们用小问号点击Widget1所在区域的时候, 如果Widget1含有帮助信息, 就显示Widget1的, 如果没有, 就显示Widget2的以此类推. 当然, 真实的例子不会这么摆, 只会比这个更复杂, 从最底层的对象开始, 往上基本是一个树状的样子. 我们可以认为上面的图, 是这棵树某一个分支, 正好就这么简单.

我们换一个放法: Widget1下面是Widget7, Widget3, Widget9(按顺序), 也就是说, 队伍根本不同了; 寻找的过程仍然相似, 只是最终显示的帮助信息有可能不一样.  为什么呢? 我们考虑一个控件: Label. Label本身一般没有帮助, 我们把它放入Widget2里, 它如果被点击了, 其效果和点击Widget2应该是一样的; 换成Widget7如果Widget7有帮助, 就应该显示Widget7的帮助. 但如果正好Widget7也没有帮助呢? 就显示Widget3的呗, 因为Label既然是Widget7的一部分, 显然也是Widget3的一部分; Widget7自己被点击, 显示的也是Widget3的帮助.

我们需要看到的是: Widget7和Widget3, 显示帮助的方式可能完全不同, 这原因并非因为Widget7对应了一个既定的传入信息而Widget3对应了另外一个, 而是因为Widget7的自己的情况(在GoF的例子里是Widget自身的帮助)和画图方式可能和Widget3不同; 即无论是不同的选择, 还是不同的行为, 不是对应于外部对象, 而是职责链上对象本身的特性所确定的. 这就决定了, 在运行时, 对于所有可能传入的任意信息全体, 我们通过职责链决定谁是接收者从而改变了处理它们中每一个方式, 实际上这就是使用职责链的一个需求. 对于GoF的例子, 没有传入任何信息; 对于本文的例子, 其实没有这样的需求.

接下来要说明的是, GoF的例子中, 一个对象的类别是否能够响应职责链, 也不是固定的, 这话看着和上面重复, 其实这是另一个不固定: 任何一个从基类继承下来的对象, 比如上面这些Widget, 如果它被设置了帮助, 当它被点击就会响应, 如果没有设定帮助(默认值), 就应该由它传递给更下层的一个. 这个情况下Widget7响应这个请求, 另一个情况也许就不响应了, 这只和是否给Widget7设置了帮助文本有关. 即: 判断是不是响应的条件, 并非传入对象持有的信息(事实上GoF的例子根本没有传入对象), 而是由职责链上的对象自己持有的信息来确定; 在GoF的说法里, 非常关键的一个词, 就是上下文相关, 这才是职责链的精髓所在, 而传递这一形式, 仅仅是具体做法罢了.

最后, 考虑这个窗口平面的例子, 我们用小问号点击的Widget, 不一定是处于链表头的对象, 我们的点击可以落在链表中任何一层上任何一个对象所对应的范围内, 所以链表中每一个对象都可能而且可以成为入口. 按照原文例子的用法, 所有的请求都只能从链表头也就是最上层进去, 这和DP95上所描述的意图和结构完全不符, 在GoF描述的职责链功能和用法中, 每一个具体子类都可能响应请求, 第一个接收者是谁是由输入动态决定的, 如果只从头部进去, 只不准哪个不该负责的对象就进行了不正确响应: 比如点击的Widget3, 请求却让Widget1给截获了.

另外既然说的这么细了, 顺便说一下, 没人处理的方式(这不是因为链条上的对象没有能力处理, 而是因为没人愿意处理)一般也不是返回false, 而是交给最终的一个DefaultHandler, 做默认操作; 这表面看跟直接返回没啥区别, 每次还必须将DefaultHandler接到职责链尾端, 但实质上保证了处理方式的统一.

不同的子类, 只是行为不同, 而不代表该子类从根本上只可以处理它对口的信息, 不能处理其它信息, 即不存在(信息n --> 行为n)这样的对应关系; 每一个行为都应该可以处理任意一种信息, 子类改变的就只是处理方式, 而到底哪一个响应用什么方式处理, 原则上说与传进来的信息是什么无关, 而是动态的根据输入决定入口, 接下来入口后面的候选者根据自身状态决定的. 正是因为我们不知道某一对象, 是否会响应这一操作及处理它附带的信息, 所以我们就无法应用表模式, 这样就需要职责链模式来解决.这才是23种而不是22种设计模式的原因.

比如这样的形式:

XxxBindingConstraint x = new XxxBindingConstraint();
x.SetParent(previousBindingConstraint);
x.AddNames(
{"Basic""WS""xxx".});


很显然, 如果这样修改原文的例子, 虽然每个对象都能响应请求了, 却不能正确执行: 但因为其设计目的缘故, 比如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了, 该模式是我自娱自乐的一个野路子, 在经过我自己严格检验前, 不打算发表(而且对于很多固执的人而言肯定不愿意付出某些代价去交换), 先卖个关子吧.

posted on 2008-02-18 08:03 怪怪 阅读(2333) 评论(31)  编辑 收藏

评论

#1楼    回复  引用  查看    

严重支持
2008-02-18 09:15 | rockshit      

#2楼    回复  引用  查看    

谢谢
2008-02-18 09:47 | Flymouse      

#3楼    回复  引用  查看    

哈哈 谢谢搂主
2008-02-18 10:16 | BlueMountain      

#4楼    回复  引用  查看    

UP ^_^
2008-02-18 10:18 | 钢钢      

#5楼    回复  引用  查看    

一直喜欢看楼主写的文章
2008-02-18 10:21 | carysun      

#6楼    回复  引用  查看    

字典,呵呵,很好的,我也喜欢用。
一个使用场景就是Visitor模式中的延迟绑定,也就是在运行期才判断对象的真实类型,然后调用合适的Visitor方法。这个在目前的java/c#里都做不到。
我遇到这个问题也就会开一个字典,key是Type类型,Value是Visit方法的delegate引用,然后……
2008-02-18 11:19 | Jeffrey Zhao      

#7楼 [楼主]   回复  引用  查看    

@Jeffrey Zhao
没错, 对于.NET, 如果是单纯的行为字典, delegate才是最合适的.

Visitor在你说的这种用法下还好一些, 否则改变一下类型体系, 别提多痛苦了...

不过你说"在目前的java/c#里都做不到", 是指什么? 我觉得反射一下似乎就可以了?
2008-02-18 11:41 | 怪怪      

#8楼    回复  引用  查看    

动态类型的同名方法调用?
2008-02-18 12:03 | A.Z      

#9楼    回复  引用  查看    

惨了没看懂。
2008-02-18 12:07 | 金色海洋(jyk)      

#10楼    回复  引用  查看    

开始直接看本文 确实不知道在说什么 可能和排版有关 后来看了原贴 才明白

确实 对原贴的处理方法不敢苟同 那样的一个处理 没必要扯到职责链
2008-02-18 12:16 | progame      

#11楼    回复  引用  查看    

又看了一遍,还是没懂。
2008-02-18 12:26 | 金色海洋(jyk)      

#12楼    回复  引用  查看    

@怪怪
对,反射也可以,我的做不到就是指自动的“在运行期判断对象类型进行方法选择”——语言特性或平台特性上不能直接支持。
2008-02-18 13:09 | Jeffrey Zhao      

#13楼    回复  引用    

没有看懂啊.
2008-02-18 15:22 | tmxkhd666 [未注册用户]

#14楼    回复  引用  查看    

大把的白花花的银子花出去, 最后只等到一张好人卡
真逗。
文章很不错。比较认同你的观点。
2008-02-18 16:01 | 暗香浮动      

#15楼    回复  引用  查看    

尽管怪怪的话是重了些,不过从我的理解来看,怪怪说的更有道理。本例设计时用策略模式要比职责链更加合理。而用表模式(是否是《代码大全》中的“表驱动法”,即字典的应用)可以简化策略的代码,是比较优雅的做法。
2008-02-18 21:56 | 伍迷      

#16楼    回复  引用  查看    

@暗香浮动
那句话貌似苏鹏的播客里面有过(http://www.supengcast.net/好像关于找女朋友的)……
还有个词“靠谱”不知道是不是也是里面借鉴的……
……

#17楼    回复  引用    

发错地方了,汗..
怪怪,我看了原文,我觉得你前面那两个对代码的优化做的很不错,我暂时没想到这样改了以后会有哪些不足之处,你想了多久想到的?:)
另外,我对原文的出揣测,似乎还包含了这样一层意思,我直接用Switch来表达
switch (endpoint.Binding.Name)

{

case "BasicHttpBinding":

Constraint1();
Constraint2();
Constraint3();

case "NetTcpBinding":
Constraint1();
Constraint3();


break;

case "NetPeerTcpBinding":
Constraint1();
Constraint2();


break;

//...Other bindings' constraint;

}
如果 原文 “当分支条件存在扩展的可能时”,指的是这类情况时,那么存在一种链表或集合来存储这些不同的约束集合应该也无可厚非了。
不过除非 每个分支基本上要对所有约束都走一遍,否则以我个人的习惯,我不会只建一条链表,因为速度在当前硬件下可能不会有太大的感觉,但是空耗太多终究不太美观。我可能更倾向于选择表驱动+链表结合/或是 在binging对象中储存相应的链表对象,不过表驱动+链表可能对象重用性更好一些,不知道是否有更好一些的方法?
另外,其实Flyweight人似乎有一点点表驱动的感觉吧:)
2008-02-19 01:44 | 114411 [未注册用户]

#18楼 [楼主]   回复  引用  查看    

@金色海洋(jyk)
@tmxkhd666
@progame
说实在的, 我自己看着都乱. 我觉得不是排版的问题, 而是没有整体规划思路和安排段落. 因为开始只想说说职责链模式咋回事, 什么样的情况是误用, 逐渐就越写越多, 因为我发现职责链属于那种看着结构简单, 但特征不容易描述清楚的模式.


GoF那一章说实话写的不是特别好, 模模糊糊的, 要点根本没交代清楚. 好多人可能第一次理解的时候把职责链想简单了, 觉得好懂, 不就是拉下手么.

其实我最早的时候和原文作者对职责链的理解也是一样的, 只是心里一直存着疑惑, 觉的是个鸡肋模式, 后来有一天自己写了些类似真正职责链的代码, 才突然想到我写的这个是职责链, 回头一翻DP, 才知道开始看的太草了.

嗯, 看来我这码字的水平还有待提高...
2008-02-19 04:03 | 怪怪      

#19楼 [楼主]   回复  引用  查看    

@volnet(可以叫我大V)
看了一眼你说的苏鹏, 很有才.., 我以前都不知道他, 我这些话可能都不知道借鉴的是第几手了, 反正肯定是网上看来得 ^^
2008-02-19 04:05 | 怪怪      

#20楼 [楼主]   回复  引用  查看    

@114411
要是优化的话, 其实我现在基本不用想的, 就看心情. 如果烦躁就会乱写, 如果不烦躁, 就会把所有看着象重复的地方提炼一遍. 但是其实乱写的代码, 我看一会儿也会烦, 最后还要重构.

只是有时候提炼出来反而结构复杂了, 这时候如果时间条件允许, 就会再次重构到简单合理的结构, 或者干脆重新写一个结构. 对我来说不太想模式, 原则这些了, 主要是怎么自己看的舒服.

要说后面的, 其实因为正好我也曾经理解出错过, 所以一下就看出来了. 不过写这些我花了个把小时, 主要是翻书确认论据和如何表达的更清楚一些. 我比较怕自己写错了然后误导了别人.

说到本例, 其实我开始也以为原文是这个意思, 后来仔细看了几遍, 感觉体会不到这样的意思. 因为实际上例子中的不同Constraint都分别在不同的子类上, 而每次响应的只有其中一个子类, 就成了我文中说明的对应关系.

更详细的分析我开了一个新帖:
http://www.cnblogs.com/guaiguai/archive/2008/02/19/1072942.html
2008-02-19 05:16 | 怪怪      

#21楼    回复  引用  查看    

@volnet(可以叫我大V)
靠谱是借鉴不靠普的。搜一下十大不靠谱。
2008-02-19 09:31 | 暗香浮动      

#22楼    回复  引用  查看    

@怪怪
我还在自己的博文上发表意见,没想到你在这里开了贴,树了一个靶子,让我有些找不到北的感觉。

仔细阅读了你的博文,我觉得意见非常中肯,语言也很有趣,真是受教了。我觉得这样的讨论很有意思,至少可以让我们从讨论中收获更多的知识,特别是能够纠正一些一直以来固执在心中的看法与结论。例如,你对职责链模式的分析,让我豁然开朗,发觉原来自己的认识未免过于狭隘了。例如,你提到在应用职责链模式时,应注意关注职责链对象的上下文关系,以及发送者和请求者的关系。此外,你提到的职责链对象在处理请求时,不一定要有传递的参数,这让我深感惭愧,原来自己的理解确有偏颇之处。

但对于我所述的场景,如果不考虑链表带来的性能缺失(虽然有影响,但应该损失不大)之外,采用职责链模式仍有可取之处。这里,还要说明一点,你对我例子中代码的重构,我是深表赞同的。

如果采用策略模式与字典的方式(或者用工厂),假定我们为每个绑定建立一个策略对象,抽象类型为BindingConstraintStrategy。至于工厂,为了简化起见,我们直接用简单工厂实现即可,工厂方法的参数仍然考虑使用ServiceEndpoint。

我想经过这样的实现后,那么在客户端代码中,应该是这样来调用:

//注意,此处不能通过工厂来创建m_constraint的具体对象,因为一旦创建完毕,就限制了m_constraint只能为其中某个绑定的约束检查对象。
private static BindingConstraintStrategy m_constraint;


public void AddServiceEndpoint(ServiceEndpoint endpoint)
{
//只能在这里面创建具体对象;或者是在表(字典)中查找得来;
m_constraint = Factory.Create(endpoint);

if (m_Constraint.Constraint(endpoint))
{
m_endpointsList.Add(endpoint);
}
}

注意这段代码与我的文章中客户端代码的区别。在AddServiceEndpoint()方法中出现了对象的创建(或查找),这种设计是否合理呢?如果不仅仅是该方法需要检查绑定的约束,那么就会在多个地方出现调用工厂的逻辑。这种实现方式与职责链的区别,是因为它无法智能的识别和判断,以找到准确的对象。

还有一点意见,我觉得有必要说明。诚然,我写了一本关于设计模式的书,我的理想自然是书中描述的内容与观点都是正确的,如此才不至于担当起误人子弟的罪名。不过,是否一个著了书的人(还谈不上立说),以后在发表文章的时候,一旦出现错误(或许仅仅是与人有分歧),就要被扣上吓人的帽子,以至于战战兢兢,不敢说出心中所想呢?

我并不怕成为靶子,更希望更多的人提出错误,而不要说因为害怕写书人的怨愤,就只能在心中腹诽一下。所以,我得感谢怪怪,及时地能够纠正我的错误。我想dudu也不会有任何意见。至于出版社更不要考虑,写自己写的,让别人去说吧。只要是正确的意见,有争执又何妨。

所以,不仅我的博文,即使是我的书中有什么谬误,大家都可以大胆的提。但我必须申明,即使我的书和文章或有什么错误,但我能确保我的每一个字都是经过深思熟虑之后的产品,而并不是如怪怪所说,不经思考就随意发表出来的。所幸,我的著作至今还没有发现太多根本性的错误,至少我还没有犯下误人子弟的罪行。
2008-02-19 17:13 | 张逸      

#23楼    回复  引用  查看    

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

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

首先说说前1段,关于是否需要为职责链的创建建立专门的工厂类的问题,我不认为不靠谱。首先,是否建立工厂本身与职责链模式没有必然的联系,我并不是说没有这个工厂类,就不是职责链模式了,所以GOF的模式关系图上没有,并不能代表我们不能为它加上这一辅助类。其二,我所说的建立工厂类,并没有要求在建立职责链时,必须按照固定的顺序,只要建立的链没有错误,什么顺序都可以。同样以Widget为例,只要链条不至于出现断裂,在工厂类为其建立职责链时,并没有要求绝对的顺序(当然大体上要按照从特殊到普遍的顺序),Widget1、2、3排列在前还是后,实际上,只要建立了职责链都是可以找到正确的对象的,因此不必考虑它们之间的排列组合。如果没有这个创建职责链的工厂类,难道将这一职责交给客户端去做吗?所以,我们在应用设计模式时,并不一定要严格按照GOF的模式来套用。

说道你的呼吁,我很赞同。确实如果作者都不确定的内容却盲目发表,结果会导致“谬种流传”。但问题是,如果作者自己确定,只是理解错误,或者本身正确而读者理解错误呢?又该如何处理呢?写博文的作者本身代表的就是“一家之言”,而非什么专家论断,如果读者应要认定博文必然正确,是否就是作者的责任呢?如果每写一篇文章,都要谨慎的说“我是这样想的, 大家看对不对? ”,那这个博客也就真的当得累了。宽容些吧。再说,以你的矛头来攻击你的盾牌,我看了本文通篇下的结论,好像也没有实现申明“我是这样想的, 大家看对不对? ”吧。为何,还不是觉得太累的缘故。

所以我认为,写博客一定要认真,要深思熟虑,但却不要过于谨慎,搞得如履薄冰一般,这就很难看到精彩文章的诞生了。
2008-02-19 17:36 | 张逸      

#24楼    回复  引用    

@张逸
这个, 好几个话题, 我还真不知道从哪儿开始.

"严格按照GOF的模式来套用"
其实我从来没这么想过 :), 我现在基本上已经脱离了模式的束缚, 有点吹牛哈 :) 只是职责链等几个模式, 实际上是看似简单, 实则前提条件和约束很多的模式, 我自己不知如何表达好, 没有当作者的天赋, 所以只好说说GoF是如何表达的, 我个人认为为什么GoF这样表达.

实际上, GoF几个人, 在一些问题上很全面, 比如这个图上为什么没有; 其实并不是他写了画了我们才要琢磨; 他们没画, 有时候是漏了(因为不可能说全), 有时候没画有没画的道理.

你可以看看我上面回复某兄弟里面那个链接, 对几个问题, 我又整理了一遍, 可能会比这里清楚一些.

"Widget1、2、3排列在前还是后,实际上,只要建立了职责链都是可以找到正确的对象的,因此不必考虑它们之间的排列组合。如果没有这个创建职责链的工厂类,难道将这一职责交给客户端去做吗"

老兄, 实话实说, 你对职责链的理解很存在问题. 让我们想想和GoF所说的例子相仿的Winform和WebForm吧, 当然是"职责交给客户端去做"了, 当你能够接受这一点, 一切就豁然开朗了: 想想看那张图里职责链唯一链接的模式是什么? 是Composite!

我们平时见到的Composite是谁? 就是 Control.Controls啊; 这些Controls如何构成的? 我们从IDE上拖拽, 实际上做了设置嵌套关系的工作, 最终是生成客户端代码的! 换句话说, 这个工作, 客户端是一定要做的, 这可不仅仅是为了灵活性付出的代价, 而是需求所致, 正是因为有了这个需求, 才需要用到职责链这个模式.

现在不是职责链所针对解决的问题, 没有这种需求; 你可以想想, 三个Panel嵌套, 各有各的帮助文本, 前后关系是决不能对调的. 除非一种情况, 建立多个链表, 每个Panel有自己的职责链, 上包括上中下, 中包括中下, 下只包括自己的响应. 否则, 当我点击中间的Panel时, 让上接活了怎么办?

说到这里, 我想你可能有另外一个误区, 你解决这个事情的办法, 很有可能是把坐标值传进链条, 然后所有的Widget类里有一个判断方法, if(this.Contains(mousePoint)) 这样, 那当然链条里的顺序就无所谓了, 但这就不是职责链了. 如果你有一百个类, 就得Copy这个判断一百次, 不说开销, 光是代码的重复呢?

如果这点还没有概念的话, 你可以就咱们平时用的控件树, 窗口, 结合GoF的讲解, 看看如何建立起想像; 另外, 其实我的文章, 你还没有真的看清楚, 这些问题我都是做了说明的, 不过这不赖你, 比清晰表达想法, 你比我强太多了; 所以即使我认为你在这个问题上可能理解还有欠缺, 作为一个技术作者, 也比我强的多.

说实话, 这个例子GoF讲解的并不好, 虽然该表达的已经表达了, 但对要点突出很不够, 把它的用途和前提想的过于简单是非常自然的事(就像我说的, 我也象你这么理解过).

在我上面的第二篇文章的链接里, 对位置和对象间关系的强调, 对于对象间顺序和结构的问题, 做了更好的说明, 你可以先看一下. 我个人不擅长画图, 你可以自己画画图, 这个问题用图表达特别合适.


关于我的矛和我的盾, 说实话我不知道应该怎么说. 我在过去说过, 如果是老Fowler发言, 一般人都会信; 如果是我说的, 大家肯定是你凭什么牛B哄哄, 大家都不信身边的一个普通网友会有多高水平. 我不希望给大家留下一个狂妄的印象. 我只能说, 就我这篇文章和下篇文章所讨论的问题, 我基本上扫到了大多数角落, 所以可能发言权会大一些.

但实际上我要说的是, 比如我拜读你的大作时, 我不会先去考虑你的水平, 你说的话我不会全信, 但我也不会不信. 我说不会因为你是Fowler就更多的服你, 但我也不会因为你是个普通的网友, 就不服你.

我只看内容, 如果内容错了, 我就会说出来; 你的社会属性越公众化, 我的反映就会越强烈; 其实我个人打心里认为, 我这么做, 受益的并不仅仅是看过我的文章澄清对某一种误解的. 而是也包括作者; 大家都需要成长, 不是么?

#25楼    回复  引用  查看    

@怪怪
“我只看内容, 如果内容错了, 我就会说出来; ”,我赞成你的这个观点。

“你的社会属性越公众化, 我的反映就会越强烈; ”我初看不赞成,仔细想想,还是赞成。之所以最初不赞成,是我并没有这个自觉,觉得自己就是什么专家,只觉得自己也不过是一个普通网友而已。现在看来,我因为写了一本书,就像在网游里面,多收获了一些名誉值一般,所以,我的社会责任就更大了。

嘿嘿,看来成为“名人”很难啊,呵呵:) 还好,我还不算太出名。

关于职责链模式,我看我还需要仔细分析和理解。现在看来,最近这一两年,自己未免有些浮躁了,呵呵:)
2008-02-19 19:13 | 张逸      

#26楼    回复  引用    

@张逸: 回第一个帖子
呵呵, 新开帖子是因为这个主题, 我认为有必要详细的讨论一下: 大多数人把职责链想的太过于简单, 以至于变成了普通的链表. 其实DP95中一些模式, 其针对性和行为特征是相当的强的, 说实话, 没写过真正符合它们所针对的需求的代码, 造成错觉十分容易. 你看, 我强调的不是水平, 不是去人为的练习这些模式, 只有两个字, 需求, 需求决定一切.

你说的策略的问题, 是这样: 你忽略了我文章中说的执行者这一角色. 我的想法是这么用的:

public void AddServiceEndpoint(ServiceEndpoint endpoint)
{

if (Singleton<ConstraintExecutor>.Instance.Execute(endpoint))
{
m_endpointsList.Add(endpoint);
}
}

其实我在文中已经说的很清楚了, 你太着急反驳了. 我们甚至可以不用Executor的形式. 因为你的endpoint是一定要传入的, 这样用也一样:

ObjectLocator<IConstraint>.GetObject(endpoint).Constraint(endpoint);

不过这样endpoint出现了两次. 如果再变变:

ObjectLocator<IConstraint>.GetObject(endpoint.binding.name).Constraint(endpoint);

看着更费劲了不是? 但是其实不能这么理解, 因为string是一个最普遍的类型, 谁也不会说, 对string造成了依赖. 这就已经类似于他们说的IoC/DP, 也是一样的.

这样的好处, 比我上面的那个Singleton有啥区别呢? Singleton本身就是一个形式, 上面的形式固定死了, 而下面的没有. 你只关心给你的对象能不能干这个事, 而不关心它是Singleton还是别的什么. 对象的生命周期和你不再有关系, 所以付出了多打代码, 降低了耦合, 提高了灵活性.

其实我并不是说你不能出书写文章, 写的时候稍微谨慎点就行了. 你看我这篇文章乱七八糟的, 让我写像样的文章和书, 我还写不出来呢. 至于理解, 其实理解到哪一地步, 就写到哪一步, 读者也有不同层次. 不过作为技术宣传者, 不得不谨小慎微啊..

说实话, 我觉得我又交到了一个朋友, 因为你很大气; 有这样的胸怀, 成为技术明星, 只要你不放弃写作, 就一定没问题; 至于让我这样的家伙叮俩下, 你就别放心上了; 就是Fowler, 只要让我抓住一个地方理解没我准确, 我都会疯言风语呢 :P

#27楼    回复  引用    

对了, 我MSN发给你了, 只是我不常上线, 有机会交流还有合作 :)

#28楼    回复  引用    

@张逸
还有, 不是你浮躁, 是社会和环境浮躁.

我现在基本就是"南阳诸葛庐, 西蜀子云亭"(不要脸, 太不要脸, 呵呵)的状态, 自然时间比大家多一些, 较真的事也干的多一些.

#29楼    回复  引用  查看    

本来对职责就 还没有看呢,昨天草草地看了一遍书籍(《大话设计模式》),还是比较晕,呵呵。
2008-02-19 19:43 | 金色海洋(jyk)      

#30楼    回复  引用  查看    

真可惜这几篇文章我只能看懂一部分
2008-02-20 10:06 | carysun      

#31楼    回复  引用  查看    

嗯,今天翻职责链模式来到这里,我比较同意怪怪的观点,果然眼光够毒辣。。。
2008-08-13 10:11 | 阿多斯      

标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-06-06 21:50 编辑过