Software分类的最新日记
从我最熟悉的设计模式写起。英文是chain of responsbility,常常被译为责任链模式。
在1980年代,报纸上常见的一个批评政府的词汇叫"踢皮球"。当下看来,从社会主义社会转型到资本主义社会,这当中有诸多的未定义案例出现,导致了政府部门互相推诿的现象。在软件设计中,有时候也会有类似的案例,就是不知道一个事件/数据该由哪个对象来处理,或者是需要多个对象来依次处理,就出现了责任链模式。
责任链与踢皮球的不同之出在于,责任链不可以把球回传。责任链应该是一个单向可走通的链,允许中间退出,而不允许往回走导致死循环。
前面说过,设计模式的思想不限于面向对象设计。。。那么先举一个PC机上PCI总线的中断共享的例子。我还记忆犹新,当年上大学的时候,用着导师的一款386机器,很不错的,装了ISA网卡,在DOS下上网。后来想在那台机器上玩大富翁3, 找了个ISA声卡,费了九牛二虎之力才装上啊,记得是把网卡声卡的中断全改了,还改了DOS的config文件。。。
为什么装个声卡会这么痛苦?原因么,当然是因为ISA总线是不支持PnP的,共享内存地址和IO口地址都要手工指定,中断也不可以冲突。。。其它不深入了,总之后来的PCI总线就支持中断共享了。按照一般人的想法,一个PCI系统,当中断发生时,系统应该可以通过中断号、中断发生的PCI槽号来找到对应的驱动程序(别忘了多块PCI卡可以共享一个中断号),调用中断回调函数。--可是,令人发指的是,由于历史原因,主要是中断控制器的设计的一些兼容性原因(PCI和ISA是要共存的),当中断发生时系统没有办法知道是哪个槽中的PCI卡发出了中断,系统只能得到一个中断号。。。
那么我们来看一下WindowsNT下的驱动程序是怎么处理的(Linux下其实也类似)。首先,计算机开机。上电的时候系统能够识别那个插槽插了什么卡,得到它的厂商ID和板卡型号ID,并写入注册表。系统也会得到它所期望的资源类型和大小,并分配一块物理资源。Windows会根据厂商ID和板卡ID, 去寻找已经在注册表里注册的驱动程序,并使用驱动里的AddDevice功能增加一个实例。这个AddDevice中间包括了往PCI卡的配置空间写资源和向Windows注册资源的,包括注册中断号和中断回调函数的过程。这一点Windows做得很绕,先是分一块资源,把这些资源告诉PCI驱动,然后驱动再自己去注册这些资源。到了这一步,Windows内存里就有了以下模块(假设有三个板卡使用中断号17, 顺便说,很多主板都不完整支持PCI规范,也不支持中断号16~31),结构图是这样的
当中断发生时,通信图是这样的。这里画了一个最糟糕情况,当a收到中断请求包(IRP)时,它去查自己的板卡的寄存器或者共享空间,判断是否自己发出了中断,如果是自己的,立即擦除板卡上的标志,并且改变IRP的状态。题外话,因为中断是不可重入的,在这里做太多事情会影响多线程系统的可协作性,那些响应中断的活应该调用驱动里的DPC(推迟过程调用)来实现。中断服务程序应该在改变IRP状态字后立即返回。Windows核心会判断IRP的状态字,决定是否需要把这个IRP传递给下一个中断服务函数。所以责任链并不一定要调到到链上的所有元素,它是很有可能中间就完成任务。
如果觉得上面的例子太过晦涩,请看第二个例子。最近我需要做一些文件的文字处理。我有一个C++写的处理程序,叫Process。可耻的是,Process的功能不够强,有一些特殊的情况处理不了,所以我用一个perl脚本pre-processor.pl去预处理一下,把输入文件规整成可以被Proess的格式。再可耻的是,Process的处理功能还是不够强,出来的文件某些地方不符合我的需求,于是我再写了一个脚本叫post-processor.pl的来后处理一下。最后,我不愿意手工做这些事情:去调用、判断中间结果是否生成,另外,我还需要输入一个目录下的所有文件逐个处理,所以我写了一个shell脚本来做这个事情。
怎么样,我把文件画成了圆圈,很生动的踢球过程吧。上图只画了最佳结果,没考虑处理失败的情况,程序需要处理的。
最后,作为面向对象的责任链模式,有什么特征呢?很容易想到,所有的责任处理模块都有相同的输入和输出,所以这些模块作为类,应该有共同的基类。这是四人帮的书里举的例子,里面有一个Handler基类和两个派生类
这个例子,让责任处理类互相知道彼此的存在,一个责任处理类处理过输入之后,再传递给successor里所存的下一个责任处理类实例。这样做的一个坏处是,责任处理类必须知道谁是它的下一个负责者(successor),而且也很难在运行期改变这个链的结构。
我个人更喜欢有一个额外的中介类来存储责任链链表,这个类里面有一个链表存储所有的handler, 有一个接口接受client输入,有方法响应外部从链表里增加或删除Handler。这样具有更多的灵活性,比如可以方便地修改责任链;甚至是取消一个已经进入责任链的请求。更复杂一点的来说,当handler的处理速度有可能瞬时慢于client的请求速度时,可能还需要实现一个buffer来存放请求(下图没有画出这种情况,而是假设client自己处理了这种情况)。
有没有觉得上图我的做法(不考虑责任链的功能)也许可以归纳出另外一种模式?没错,看四人帮对Mediator( 中介者)设计模式的定义:"用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。"--所以上图是中介者模式和责任链模式的一个混合。
总结:(1)当有一个请求需要处理,但发送方不知道应该由谁处理的情况,可以使用责任链模式。责任链降低了请求发送类和多个处理类之间的耦合度。
(2) 当你有很多类来协同处理一个事情时,考虑用一个中介者来掩盖各对象之间的耦合。
翻箱底,几篇很久以前没写完的笔记。。。
看过金庸的武侠小说《笑傲江湖》么?里面有个华山派,华山派有剑宗和气宗之争。剑宗讲究的是招式,而气宗推崇内功。软件的世界,也是一个江湖,行走江湖就是不断出手的过程。很有趣的是,武术的招式和内功在软件的位面里有着投影。。。
什么是软件的内功?对编程语言的理解、对算法/业务流程的理解可以被看作内功。例如说,我常被新工程师问到的一个C的问题是:请教把"2046"这个字符串数组转换成整数应该怎么办?--好吧我的答案就是一句话,"函数atoi(),自己去查MSDN"。知道不知道一个功能函数,无关个人能力,只是他有没有遇到过的问题,更多的是经验的积累加上一点本能,很符合内功的一些特点:需要通过修炼(做项目)获得,通常年龄越大内功越高。近年来开始有人试图在软件设计方面归纳出一些内功心法,就是所谓的DDD(Domain Driven Design),这里面的domain指的是业务领域,对它的建模叫领域模型。这个话题以后再谈。
再说招式。早先学计算机的人都知道一个等式,"算法+数据结构=程序"。这个等式在早期程序大多开发用来坐科学计算的时候是适用的,但是,一个软件,除了算法和数据结构,就没有其它东西了么?那个加号是什么?其实是值得琢磨的东西。而且这一点在面向对象的时代显得更突出,就是对象之间的关系用什么来描述?即使在非对象的编程里面,一样也有模块的概念,最小粒度的软件模块之间关系同样是值得关注的话题。
1995年,Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides这四个人(常被称为四人帮,在英文里就是GoF)写了一本著名的书《Design Patterns: Elements of Reusable Software》,直接了当地说,软件对象之间的关系可以用建筑学的名词design pattern(设计模式)来概括,而且列出了23种常见设计模式。这几个人厉害就厉害在,后来其它人想了又想,却不太能想出更多的设计模式,就是说天下武功都不出这23招,洞察力果然了得。
值得指出的是,说到四人帮的模式的时候,一定要指名带姓说"设计模式",光说模式是不精确的,因为设计模式其实是软件设计里最低的一层,再低就低到软件实现里去了。在设计模式之上,还有所谓的"软件模式",例如著名的MVC模型,同样不是在这里讨论的话题。
往往会有这样的情况,一个工程师可能用过某个设计模式,而其实他并没有看过四人帮的书。这很好理解,设计模式本来就是软件实践的概括。但一个工程师23个设计模式都用过的,倒也不多,从设计模式的书里面往往可以得到一些启发。这就是学习设计模式的意义了:总结历史,学习经验。
下面会随机挑一些设计模式来做分析。有两点要说明:
1) 我认为设计模式的实践不限于面向对象设计(OOD),所以有可能举一些非面向对象的例子。
2) 为了说明类或者软件模块之间的关系,使用了UML标识图。UML是一种用来标识对象和对象间关系的图释法,相当直观,看懂它不比看懂通用地图的图释难多少。用UML结合Rational公司的软件Rose来做设计并自动生成代码当然要仔细了解使用细节,但只是为了看懂UML图而去看那些砖头一样厚的书是浪费时间。UML仅仅是工具而已。
在这里列出四人帮归纳的23种模式,他们把它们按目的分成了三个组,按作用对象分了两个组,结果就是这样的一个矩阵。其中Adapter模式即可以作用于类,也可以作用于对象,所以矩阵里面有24个项目。
| 创建型 | 结构型 | 行为型 | |
| 类 | Factory Method | Adapter | Interpreter Template Method |
| 对象 | Abstract Factory Builder Prototype Singleton |
Adapter Bridge Composite Decorator Facade Flyweight Proxy |
Chain of Responsibility Command Iterator Mediator Memento Observer State Strategy Visitor |
在继续深入之前,强调一下,设计模式是软件设计中的重要一环,但是领域模型也很重要,很多时候都要比设计模式更难做好,相比之下设计模式要简单一点。。。个人观点。这么说我是华山气宗的观点了,嘿嘿,要和君子剑岳不群岳先生划清界限啊。。。
未完待续。





