首页 > 杂感 > 侯捷的Design Patterns培训杂感

侯捷的Design Patterns培训杂感

应该是公司花了不少钱请来的,虽然据他说他和趋势一直保持着良好的关系。

今天第一天,后面还有两天,题目关于Design Patterns。第一天总体感觉不错,jjhou确实是一个文字的高手,也可能是看得多了,翻得多了,写得多了,讲得多了也就自然成了高手。旧的东西从新的人口中讲出来,往往可以有更多的认识。

今天主要讲的是一些比较简单的OO概念和几个简单的模式,讲模式从Template Method开始,引出重用的基石:延迟实现;然后从其不足引出Strategy,然后解释OO当中的delegation。这个门入的非常cool。我们也在做Design Patterns的讨论班,从Factory模式开始,结果发现非常难于入门——不太熟悉的人都在问:为什么我们要用Factory来创建对象?今天发现,结构型模式,尤其是Template Method确实是一个不错的切入点。而且jjhou也说,Factory一定程度上也是由于Template Method产生的——当算法骨架当中需要创建object的时候。

然后是Adapter。本来我一直认为Adapter非常的简单,因而从来没有深入的考虑过Adapter更深层的东西。今天jjhou的例子却让我打开了一个新看待Adapter的视角:STL的functor adapter(事后查了一下《STL源码剖析》,发现其中有讲)。其实interface不一定是纯虚基类,std::unary_function也是一种interface,考虑template meta programming,当中的typedef也是一种interface!cool。原来adapter真的无处不在。

吃过晚饭一直在讲Reference Counting,传说中的来自于More Effective C++。这本书在大二的时候浏览过一番,后来就再也没有重读(那个时候还不知道什么是Design Patterns)。现在回头再去读一下,发现确实有一些不同的收获。一个Reference Counting的东西讲了这么久是因为例子。例子应该还是来自于他的《STL源码剖析》,源于SGI STL(古老的用于写书的那个版本)当中的string实现,中间穿插有copy on write。整个讲述的过程当中思路非常好,如果思路跟的上(其实以他讲的速度基本上都可以跟得上),听起来的确有趣,和他的书一个风格,从故事开头娓娓道来,深入浅出。不过我一直在考虑这个东西用在string上面的意义。Reference Counting用在string上面的最大问题在于非透明性——尤其对于遗留下来的代码的复用:试想一个用C实现的程序需要一个char*指针,如果这个时候传入了一个用Reference Counting实现的string.c_str()得到的东西,那么十有八九会出现问题。遗留下来的C程序并不知道这个东西是不是被share了,或者他的Reference Counting是多少。有人说“你不用c代码不就行了”,没错,非常好,但是在大型项目当中几乎做不到:大型项目有很多遗留代码和无法控制的第三方库,你可以在重写和复用之间权衡,但是一旦选择了复用,想要不出问题,概率大概和南京一整天不出现车祸的几率差不多(大概<0.1%)。所以VC8的std::string已经彻底抛弃掉了Reference Counting,大概也就是因为Windows平台上如此多的遗留程序和软件造成的。

但是string的Reference Counting的问题不足以掩盖Reference Counting的价值。但是一个好的Reference Counting的代码太难写了,尤其是要保证线程安全的时候(比如CComObjectRoot和CComMultiThreadedModel)。感觉还是非侵入式的Reference Counting比较安全一点,例如shared_ptr,这个应该明天会讲。

仔细想想为什么Reference Counting + Copy on Write这么难搞定,直接原因是因为现实和理想之间的差距:因为c风格的字符串处理函数无处不在——虽然guru们建议我们不要用它们,但是那之前的历史更长。而c风格的字符串处理函数有一个假设,即字符串的二进制布局:以单字节零结尾的连续内存空间。但是string没有这个假设。至于这种对于二进制布局的放弃,确实增加了string的灵活性,但同时也增加了互操作性的难度。我感觉在现在的环境当中来看,在C++程序员对于“用const引用替换传值调用”已成为一种思维定势,并且编译器有了更强的优化能力的情况下,在string上面使用Reference Counting真的是弊大于利。Reference Counting解决的是减少复制string对象的内存分配开销,而一旦使用了Reference Counting,为了保持string的行为,修改两个共享存储的string对象当中的任何一个都必须使用Copy-on-Write。但是,当C++程序员复制一个string对象的时候,他的目的在绝大多数时候都是为了修改它(我相信对于大多数有经验的C++程序员来说,这个命题基本成立),如果这个假设成立的话,Reference Counting的作用其实并不太大。不过仍然存在着其他的可能性,例如作为类成员的string object。不过我认为这种东西不应该会是性能的瓶颈:如果人们需要频繁的拷贝一个具有string成员变量的类实例,这种设计往往就有问题,有经验的C++程序员应该会避免的。剩下的能够从string的Reference Counting当中得利的,也就是很少的一部分case了。这种时候去寻找一个用Reference Counting实现的专用版本的string,看起来似乎更加合理。

在我看来,C++从一开始就放弃对二进制和线程模型的标准化也许是一个错误(虽然这样确实让C++可以在更多的平台上发挥作用)。因为没有二进制和线程模型,C++才不得不在编译期做文章,于是才有了模板元编程和很多的编译期优化手段。但是对于强调运行期的可配制性的产品,编译期的手段很难有用武之地。这种环境当中,更加动态的,可以在运行期变化的方案更受欢迎。这么来看,介于编译型语言和脚本语言之间的方案会很有前途,比如.NET的IL+JIT。

标签:
  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.