摘要: 集合模式基于集合,用于在不改变现有结构的情况下,由访问单一对象升级为可访问一组对象,特别适用于代码重构过程中。它一般与观察者模式结合使用。主要应用于重构过程中将混乱的代码或者用别的不适用的模式写的代码升级为观察者模式之后,解决大量繁索的调用细节,将关联关系由一对一升级为一对多。对于类似事件处理的流程最为适用。 考虑一个简单的日志处理系统。所有的业务逻辑类Logic都持有一个日志类Logger,Logger统一控制日志信息的去向,这样的系统已经非常灵活了,并且可以很容易地扩展Logger类来将日志存放到不同的介质中,甚至显示到不同的屏幕上。然而新的需求出现了,系统中产生的所有日志要同时出现在多.阅读全文
posted @ 2011-06-28 19:53 黄鹏 阅读(1467) 评论(10) 编辑

     应用程序中一般都有各种可配置项,它们往往通过配置文件的形式存在于程序可执行目录、操作系统目录或者注册表中。开发过程中,由于需求的不断变化,配置文件的格式也跟着不断变化,与之对应的最难受的问题就是没有什么成就感的配置文件读写操作的变化。这对于需求稍微明确点的项目来说问题不大,至少很少会从根本上对配置文件的格式进行修改。然而,对于一个研究型的项目,大多数程序“从无到有”的过程来说,这种变化往往很频繁。如何合理地组织与配置文件(或者叫序列化和反序列化)相关的程序,以使程序比较灵活,修改起来比较容易,是一个很值得好好考虑的问题。

    配置文件一般分为两种,一种是与用户相关直接相关的配置,可以称为软配置,这样的文件在程序运行过程中可以随时接受用户的修改;而另一种是与外部环境相关的配置,一旦环境确定,配置文件基本就不变了,否则会导致程序运行不正确。很多软件都将这两种类型的配置合二为一。但对于追求用户友好性的程序,把它们区分出来单独对待还是有必要的。简单地说,程序运行的各种参数,如使用何种协议、端口号等可以作为一种与环境有关的配置,在程序运行过程中,这些配置不能改变,即使改变了也不应该去处理,除非用户重启。而对于界面风格等可配置项则可以作为用户可以随时修改并及时反映到应用程序的文件。

    软件做的越灵活,对用户越友好,其对应的可配置项就越多,在软件开发过程其变化频率就越高。并且配置文件的作用域往往具有全局性,很难将“由于配置项变化而引起的代码变化”局限到一个局部范围。用面向对象的说法就是很难“将这些变化封装到一起”,做到“单一职责原则”。其原因在于配置文件不同配置项之间可能根本就没有语义相关性,两个没有相关性的元素在代码中往往也是两个没有任何关系的类。

    有没有现成的什么方案,可以有效地解决这个问题,把“大量没有成就感的人力劳动”给解放出来,一直困扰着我。曾经尝试过统一对配置文件进行序列化和反序列化,将配置文件封装到一起传给各个需要配置的模块,然而配置文件的变化导致配置类的变化,配置类的变化导致所有用到该类的所有模块发生变化,最终以“听到要改配置项就想自杀”而宣告失败。也尝试过分散配置文件的使用,带来的问题更明显,修改一下格式要到处去找所有的比如“ReadFile"、”WriteFile“ 等操作,更是头疼。

posted @ 2011-06-30 13:14 黄鹏 阅读(64) 评论(0) 编辑

       集合模式基于集合,用于在不改变现有结构的情况下,由访问单一对象升级为可访问一组对象,特别适用于代码重构过程中。它一般与观察者模式结合使用。主要应用于重构过程中将混乱的代码或者用别的不适用的模式写的代码升级为观察者模式之后,解决大量繁索的调用细节,将关联关系由一对一升级为一对多。对于类似事件处理的流程最为适用。

      考虑一个简单的日志处理系统。所有的业务逻辑类Logic都持有一个日志类Logger,Logger统一控制日志信息的去向,这样的系统已经非常灵活了,并且可以很容易地扩展Logger类来将日志存放到不同的介质中,甚至显示到不同的屏幕上。然而新的需求出现了,系统中产生的所有日志要同时出现在多个地方,比如在显示到屏幕的同时上传到网络上的其它设备中。

      解决方案之一创建一个同时将日志放到两个不同地方的Logger,然后将此类型的对象交给Logic。然而,每出现一种不同的需求,或者任意两个需要显示日志的需求全在一起,就要产生一个新的Logger子类。如果目前有N种处理日志的方式,隐含的要新创建的Logger子类就有N*N个! 显然这种设计不太实用。

      另一种方案是将Logic中所有写日志的地方都改成集合形式,这样灵活度和可扩展性都比较好。但面临的问题是要修改所有的Logic子类,并且还要在每一个地方处理诸如对象不为空等等的重复逻辑。工作量无疑非常大。

     集合模式正适合这种情况。它首先创建一个带有AddLogger、RemoveLogger集合接口的Logger子类LoggerCollection,并且持有一个数组(或者其它容量类的对象),每个元素都保存一个Logger,然后这样实现所有Logger接口的方法:对Logger的任何一个调用,都将其转换为对集合中每一个Logger对象的同样一个调用。在使用时他分为两步,第一是创建LoggerCollection类的对象,然后为每个需要处理日志的需求创建一个对应的Logger子类对象,并将所有这些Logger子类对象通过AddLogger方法添加到LoggerCollection对象中。第二就是用LoggerCollection对象代替原来的Logger子类对象,将它们交给所有的Logic。

    集合模式的优点是不改变现有的类,改动最小,但带来的灵活性却是最大的。很明显,以后可以很方便地将日志放到两个任意不同的地方,同时还不止两个,可以同时将日志放到任意N个不同的地方,而都无需任何修改,只需简单地创建每一个Logger,然后将它Add到LoggerCollection中。

posted @ 2011-06-28 19:53 黄鹏 阅读(1467) 评论(10) 编辑

         初次接触WEB开发,折腾了一段时间了。开始时只知道WEB好像离不开Server,后来知道有HTML这个东西,再后来见过javascript、css、php。作为新手,先总结一下目前知道的好的开发方式吧。

     WEB程序其实与单机程序基本一样,无非就是做个界面显示出东西,与用户交互,与其它程序交互。唯一不同的是:WEB界面一般不能直接调用底层服务提供程序,只能完全关注于信息的呈现和捕捉用户的输入,除此之外,界面对于业务逻辑处理毫无用武之地。这也强迫开发者将程序逻辑分层,是坏事的同时也是好事。信息送到服务器端后,这就无所顾忌了,想干啥就干啥。只不过不能直接控制界面,也不能主动给界面发消息。

posted @ 2011-06-21 19:20 黄鹏 阅读(114) 评论(0) 编辑

      最近设计的软件不太好弄,需求总变。费了好大劲折腾了很长时间搭好了框架,内容还没写完总会发现需求变了。接口变了,功能变了,业务也变了。于是很是郁闷。一次次地受打击。但是没办法,客户至上嘛。

      我的上级总找到我,说那个谁谁谁啊,你别考虑那么多啊。我总劝别人考虑结构,但我要劝你弄乱点,别什么东西都封装的那么严实。

      晚上本来想要加班,有点郁闷,在家反思一下。什么玩意软件工程、面向对象设计,全白扯,没有说到中国软件的本质。领导也说了,你再那么整就让你搞几个单片机锻炼一下。

      总之目前悟出一句很早就懂的话:先跑起来,再重构去吧。

posted @ 2010-11-01 19:55 黄鹏 阅读(42) 评论(0) 编辑
      昨天室友拿一个面试题为难我,问我C/C++函数调用是怎么一个流程。这问题实在简单,然而有一本什么面试宝典却说的前后不一,漏洞重重。室友尽信于书,非与我分个高低。单从机制本身来说,公说公有理,婆说婆有理,于是我就用了一个简单的实验才勉强说清楚。在此也顺便总结一下,从汇编的角度介绍一下函数调用过程。

      当调用者比如h调用某个函数f时,从编译器或者汇编语言角度来看,主要分以下几个步骤进行:
    1. h将实参按照从右向左的顺序一个个压入stack中。
    2. 执行一个转移指令call f
    3. f执行完函数体后,将返回值传入寄存器AX/EAX/RAX中。
    4. f执行转移指令ret
    5. h将实参从stack中一个一个弹出。
      由此可见,编译器是不会把“下一条指令地址”压入Stack中的。然而,当从f返回后,CPU是如何知道下一步应该执行什么指令呢?也就是说下一条指令的地址从哪来的呢?这当然还是从stack中获得。那么,这个地址是什么时候放到stack中的?还有,它什么时候从stack中出来的?这些工作是由谁来完成的?是调用者?还是被调用者?这就得先从内存的角度看一下Stack的变化。主是看esp/rsp寄存器的内空以及该地址对应的内存单元的内容。
      
      具体来说,从内存的角度看,函数h调用f时,Stack是按下面步骤发生变化的:
    1.  实参按照从右向左的顺序一个一个进入stack中。
    2. 函数调用指令之后的“下一条指令地址”进入stack中。
    3. 函数f中的局部变量加入到stack中。
    4. 函数f中的局部变量从Stack中弹出。
    5. “下一条指令地址”从stack中弱出,流入程序计数器寄存器IP中。
    6. 寄存器AX/EAX/RAX中的值流入到stack中h的局部变量(或者全局变量等)中。
    7. 调用函数f时的实参从stack中弹出。
      然而,由于编译器的优化,用较新的编译器将程序翻译成汇编后,这部分逻辑变的比较难懂。如较新的GCC/G++编译器压stack和弹stack的操作都不是用push和pop指令实现的,而是一次性地将ESP/RSP增加一定的数值(分配好实参的空间),然后用MOV指令将参数放入Stack中的,这样速度比较快。老版本的编译器翻译成的汇编比较好懂。

      那么,到底是谁将“下一条指令地址”放入stack中的呢?当然是调用者h了。其实这个功能是一条汇编指令call实现的,而不是简单的用push/pop/mov指令实现的。CALL指令的执行可以视为做了以下工作:
    1. 将“下一条指令地址”压入stack。
    2. 改变IP的值为被调用的函数的地址。
      相应地,将“下一条指令取出”的操作是被调用者做的。其实这是RET指令的功能,而不是用PUSH/POP/MOV来实现的。从硬件角度来讲,RET指令也没有什么特别的,它仅仅就是与CALL指令对应的功能相反的指令,与CALL做的工作恰好相反,恢复了IP寄存器,使其指向调用者调用函数之后的“下一条指令的地址”。

      想来个直观点的说明,最好还是通过一个小程序。昨天用GCC已经做过测试,由于版本比较新,它做的优化太多了,比如它尽量使用寄存器进行参数传递而非Stack,所以介绍起来比较麻烦。并且GCC使用的AT&T汇编格式比较难懂,还是用WINDOWS下都熟悉的MASM格式的汇编来列一下吧。同时为了清晰,少费点口舌,就用可视化的工具VC++6.0来介绍。

      首先,假设程序代码如下(很简单的):
int f(int a, int b)
{
    
return a*b;
}
int main(int argc, char *argv[])
{
    
int x = 0;
    x 
= f(5,6);
    
++x;
    
return 0;
}

      编译完的汇编代码不再列出。对它调度跟踪一下,一切问题都有了着落。比如先把断点设在x=f(5,6)的地方,执行到该位置后,各个寄存器的值如“Resisters”窗口所示,此时的断点以及对应的汇编代码如下:

      接着执行,执行到call那条指令时,内存内容及相应寄存器的值如下面的图。Memory窗口显示了当前Stack从顶部开始的内存内容。最顶的是参数5(占4个字节,从低到高),然后是6,这两个是传给f的实参。此时,“下一条指令地址”也就是紧挨着的add指令的地址(0x401078)并没有放到stack中。由此可见,“下一条指令地址”是第一个进栈的这种说法是不对的。



      然后接着执行,采用step into(VC6.0中对应F11),也就是仅执行call这条指令,而不执行f中的任何指令。执行后如下图。可以看出,此时寄存器ESP的值变了,向下移动了4个字节,也就是stack中新插入了一个4字节的整数(其实它是一个内存地址)。这个新进来的地址是0x00401078,对应main函数中的add那条指令,也调用时的就是“下一条指令地址”。现在很清楚了,将“下一条指令地址压stack”是CALL指令的功能,是硬件干的,而不是软件。



      然后要说明的就是函数f返回过程了。当程序执行到RETURN语句时,对应的内存和寄存器状态如下。可以看出,RET指令执行之前,“下一条指令地址”还在stack中,同时EAX的值0x1E也就是30就是函数f的返回值。用EAX传递返回值是编译器的一个习惯,能不能说是标准我不太确定。反正GNU系列的编译器和微软的都是这么干的。



      最后,当f中的RET执行完后,会形成如下格局。与上图对比,会发现,stack顶的值跑到EIP里面去了。stack中仅剩下之前的两个实参!所以,“下一条指令地址”是第一个出栈的,而不是最后一个。接下来的add指令的意思是将ESP加8,其实就是将之前放入stack的两个实参从stack中移除。函数调用也到此结束。



posted @ 2009-09-05 15:50 黄鹏 阅读(1803) 评论(3) 编辑
    新学期刚开始,就业已经拉开了序幕。
    今年遇到的第一个招聘公司是迅雷。当时由于时间紧张,手头事情比较多,简历还没有做完。后来参加了霸王一笔,全是基本语言语法,答的很惨。心想一定被拒了,然而考完几个小时,也就是凌晨不到一点的时候,收到了迅雷的二笔通知,心中狂欢。
    二笔安排在第二天晚上7点到9点,三个程序题(30分+30分+40分),2个小时,答的够累的。两张答题纸用完我又要了三张,最后还是没写完,也只好就此收尾了。说实话题目很容易的,自己答的很有自信。然而,我却没有收到面试通知。
    我在想,什么原因把我给拒了?真的是笔试没答好吗?想了很久,觉得可能是简历上工作地点的原因,因为我对地点的要求是北京和上海,没有深圳。说实话,我还是很想去迅雷的。于是就去了霸王面。但是不凑巧,所有去霸王面的,都只是被拒在了门外。没办法,只能恭喜闯关成功者了。
   昨天晚上回来,室友又拿笔试题来和我探讨,猛然发现,自己把题意搞错了!心中特别惭愧,同时也输的心服口服。
   最后,把迅雷笔试题贴出来留个纪念吧。

(下面来自CSDN博客,转载请标明出处:http://blog.csdn.net/morre/archive/2009/09/01/4509390.aspx
迅雷2010校园招聘吉林大学第二次笔试题

答题时间: 2小时,请将答案写在答题纸上
一. 有n个文件的长度记载在一个无符号64 位整数数组中unsigned __int64 file_length[n],把这n 个文件从逻辑上按序首尾拼接在一起形成一个逻辑上的大文件,然后以每块长度为unsigned block_length把这个逻辑上的大文件划分成大小相等的数据块(当然,最后一块有可能比block_length小),请定义和实现一个函数,把边界块的序号集合返回给函数的调用者(第一个数据块序号为0)。
注:边界块指的是跨多个文件的数据块。(30分)


二. 请实现一个函数,把两个从大到小的有序链表合并成一个链表,新的链表是一个从小到大的有序链表。
struct list
{
int value;
list* next;
};
list * merge (list *list1_head, list *list2_head);
(30分)


三. 如果两个英文单词,组成它们的字符集合相同,而且相同字符出现的次数也相同,则称这两个词匹配:比如说:同”abbc”与词”babc”是匹配的。有一个词典,存储在字符串数组const char* dictionary[n]中,数组的每一个元素是一个词。对于任意给出的句子。句子中的单词使用空格分割。请实现以下函数,判断句子中是否有词和词典中的词匹配。
bool is_matching( const char* dictionary[], int n, const char* sentence);
(40分)


注意:这一题需要先描述思路,再写程序,没写思路扣10分。



    
posted @ 2009-09-05 13:45 黄鹏 阅读(756) 评论(0) 编辑
摘要: 我跟踪一个系统的BUG,发现非常离谱的事:malloc时竟然出现错误,并且直接崩溃,而没有返回。 出错的地方在malloc内部调用_int_malloc中,执行到指令movl %rcx, (一个内存地址)时引起的。不知道是系统的原因还是C函数库的原因引起的。阅读全文
posted @ 2009-06-23 14:53 黄鹏 阅读(483) 评论(1) 编辑
posted @ 2009-03-23 14:18 黄鹏 阅读(149) 评论(0) 编辑
posted @ 2009-03-12 16:23 黄鹏 阅读(844) 评论(0) 编辑
posted @ 2009-03-11 17:10 黄鹏 阅读(3545) 评论(3) 编辑