产品分类
联系我们

销售直拨
     025-85550202;
     025-85550520;


master@csch.com.cn

技术咨询:
     025-85550520

duan@csch.com.cn

售后服务专线:

     15251851604    

wu_yuyang@csch.com.cn

传 真:025-85550303


深圳市中霍电子技术有限公司
地址:深圳市龙华新区龙华街道牛地埔村美满圆小区
联系人:颜安军/副总
Mobile:18038070895
E-mail: szyanaj@csch.com.cn  
 
首页  >  新闻中心  >  其它

拥抱函数式编程

来源 :CSDN知识库


几十年来我都在用面向对象的语言编程。我用过的第 一个面向对象的语言是 C++,后来是 Smalltalk,是 .NET 和 Java。 我曾经对使用继承、封装和多态充满热情。它们是范式的三大支柱。 我渴望实现重用之美,并在这个令人兴奋的新天地中享受前辈们积累的智慧。 想到将现实世界的一切映射到类中,使得整个世界都可以得到整齐的规划,我无法抑制自己的兴奋。 然而我大错特错了。 

 01  继承,倒塌的第 一根支柱 

乍一看,继承似乎是面向对象范式的zui大优势。所有新手教程讲解继承时都会拿出zui简单的继承的例子,而这个例子似乎很符合逻辑。


甚至以后的一切都是重用了。 我囫囵吞下这一切,然后带着新发现兴冲冲地奔向世界了。 香蕉猴子丛林问题 带着满腔的信仰和解决问题的热情,我开始构建类的层次结构然后写代码。似乎一切皆在掌控中。 我永远不会忘记我准备从已有的类继承并实现重用的那一 天。那是我期待已久的时刻。 后来有了新的项目,我想起了另一个项目里我很喜欢的那个类。 没问题,重用拯救一切。我只需要把那个类拿过来用就好了。 嗯……其实……不仅是那一个类。还得把父类也拿过来。但……应该就可以了吧。 额……不对,似乎还需要父类的父类……还有……嗯,我们需要所有的祖先类。好吧好吧……搞定了。没问题。 不错。但编译不过,怎么回事?哦我知道了……这个对象还需要另一个对象。所以那个也得拿过来。没问题…… 等等……我不仅需要那个对象,还需要那个对象的父类,和父类的父类,和……包含的所有对象的所有祖先…… 唉…… Erlang 的创建者 JoeArmstrong 有句名言:

面向对象语言的问题在于,它们依赖于特定的环境。你想要个香蕉,但拿到的却是拿着香蕉的猩猩,乃至zui后你拥有了整片丛林。
香蕉猴子丛林的解决方法 这个问题的解决方法是,不要把类层次建得那么深。但如果继承是重用的关键,那么给继承机制添加的任何限制都会限制重用。对吧? 没错。 那我们可怜的面向对象程序员该怎么办?指望一杯三聚氰胺奶维系我们的健康吗? 答案就是:包含和委托(Contain and Delegate)。一会儿会详细解释。 菱形继承问题 早晚你会遇到下面这种恶心的问题,有些语言甚至根本解决不了。 


注意 Scanner 和 Printer 类都实现了名为 start 方法。 那么问题来了,Copier继承哪个start?是Scanner的还是Printer的?肯定不可能同时继承啊。 菱形继承的解决 解决方案很简单:不要这样做。没错。大多数面向对象都不让你这么干。 但是,但是……要是必须这样建模该怎么办?我需要重用! 那就必须使用包含和委托


注意现在 Copier 类包含一个 Printer 实例和一个 Scanner 实例。然后将 start 函数委托给 Printer 类的实现。要委托给 Scanner 也很简单。 这个问题是继承这根支柱上的另一条裂缝。 脆弱的基类问题好吧,那我尽量使用较浅的类层次结构,并保证里面没有环,这样就不会出现菱形继承了。 似乎一切都解决了。直到我们发现…… 我前一 天工作得好好的代码今天出错了!关键是,我没有改任何代码! 嗯也许是个 bug……但等等……的确有些改动…… 但改动的不是我的代码。似乎改动来自我继承的那个类。为什么基类的改动会破坏我的代码? 原来是这样…… 看看下面这个基类(用Java写的,但就算你不懂Java,应该也很容易看懂):


从基类的作者的角度来看,这个类实现的功能完全没有变化。而且所有自动化测试也都通过来了。 但是基类的作者忘记了继承的类。而继承类的作者被错误吵醒了。 现在ArrayCount的addAll()调用父类的addAll(),后者在内部调用add(),而add()被继承类重载了。 因此,每次继承类的add()被调用时,count都会增加,然后在继承类的addAll()被调用时再次增加。 count被增加了两次。 既然会发生这种现象,那么继承类的作者必须清楚基类是怎样实现的。而且,基类的每个改动必须要通知所有继承类的作者,因为这些改动可能会以不可预知的方式破坏继承类。 唉!这个巨大的裂隙威胁到了整个继承支柱的稳定。 脆弱的基类的解决方法 这个问题还得要包含和委托来解决。 使用包含和委托,可以从白盒编程转到黑盒编程。白盒编程的意思是说,写继承类时必须要了解基类的实现。 而黑盒编程可以完全无视基类的实现,因为不可能通过重载函数的方式向基类注入代码。只需要关注接口即可。 这种趋势太讨厌了…… 继承本应带来zui好用的重用。 在面向对象语言中实现包含和委托并不容易。它们是为了继承方便而设计的。 如果你和我一样,你就会开始反思这个继承了。但更重要的是,这些问题应当引起你对于通过层次结构进行分类的反思。 层次结构的问题 每到一个新公司时,我都要为在哪儿保存公司文档(即员工手册)而纠结。 是应该建一个Documents文件夹,然后在里面建个Company呢? 还是应该建个Company文件夹,然后在里面建个Documents呢? 两者都可以。但哪个是正确的?哪个更好? 层次分类的思想是因为基类(父类)更通用,继承类(子类)更专用。沿着继承链越往下走,概念就越专用(见上面的形状层次)。 但如果父节点和子节点能随意交换位置,那么显然这种模型是有问题的。 层次结构的解决 真正的问题出在…… 层次分类是错误的。 那层次分类应该用在哪里? 包含关系。 真实世界里有很多包含关系(或者叫做独占关系)的层次结构。 但你找不到层次分类。仔细想一下。面向对象范式是根据充满了各种对象的真实世界建立的。但它用错了模型——层次分类在真实世界中没有类比。但真实世界里到处都是层次包含关系。层次包含关系的一个非常好的例子就是你的袜子。袜子放在装袜子的抽屉里,然后抽屉包含在衣柜里,衣柜包含在卧室里,卧室包含在房子里,等等。  硬盘上的目录也是层次包含关系的另一个例子——它们包含文件。 那我们该怎样分类呢? 仔细想一下公司文档,就会发现其实放在哪儿都无所谓。我可以放在Documents目录下或者放在Stuff目录下也可以。 我选择的分类法是标签。我给它加上不同的标签。