此文只是杂乱的记录一点点对于面向对象的个人看法,有些观点也并非原创。没什么系统性可言,虽然笔者稍作整理,但始终还是显得很散乱,只是一些片段的堆积。
由于涉及的题目过于庞大,反而不知道如何下笔。先罗列一下问题,之间没有严格的先后之分,纯粹就是笔者想到哪里,就写到哪里。也不一定就会解答。继承的本质是什么?为什么一定要有接口?c++多继承为何饱受非议,真的就一无是处?为何笔者就反感go接口,反正go独有的一切,笔者都是下意识的排斥?功能繁杂的Com,结合C++的自身特点,能否改头换面? ……
在原教旨眼里,面向对象的教义就是“对象+消息发送”,整个程序由对象组成,而对象之间的就仅仅只通过发送消息响应消息来交互,程序的功能都是在对象与对象的来回消息发送中完成,用现实事情类比,人类就是一个个活生生的对象,人类通过消息的往来,比如语音、文字、广播等,有人制造新闻,有人接受到这些消息后,各自反应,最后完成一切社会活动。好像说得有点抽象,展开来说,其实就是,消息的发送者,原则上不需要事先了解目标对象的任何背景资料,甚至他明知道对方不鸟消息,比如说,明明对方就是一个乞丐,但是并不妨碍你向他借500万人民币,反正,消息就是这样发送出去的。然后,对象接受到消息之后,就各自反应,比如说有人真的借钱给你;有人哭穷;有人嘀咕你到处借钱,无耻;……,各式各样,不一而足。
听起来好像人类社会活动就是消息的往来下推动,艰难的前进,但是,这能拿来搬砖吗?可以的,真的可以!即便是C语言,都可以来搞消息发送这种高大上的事情,就好像win32那样子,通过SendMessage函数给窗口发送消息,其签名如下:
LRESULT SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
好像参数有点多。说白了,消息发送就相当于成员函数函数调用的一个新马甲,换了一种说法而已。成员函数调用,形式是这样子,obj.fn(param1, param2, …),涉及到对象,函数名字,还有参数,可能参数数量不止一个,参数类型也各不一样,这些都没关系。hWnd为窗口,也即是对象;Msg为函数名称,现在用正整型编号来代表,有些消息发送系统用原子,qt好像是用字符串(性能堪忧啊);wParam,lParam可以看成void*类型,也即是函数的参数,用这两个值封装所有的参数。天真,天下函数参数类型成千上万,参数数目或0个、或1个、或三五个、或七八个,就wParam,lParam这两个弱鸡,就能封装得过来?可以的,通过强制类型转换,就可以让void*的值保存char、int、float等值,又或者是将参数打包为结构体,这样子,就可以应付千千万万的函数参数要求,这样子,不要说,有两个wParam,lParam来传递参数,就算是只有一个,也都可以应付千千万万的函数要求。
那么,如何响应消息?可以参考win32的原生api开发,这里就不展开了。原理就是,每个对象都有一个函数指针,那个函数把全部的成员函数都压缩在一个庞大的switch语句里面,每个消息编号case分支,就代表一个成员函数,显然,这个分支,要先将wParam,lParam里面在还原成对应参数的实际情况,然后再执行相应操作。
SendMessage显然抹去了所有窗口的具体类型信息,甭管你是按钮、漂亮按钮、菜单、编辑框、……,全部一律都退化成窗口对象。要往编辑框里面添加文字,就给它发送添加文字的消息,wParam,lParam就带着要添加的文本和长度。而不是调用编辑框的添加文字的成员函数来做这个事情,最明显的事情,就是也可以给按钮窗口也发送添加文本的消息,虽然按钮窗口对此消息的反应是啥也不做。令人惊讶的是,你可以子类化一个按钮窗口,让它对添加文本的消息做出反应,这完全是可以的。
显然,原教旨的面向对象教义,的而且确,灵活,解耦彻底,不同类型对象之间的耦合关系一律不复存在,之间只有消息的往来。随心所欲的发送消息(胡乱调用成员函数),自由自在的反应消息(一切全无契约可言),不理睬,或者这一刻不理睬下一刻又动了,或者这一刻动了下一刻又拒绝反应。甚至,消息还可以保存,排队,求反,叠加什么的,也即是消息已经是一种抽象数据类型了,支持多种运算。相比于不知所谓的基于类的静态面向对象(继承封装多态),简直不可同日而语,太多的约束,呆板的语法,深入的哲学思考,架床叠屋的类型关系,也好意思学人家叫面向对象。
当然,对象+消息发送这种机制,付出的代价也是很巨大的,基本上,函数调用的静态类型检查不服存在,所有问题都要到运行时才能发现。并且,消息发送的语法也很不直观,必须各种类型转换,而响应消息时又必须转换回去。此外,为函数定义消息编号,也很恶心。不过,这些在动态语言里面都不是问题,反正,动态语言里面没有静态类型约束。另外,笔者用template、全局变量、宏等奇技淫巧,在c++里面,已经实现了类型安全的消息发送框架,比如,Send(obj, kAppendText, U8String(“hello”)),而对象实现对消息的响应,直接也是成员函数的形式,并且还是非侵入式的,也即是说,在main函数之前,可以随时在任意地方给对象添加新的消息反射,所有参数上类型转换以及返回值上的类型转换,全部都不需要了。 但即便是这样,也不赞成原教旨的面向对象到处泛滥。原因是,用它写出来的程序,类型层次很不清晰,相比于架构良好的类形式的面向对象程序,可读性远远不如,也不好维护。更深刻的原因是,对象+消息发送的威力太惊人,用途太广,任何多态上的行为,都可以用它来做。什么都可以做,就意味着什么都尽量不要让他来做。
其实,即便java、C#这种继承封装多态的面向对象千般弱鸡各种繁文缛节,也不妨碍人家称霸天下,到处流行。你对象+消息发送再美妙,流行度都不及人家java一个零头,obj c还不是靠着ios的流行才有所起色,挤入排行榜十名内。虽然说市场不能说明什么,但是对比如此悬殊,自有其道理。
再说,静态类型的成员函数调用模式,广泛存在于人类社会活动中。人与人之间的很多事情,其实只要满足一定的条件,必然就会发生,其后果也可以预料。很多消息的发送,其实是有考虑到对方的身份问题,才会发起,好比小孩子跟爸妈要零用钱的消息,小孩子再发送要零用钱的消息,一定是针对亲人才发起的。真相是,往往要满足一些必要条件,消息才得以发起,当然,只要你高兴,随时都可以发起任何消息,问题是,这种人多半不正常。量体裁衣,针对什么样的问题,就应该采用相应的手段,一招鲜吃遍全天下,行不通的。具体问题,必须具体分析。每种问题,都有自己最独特有效的解法。笔者在原教旨的面向对象上重复太多内容,连自己都恶心,以后应该很少再提及。
所以说,面向对象的设计,首先应该采用的必然还是继承封装多态的思路。在此基础上,根据不同的动态要求,采用不同策略来应对。企图用万能的消息发送来代替静态类型面向对象的荒谬就如同用僵化的面向对象来模拟一切动态行为,两者都是犯了同样的毛病。可是,静态面向对象做设计,又确实困难重重,而最终的开发成果,总是让人难以满意。那是因为,广大劳动群众对静态面向对象一些基本概念的理解,存在这样那样的误区,而由于面向对象语言(java,C#)还缺乏一些必要机制,导致设计上出现妥协,原则性的错误越积越深,以至于最后崩盘。其实,不要说一般人,就连大人物,在面向对象上,也都只是探索,好比c++之父BS,搞出来多继承,虚继承,iostream体系,在错误的道路上,越走越远,越走越远。
好吧,其实,多继承,还是很有作用的,在很多奇技淫巧上很有用武之地,很方便。但是,用多继承做架构的危险,就在于其功能太过强大。这就意味着它要沦落成为goto啊、指针啊那样的角色,先甭管它钻石尴尬。多继承的最重要角色,概念实现,也即是接口,也即是定义一批虚函数,里面没有任何数据,这个抽象就必须鲜明,这一点,java和C#就做得很到位。就应该从多继承上提炼出来这么一个好东西,咦,对了,为何要有接口?没有接口,就真的不行吗?是的,静态面向对象里面,接口确实必不可少。
继承,本质上就是分类学。而分类,最重要一点,就是任何一件元素,必须也只能只属于其中一个类,不得含糊。可以存在多种分类方式,但是,一旦确定某种分类方式,那么集合里面的一个东西,就必须只能属于其中一大类。继承,就是分类的一再细化,也是概念的继续丰富。比如说,从生物到动物到哺乳动物,概念包含的数据越来越多。所以说,继承体现的是数据上的丰富关系,它强调的是数据的积累,从远古基类开始,一路积累下来的数据,全部必不可少,也不得重复,一旦违反这条底线,就意味着继承体系上的错乱。继承,相当于类型的硬件,缺乏硬件元器件时,就无法完整表达该类型的概念。比如说,人类可分为男人、女人,自然,男人有男人的阳刚,女人有女人的阴柔,那么阴阳同体怎么办,集两性之所长,难道就要阴阳人多继承与男人女人吗?那么,这样继承下来,阴阳人岂不是就是有两个头,四只手,四条腿了,啊,这不是阴阳人,这是超人,抑或是怪物。所以,阴阳人应该是人里面的一个分支,也即是,人的分类,就要有男人、女人、阴阳人这三大基类。再次强调,继承是为了继承数据,而不是为了功能,功能只不过是数据的附带品。那么,怎么描述男人的阳刚、女人的阴柔,怎么避免阴阳人引入后,分别从男人阳刚,女人阴柔上复制代码呢?此外,再次考虑平行四边形,下面好像又有菱形,有矩形两大类,然后身集菱形矩形的正方形,这里的分类该如何处理,难道忍不住要让正方形多继承菱形矩形吗?从这个意义上讲,在同一体系下,多继承的出现,理所当然,大错特错,由此可知,iostream就是败类。iostream通过虚继承避免绝世钻石的出现,但是这个虚继承啊,真是要让人呵呵。C++中引入虚继承真是,怎么说呢,好吧,也算脑洞大开的优良物品,也不是完全一无是处,起码,在iostream上就大派用场了。你就说说,虚继承那点不好了?就一点,为了子子类的千秋基业,子类必须虚继承基类,子类受子子类影响,就这一点,你能忍。
突然发现,文章已经很长了,不管了,这就打住。至于非侵入式接口,以后再说吧!