关于面向对象
很多程序员应聘的时候,除了会写精通某种语言之外,往往还会写上熟悉和精通就是面向对象的软件设计,结果与之交谈之后才发现,根本不是那么回事,他会定义类,也会从一个类派生另一个类,然后会使用这个类,这就是面向对象的设计。其实,还差的远呢,类,继承,派生是面向对象的设计思想的基本概念,但是,这不是全部。多年前,当我刚刚从turbo c2.0转向borland c++3.1的时候(大约是1997年吧),读了一本c++教程之类的书,我也是这样认为,从面向过程到使用类来解决问题,的确让人感觉兴奋,我那时候也认为自己懂得了什么是面向对象的设计。因为使用vc写了很多代码,也定义了很多类,觉得自己对于面向对象的思想理解的很深了。可是在我读了<敏捷软件开发>一书之后,我才知道面向对象的设计思想还有这么多指导性的原则,这些原则会让我们的设计如此优雅和美妙,然后,我改进了新版软件的设计,而且使之具备了二次开发的功能。关于面向对象的设计思想,我仍然在学习中,实践中,理论和实际相结合,才可以真正领会面向对象的精妙。建议每位热爱软件开发的朋友一定多读一读<敏捷软件设计,>这本书。
走过多年的路之后,回想自己对于问题的认识,才发现,在我最无知的时候,往往认为自己理解了整个世界,当我们开始认识世界的时候,才意识到自己的浅薄与无知。
幽灵一样的基类
聊斋中有这样的情节,一个人被一个鬼怪或神仙附体了,则这个人会具备超出的能力,基类也是这样。我们随意通过vc的应用程序向导创建一个应用程序,然后,希望在应用程序类中打开数据库,创建数据库连接,需要设置ado环境,写一些访问数据库的代码,并且写一些特定的函数访问数据表,如果我们有一个数据库访问类,其中封装了访问数据库的基本操纵,则我们只需要将这个数据库访问类作为应用程序的基类,则我们可以通过应用程序类的指针调用数据库访问类中的函数,对数据库进行操作。我们没有对应用程序类内部作任何改动,该类突然之间具备访问数据库的超长能力,如果你有一个socket通信的类,你将这个类也作为应用程序类的基类,则应用程序类一下子具备了网络通信的功能,积累就象一个神通广大的幽灵一样,它附到哪个类的身上,哪个类就具备了跟幽灵同样强大的功能。
这就是继承。定义一个动物类,在此基础上派生一个哺乳动物类,再顺序派生一个猫类,一个波斯猫类,一个类比另外一个类更具体,每个子类都继承了父类的特征,这是最常见的继承。然而,这种继承不让人觉得功能强大,如果一个类继承了一个与它本身没有任何联系的父类,则会让人感觉到基类带来的震撼效果,例如前面提到的应用程序类,继承了数据访问类,则我们可以一通过该类的指针访问数据库了。如果你将波斯猫类增加一个plane的基类,则这个波斯猫也将具备飞机的特征,这个波斯会飞了,canfly()函数返回true。再增加一个互联网访问的基类呢?这个波斯可以访问互联网了。多重继承将好好的一个波斯猫。变成了一个怪物,基类是不是很象一个个幽灵?
接口
最初在哪里什么时候知道接口这个概念,记不清了,只记得最初接触ADO时,知道ADO是COM组件,而_RecordsetPtr、_ConnectionPtr等等就是组件的接口,接口是组件的接口,我们可以通过接口调用组件。后来逐渐的在自己的程序中使用接口,感觉接口真是一个好东西。
例如,我在一个界面上动态创建很多数据显示控件,有CEdit,CComboBox、RadioBox、CDateTimeCtrl、CRichEditCtrl等等,我希望动态的加载数据LoadData()和保存数据SaveData(),为了方便的统一调用这些控件类,我定义了一个抽象类IDataCtrl,其中有两个纯虚函数:
class IDataCtrl
{
public:
virtual bool LoadData()=0;
virtual bool SaveData()=0;
}
然后再分别定义CEdit、CCombox的派生类,实现这个接口如下:
class CDataEdit:public CEdit,public IDataCtrl
{
bool LoadData();
bool SaveData();
}
这样,我可以在一个链表中保存这些控件,并可以统一处理这些控件的数据加载和保存。
类似的,在程序中要对很多类似的对象进行统一处理的时候,可以定义上面这样的接口,使得代码的编写非常方便。
上面的接口还有一个好处是:调用者只需要保护接口的头文件,而无需保护具体控件(接口的实现者)的头文件,这样调用者只与接口有关系,我们对于数据控件的任何改变,都不会影响调用者,也就是说,接口隔离了数据控件的调用者和数据控件本身的耦合,如果你部署了应用程序,则你可以保证接口不便的情况下,改进并升级应用程序。
想想看,如果调用者直接调用了每个控件指针,则在每个控件自身增加函数或变量时,由于调用者包含了控件类的头文件,则调用者必须重新编译,否则程序将不能运行。调用者在这里显得非常无辜,因为它只关心数据的加载和数据的保存两个操作,它根本不关心数据控件自己是如何加载和保存数据的,也不关心控件为了实现这些操作动用了哪些资源,因此完全没有必要因为给数据控件增加一个成员变量,而重新编译整个调用者。怎么办,只有通过抽象,也就是通过接口,隔离这种改变对调用者的影响,对调用者和控件进行解耦合。
接口可以使得模块与模块之间的耦合度降到最低,强内聚,弱耦合,正是我们进行模块化设计追求的目标。
如果你希望在自己的程序中增加一些新的控件,被调用者调用,这非常简单,只要实现IDataCtrl接口即可。接口使得程序的可扩充性增强。
C++中没有接口Interface这样的关键字,C#中有,C++中的接口就是一个抽象类。
尽管在面向对象的设计中才出现,然而,接口这个东西在我们的生活中到处都是,例如键盘、鼠标、显示器就是一台普通的电脑与人的接口,计算机制造商在保证这三个基本接口不变(键盘的输入功能、鼠标的输入功能、显示器的显示功能)的情况下,生产出了各种各样的电脑,而且还可以增加新的接口,例如音箱和耳机。你购买了一个螺丝刀,这对你非常重要,因为你经常的拆装电脑,你为什么这么自信这个螺丝刀一定可以帮你解决问题,因为你知道螺丝刀与螺钉的接口是确定的。
还有一个有趣的例子,手机。最初的手机就是就是打电话,这是它最基本的功能,为了实现这个功能它有四个接口,键盘,显示屏,话筒和听筒,在保证这四个接口功能的情况下,各大制造商不断的改进设计,推陈出新,以至于我们很多人的手机都可以照相、录像、看电影,听MP3,上网,和其他许多新的功能,这些都是新增加的接口,但是最初的四个基本接口从来就没有改变过,如果10年之前你用过手机,今天你同样会使用手机,因为你最初调用的四个接口没有改变,当然如果你希望使用新的接口,你必须更新大脑中的有关数据了。
如果我们将手机本身作为一个接口,则这个接口实现了人与人之间的通信。在没有手机没有电话的情况下,处于两个城市的人,无法进行交谈,两个手机之间可以进行通信,而两个人希望通过手机实现通信,必须实现如下的接口:输入手机号,拨号,听,讲话(调用了键盘输入、显示屏、听筒、话筒);看谁的来电、按下接听键,听,讲话(调用了键盘输入、显示屏、听筒、话筒)。如果你希望通过手机跟另外的一个人讲话,这个人也实现上述四个接口就可以了,因此,在手机接口不变的情况下,你可不断的扩大交谈的对象范围,正是在手机接口不变的情况下,手机厂商获得了巨额利润,移动网络运营商也是赚的钵满盆盈。瞧,接口多好,多么奇妙,想想,微软也是通过保持系统基本接口不便,然后不断升级系统,增加新的接口保持业界老大的。
如果你愿意,你可以把所有通过手机交谈的这些对象保存到一个链表中啊,调用一个新的接口,这个接口是短信,然后实现短信群发。
变量的初始化
这是一个刚刚学习编程的人经常犯的错误,尤其是c++中的指针。代码编译没有任何问题,就是运行不能通过。例如例如你在一个类中定义一个指针CWnd *m_pWnd,没有进行初始化,然后你通过这个指针发消息,并且在发消息之前你确实判断了指针不为空,大门如下:
if(m_pWnd)m_pWnd->SendMessage();
由于m_pWnd没有被初始化为NULL,你也恰好没有给它赋一个有效的值,程序编译可以通过,if判断也可以通过,就是不能发消息,一运行到m_pWnd->SendMessage()就出错。初学编程的人往往不知所措:我的代码肯定没有问题,怎么会产生运行时错误,我刚刚从另外的程序中复制过来的啊。自己刚刚开始学习vc也经常犯这样的错误,有时候收到个求助的邮件,也是这样的错误。
异常处理
初学编程最急切想看到的是你小心翼翼的输入到电脑中的代码可以按照你预计的方式运行,并且得到你希望看到的代码,即使中间出现了错误,在你的一番抓耳挠腮挠腮之后,你总是能够看到你希望看到的。这给了你继续学习的信心。很多介绍计算机语言的教材,开头总是一个hello world程序,这个程序可以让人立刻看到程序的结果,这是预期的,没有任何悬念。我们总是从最简单的入手,我们发现只要我们足够仔细,程序就可以正常运行,到了课程快结束的时候,开始介绍“异常”了,这东西有什么用,没有异常处理我的程序不是也能运行吗?我的一个新同事就不理解:干吗非要抛出个异常啊?我的程序不是运行的很好吗?我说正常情况下,你程序不会出错,但是如果用户胡乱输入一些字符,你的程序可能就出差了,如果出现一个你没有预料到的错误,则你的程序将产生运行时错误,不可阻止的退出了,这会让使用者非常恼火的。他似懂非懂,但是接受了我的说法。
如果只是学习编程,编写的代码只为学习,那肯定不需要异常处理,你肯定不会重视异常处理,但是,如果你的软件将被提供给某个客户使用,假如你在用户的工作现场,突然你的程序出现一个运行时错误,你的用户不解的看着你,等待你的答复,这时候你除了会暗自嘀咕用户怎么会这么操作之外,可能很希望当初哪个函数里应该有个try catch。
在一个程序中是否需要每个函数中都有try catch?当然不需要,因为这会影响程序的运行效率。我建议在所有的消息响应函数中,各种初始化函数,关键的析构函数中,OnClose()中,应该添加try catch,而在其他的函数中,则不必总是使用try catch,尽可能通过代码处理所有可能的异常,即使有的异常无法捕捉,因为在所有的消息响应函数、初始化函数、析构函数中都有异常处理函数,因此不会出现运行时错误。
本人对于异常处理的理解并不深刻,现正在设计一个完整的软件,正在琢磨是否有必要设计一套完整的异常处理机制和定义一套全面的系统异常列表。因为我们的软件有自己的SDK,为了方便其他用户对系统进一步开发的,非常有必要对开发过程中出现的异常有系统的描述,例如我们使用ADO时,可以根据异常的编号,查看到异常的文本信息甚至是解决办法。使用MFC时,通过捕捉的异常编号,也可以得到关于异常的其他说明文字和处理办法。我希望我们的软件的SDK也有这样的功能。
编写计算机语言书籍的作者,是否可以将异常处理部分提前,这样,初学编程,就不会出现不重视或者不理解异常处理的问题。
未来的手机
手机正在改变我们的生活,很多手机越来越具备计算机的功能,例如我的诺基亚9300,可以读写Word文档,可以上网,可以记事,还可以收发传真,看电影,玩游戏都没有问题,比如,我现在的这篇文字,基本上是在9300上写的。将来的手机会是什么样子呢?
手机越来越具备电脑的功能,当手机的处理器足够快的时候,在很多场合下,手机完全可以取代电脑。手机的限制是屏幕小,键盘小,没有鼠标,这没有关系,将来的手机有一个超强的USB接口,通过这个USB接口,我们可以将普通液晶显示器、键盘和鼠标接到手机上,这样,就可以完全解除手机的限制。大街上有很多投币外设,包括显示器,鼠标和键盘,旁边有一个投币箱,投入硬币之后就可以使用。甚至在公共汽车上,火车上都有带投币外设的座位,如果你希望像使用PC机一样使用手机处理数据,那么你可以公用外设投币箱中投入一个钢蹦,然后将手机外设的统一USB接口接入你的手机,然后你就可以通过17寸显示器。标准101键盘和罗技鼠标处理您的数据了;
网吧还存在吗?存在,但是会有很多没有主机的作为,只有外设,手机可以作为你的主机使用,你可以带着手机上网,因为手机上可以接键盘鼠标显示器了。网吧上网的费用低廉,比手机无线上网便宜多了,而且速度更快。
不知道这样的设想中国的那家公司会感兴趣,这个手机上的专用USB接口,谁要是首先实现了,制定出接口规范,这家公司可就牛了。接口可以让一家公司很牛的。如果中国没有这样的公司,我想这个公司可能就是诺基亚吧。
不编程,我还能干什么?
从最初的柴油机测试到柴油机设计,一直到现在的软件设计,回想一下,觉得自己现在处理计算机之外,其他的技能都忘记了,如果有一天,我不作软件设计了,哪我该干什么?刚刚毕业的时候,我最喜欢的工作是做汽车设计,我现在也想,但是似乎离着汽车设计越来越远了。如果让我选择,我可能会选择设计机器人。因为我觉得自己的生活中需要一个机器人,尤其是在2004年有了自己的孩子之后。我和LP都是远离父母在青岛工作的,当有了自己的孩子之后,感觉很多的不便,当孩子很小的时候,LP一个人在家做饭都很费劲,LP必须时刻看着哪个小东西。如果外出买菜,LP必须一只手抱着小家伙,一只手提着菜,到了门口之外,还需亚非常费劲的开门。当然还有很多其他的事情。我想,如果如果设计一个机器人,可以帮助买菜,做饭,看孩子等等,那么就好多了。
我想很多像我这样的人生活在城市里的,他们都有像我这样的需求,或许,可以做家务的机器人会非常有市场。