透过sax解析浅谈设计模式

来源:互联网 发布:linux xargs cat命令 编辑:程序博客网 时间:2024/06/04 23:27

透过xml解析小记设计模式

前些时候有个小项目,中间需要用到xml的解析,这部分代码其实很简单,就是东西略多些,基本上没什么大问题。但是,我总觉得代码写的很丑,当时一直打算重构,却又无从下手,当时问过一个博士后大牛,他给的答案是:“这样写其实问题不大,但如果你觉得很不爽,非要改的话,可以考虑做一个抽象工厂。”

当时对设计模式的理解很肤浅,没有深入了解,工厂倒是知道,但是如何用来解决这个问题,确实有些麻烦,再加上其他一些事情,这个问题就被搁浅了。

当时使用SAX解析xml,主要就是重写了一个ContentHandle类,重写几个虚函数,就可以实现xml的解析,这还得感谢强大的Expat,好吧,至少我觉得挺强大,说不定有人觉得很小儿科的说……

现在简单描述一下当时的问题。

需要重构的开始结点函数的伪代码如下:

///////////////////////////////////////// Summary://     开始解析某个节点的处理函数// Paras://     name 节点名 (XXString为工程内定义的类似于CString的类)//     value 结点值(一个int/double/const char*/...的union)///////////////////////////////////////ErrorCode StartNode(..., XXString name, XXValue value){   ErrorCode retCode = SUCCESS; if(Compare(name, Node[0]) == 0)  DealNode0(value.value.nvalue); else if(Compare(name, Node[1]) == 0)  DealNode1(value.value.fvalue); ... else if(Compare(name, Node[n-1]) == 0)  DealNoden(value.value.xvalue); return retCode;}const Char* Node[] = { "Node1", "Node2", ..."Node3",};


 

对应于StartNode,还有一组,EndNod,如下:

ErrorCode EndNode(..., XXString name, XXValue value){ ErrorCode retCode = SUCCESS; if(Compare(name, Node[0]) == 0)  DealNode0(value.value.nvalue); else if(Compare(name, Node[1]) == 0)  DealNode1(value.value.fvalue); ... else if(Compare(name, Node[n-1]) == 0)  DealNoden(value.value.xvalue);  return retCode;}


 

虽然结构相同,但实际上,EndNode需要处理的结点较少,只有几个较高层次的结点需要处理。另外,为了减少字符串变量的开销,采用了常量数组Node。

其实,从熟悉sax的解析方式,到分析schema,到数据结构设计,到完成代码,总共消耗的时间也绝不超过三天。然而,在项目随后的进度中,xml中缺少一些我们必须的内容,所以我们不得不向xml做成的公司提交bug,要求增加某些结点,随之而来的,就是对xml解析的处理需要维护。

每次新增或更新一个结点,我都需要做以下的事情:
1. Node新增一个元素
2. StartNode中新增一个条件判断,部分结点还需要在EndNode中进行处理
3. 增加该结点的处理函数
4. 修改数据结构

以上四个是必须进行的操作,还有一些在结点出现时还无法确定的值,还要进行特殊的处理,当时用了些很“巧”的方法,就不拿出来恶心人了。这里主要讨论if-else的问题。

这个问题的核心其实就是如何消除switch语句,c++中,switch的case条件不像java中那样,可以是字符串,所以使用了很多if-else来完成相同的功能,但本质上来说,都是一样的。

我最初的想法是,利用循环来消除。
每新增一个Node,就必须有一个对应的处理函数,那么,我是否可以这样处理。建一个函数指针的数组,用于作为对应结点的处理函数。
然后利用循环来检查所有的结点名,匹配到一个,则退出循环,那么这个长达数百行的if-else函数就可以简化至十几行来解决问题。

void (*Function[])(XXValue value) ={ DealNode1; DealNode2; ... DealNoden;};


 

这里DealNodex只是为了说明,具体可以根据结点名来进行命名。

在StartNode中,内容变更如下:

ErrorCode StartNode(..., XXString name, XXValue value){   ErrorCode retCode = SUCCESS;   Assert(sizeof(Node) == sizeof(Function));   for(int i=0; i<sizeof(Node); i++)   {     if(Compare(name, Node[i]) == 0)        Function[i](value);  //这段代码没测过,不确定没问题,但参考我的其他程序对函数指针的调用来看原则上是行得通的   }   return retCode;}


 

这样,一个近百行的函数就可以压缩到十行以内,并且让代码更加干净,可读性上的提升也是显然的。

然而对函数指针的也是最近几个月自己的项目用到的,当时只能说知之甚浅,也曾想到过这种方法,但不敢下手,越拖越久,总想着是否有其他方法,导致只有空想,没有结果,再后来甚至连空想都不剩了。

后来自己做了点东西,发现函数指针这东西,其实也就那么回事,不过是个特殊的指针变量罢了,着实没什么难的。

今天在csdn上看到一篇名为《Switch语句,僵化的毒药》的博客,网址如下:
http://www.cnblogs.com/wayfarer/archive/2005/12/16/298339.html
作者使用抽象工厂,消除了switch语句,消除逻辑如下:

 

1. 重构前结构

 

2. 重构后结构


这也是我今天写这篇日志的原因,它再次提醒了我当初博士后大牛给的建议,使用抽象工厂的方式来达到消除switch语句的目的。而我的这段代码是否能够使用同样的方法来进行消除,利用动态将这个for一并解决了呢?我很期待这样的结果。

然而细看了他的这篇文章,他使用的这个例子里,使用抽象工厂,的确成功地消除了switch语句,然而,他的本质上却是将switch的工作上移到应用层,对于这个实例,也许它的上层可以使用更好的方法将string成功转换成为对应的pizzaFactary,然而对于我这个具体的工作来说,实现上却是很有难度的,因为我不可能因为我这段简单的代码,而要求修改SAX接口,这太不现实。如果我是项目经理,我也不可能接受这样的开销,虽然它的代码似乎更加合理。

然而,这却给了我们一个思路,在设计的时候,是否可以将状态模式和抽象工厂模式结合起来使用,在某些局部上,又是否能够达到更好的解耦效果,这却是值得深思的。

以上只是个人在设计编码时对代码的一点点理解,如果有什么不对,还请敬请各位朋友指出,如果有新的思路,也请大家不吝赐教,谢谢!~