文档章节

再见了,面向对象编程

周星_1980
 周星_1980
发布于 2016/07/27 18:40
字数 2867
阅读 191
收藏 4

原文:https://medium.com/@cscalfani/goodbye-object-oriented-programming-a59cda4c0e53#.z48fmajih

这是一篇长文,共有三大块,我先翻译第一块,以后有时间更新后面的

我已经用面向对象编程了好几十年了,从最开始的C++然后到Smalltalk最后到.NET和JAVA。我一直期待能充分利用面向对象编程的三大核心:继承、封装、多态带来的好处。我渴望从代码复用这一效果中能窥探到前辈们的智慧。想象着把真实世界映射成代码里各种类,我简直不能太兴奋,还满怀期待的等着他们像真实世界一样正常运转起来。

这样想就大错特错了。

继承,第一个失败的核心

乍一看,继承显然应该是面向对象编程最大的好处。对于刚刚接触面向对象思想的人来说,这些简洁的层级形状示例看起来非常有道理。


而“代码复用”,多年来都是面向对象思想的代名词。我毫不犹豫的接受了这种思想,并且一头扎进刚刚发现的这片新大陆。

“香蕉猴子雨林” 问题


成为面向对象虔诚信徒的我,带着手头的问题,开始构建类层次并编码。一切都还好。
然而,我永远忘不了当我准备利用继承来使用已有类库的那一天,毕竟说好的复用效果就要出现了。我™为这天可等了不少时候了。

一个新项目交到我的手上,我又想起来在自己上个项目中颇为喜爱的一个类。
没问题,复用拯救世界。我只需要把那个类从上一个项目拽出来放到新的项目里就万事大吉啦。
嗯,看起来好像不只需要这一个类。我们还需要这个类的父类。不过,不过,唉,就这样吧。
额,等等,还需要父类的父类,接着还要所有的父类。行行行,我能办到,没问题。

真是太好,现在™编译不过去了。什么鬼?哦,明白了,这个对象包含其他对象,没问题我们连这个对象一起包含进来。
我去,不是吧,还要这个对象的父类,不光是它,所有包含的对象的父类,以及父类的父类。我的天哪。

Erlang之父Joe Armstrong曾经说过:面向对象编程语言的问题在于,冰山一角下暗藏的各种依赖成了他们的负担。你仅仅想要个香蕉,但你不得不连拿着香蕉的大猩猩,以及大猩猩所在的雨林一起拿走。

“香蕉猴子雨林” 问题的解决方案


我可以通过不构建过深的层级结构来解决这个问题。但是,如果说继承是复用的关键机制,如果继承的从深度不够,那同样也会限制复用的效果,不是吗?
没错。
那么那些面向对象编程的脑残粉程序员该怎么办呢?
包含并且委托。这一方法以后会经常见到。

钻石问题

或早或晚,以下问题都会揭露出面向对象编程的丑陋之处——分不清从哪个开始

大多数面向对象编程语言都不支持这种结构,虽然逻辑上并没有问题。那么在面向对象编程语言中支持这种结构有那么难吗?

来看看下面的伪代码:

Paste_Image.png

注意到扫描仪类和打印机类都实现了一个叫做“开始”的方法吗?
所以复印机到底继承了哪个类里面的“开始”方法?扫描仪的还是打印机的?肯定不能是两个都继承了吧。

钻石问题解决方案


答案很简单,不要这么用。
没错,大多数面向对象编程语言不会让你这么用的。
但是,如果我非要这样建模呢?我想复用啊!
那你必须,包含并且委托。

Paste_Image.png

看到了吗,现在复印机类包含了一个打印机的实例和一个扫描仪的实例。它把“开始”方法委托给了打印机类去实现。当然,你也可以轻易的把它委托给复印机类。
这是继承这一核心特性的又一瑕疵。

脆弱基类问题


所以我尽量使我的层级足够浅,而且避免循环继承。嗯这下没有钻石问题了。

嗯,世界还是那么美好一切运行良好,直到。。。
一天,我的代码运转正常,但第二天去停止工作了。那么问题来了,我™没改过代码啊!

嗯,也许是出bug了,等等,不对,确实有什么地方更改了。
但改动的部分不是我的代码。是我继承的基类代码改了。

为毛基类的变化会使我的代码崩溃?
原因在于。。。
想象一下下面的基类(虽然是用JAVA写的,但即使你不是JAVA程序员应该也很容易理解)

Paste_Image.png

看到注释那行代码了吗,就是这行代码的改变影响了我的代码。

这个类有两个接口类:add()和addAll()。add()方法会向数组中添加一个元素,而addAll()方法会通过调用add()方法来把多个元素添加到数组里。
下面是继承类

Paste_Image.png

ArrayCount类是基类Array的具体化。ArrayCount与Array的唯一区别是,ArrayCount会在添加了元素以后再计算一下元素的个数。

让我们来仔细看看这两个类。
基类Array的add方法把一个元素添加到一个本地数组中。基类Array的addAll方法调用本地数组的add方法来把每个元素添加到数组中。

ArrayCount类的add方法调用父类的add方法,并且把count的数目自增1。ArrayCount类的addAll方法调用父类的addAll方法并且把count加上元素的个数。
一开始都没有问题。

 

现在到了关键的一行代码了!注释的这行基类的代码改成了下面这样:

Paste_Image.png

只要考虑到基类的拥有者,方法还是能正常运行。所有的自动测试也能通过。但继承类并不知道是哪个拥有者,是基类的拥有者,还是继承类的拥有者,于是继承类的拥有者被粗鲁的唤醒了。

现在,ArrayCount的addAll方法调用了他的父类中的addAll方法。父类内部会调用已经被继承类覆盖的add方法。会导致循环添加元素的时候,每次循环都让count自增了1,最后又会让count增加上元素的个数。这™就尴尬了。丫算了两次!

这种情况确实存在,继承类的编写者必须清楚基类是如何实现内部的方法的。并且基类有任何变化,必须都同步给他,要不他的继承类会以不可预知的方式崩溃!

啊!这个巨大的漏洞成了悬在继承这一核心特性头上的达摩克利斯之剑,随时威胁着我们所珍视的继承特性的稳定性。

脆弱基类的解决方案


再一次祭出包含和委托大法吧。

通过使用包含和委托,我们从白盒编程(这个不知道的小白同学请自行百度,还有灰盒,国内一般测试用这种说法比较多)变成了黑盒编程。白盒编程,我们必须了解基类的实现过程。

而黑盒编程,因为我们不能通过覆盖基类的方法来改写基类的代码。我们能完全忽略基类的实现过程。我们只需要了解接口就好了。

这个走向有点令人困惑啊。

继承本来是应该实现复用的。
但是面向对象编程并没有使得包含和委托这种繁琐操作更加加单。反而是包含和委托使得继承用起来容易些。。。

如果你跟我的经历差不多,你估计也开始重新思考继承这个玩意了。但最重要的,这应该打击到你对分层分类的信心了。

分层问题

每次我进入一家新公司,我老是纠结怎么给我的公司文档建立目录结构。比如说员工手册。我是应该建立一个叫做文档的文件夹,然后在里面建立一个叫做公司的文件夹?还是应该建立一个叫做公司的文件夹,然后在里面建立一个叫做文档的文件夹?

两种都可以,但是那种是对的?哪种更好?

分类分层的假设是说,存在着一些基类(父类),他们是最基础的,然后他们的继承类(子类)会在他们的基础上更加具体化。而且层级越往下走,就应该越具体。(看看上面的层级形状示例)

但如果父类和子类不断的调换位置,那这个模型很明显有问题。

分层问题的解决方案


麻蛋
分类分层就没有个卵用
那分层到底有什么好处?

包含


如果你看看真实世界,你会发现包含层级(专有)几乎无处不在

但你在真实世界里找不到叫做分类分层的东西。我们先把它放在一边。面向对象范式是以充满对象的真实世界为基础的。但却使用了一个有问题的模型,也就是真实世界里根本找不到的分类分层。

但真实世界确实充满了包含层级。包含层级的一个不错的例子就是你的袜子。你的袜子放在抽屉里,而抽屉是在衣柜里,衣柜又在你的卧室里,卧室又在你的房子里,等等。

你硬盘上的目录也是包含层级的一个例子。他们包含着文件

那我们我们该如何分类?

哈,如果你还想着公司文档,其实我怎么分类都可以。我可以把他们放在叫做文档的文件夹里或者叫做资料的文件夹里。

我分类的方式是贴标签,我是这么给文件贴标签的:
文档公司手册
标签没有顺序或者层级(这下也把钻石问题解决了)
标签类似于接口,你可以给文件关联上多种类型。
但是有这么多毛病的继承核心特性,看起来失败了。
再见吧,继承!

© 著作权归作者所有

共有 人打赏支持
周星_1980
粉丝 12
博文 15
码字总数 28669
作品 0
东城
个人站长
私信 提问
加载中

评论(7)

周星_1980
周星_1980

引用来自“蓝一点”的评论

能把图片都补全吗
不是图片没补全,是少写了一行“现在到了关键的一行代码了!注释的这行基类的代码改成了下面这样:” 。。。6
开源中国股侠
开源中国股侠
能把图片都补全吗
周星_1980
周星_1980

引用来自“梦孤”的评论

这个有点扯
why
周星_1980
周星_1980

引用来自“游客”的评论

没有翻完
太特么长了,等有空再更新后面两个部分
梦孤
梦孤
这个有点扯
游客
游客
没有翻完
gfjhgsfrg
gfjhgsfrg
沙发
时间划过的伤痕叫成长

我要用代码敲出整个世界! 也许刚看这句话的时候,很多人都嗤之以鼻,太自大太高傲了,但这是我梦想也是我目标! 我出生在一个小县城的普通家庭里,经济状况也只能解决温饱,上高中的时候我就没想着...

chrise_
2017/06/21
0
0
再见了,Linq to SQL,我们会想念你的!

再见了,Linq to SQL,我们会想念你的! 从DBML文件中,我学到了太多的东西,不知道为什么微软在推出linq to sql后,不对它进行支持了,可能是一山不容二虎吧,entity frameworks的崛起不是偶...

mcy247
2017/12/06
0
0
C#数组篇讲解

数组是我们经常用到的,我来介绍一下:数组是具有相同类型的一组数据。当访问数组中的数据时,可以通过下标来指明。c#中数组元素可以为任何数据类型,数组下标从0开始,即第一个元素对应的下...

晨曦之光
2012/03/09
84
0
再见2017,2018莫莫继续在!

     大家好,我是你们的莫莫,今天我们不更新任何知识点,今天我想和你们说说我和你们的故事,耽误你们几分钟可以吗?   2016年12月15日,我正式申请并通过这个微信公众号,发送到第一...

UG数控编程
2017/12/31
0
0
Java学习之面向对象与面向过程的比较

前言 Java编程语言是一门面向对象的语言,这与之前的编程语言来说,是一个重大的进步和发展,下面主要来分析一下面向对象和面向过程两者之间的关系。 编程语言的发展 如下图,编程语言的发展...

m18633778874
04/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

不可不说的Java“锁”事

前言 Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率。本文旨在对锁相关源码(本文中的源码来自JDK 8)、使用场景进行举例,为读者介绍主流锁的知识点...

美团技术团队
7分钟前
1
0
ali oss util demo

package com.example.demo;import com.aliyun.oss.OSSClient;import com.aliyun.oss.common.utils.BinaryUtil;import com.aliyun.oss.model.*;import org.slf4j.Logger;import o......

经常把天聊死的胖子
9分钟前
1
0
Windows系统中eclipse修改字体为Courier New

背景:在eclipse修改字体时没有找到Courier New字体; 解决: 1.在计算机地址栏上输入“C:\Windows\Fonts”路径,回车打开Win10字体文件夹。查看是否有Courier New字体;如下图: 2.如果有该...

anlve
9分钟前
1
0
使用hexo做博客网站

hexo有什么用? hexo 可以把md文件生成html静态网页。 hexo官网:https://hexo.io/zh-cn/ 本地安装hexo。 npm install -g hexo-cli#生成blog(名字任意)文件夹,并且在这个文件夹里面初始化...

王坤charlie
9分钟前
1
0
RabbitMQ+PHP 教程四(Routing)用yii2测试通过

开始 在本教程中,我们将为它添加一个特性——我们将只可能订阅消息的一个子集。例如,我们只能够将关键错误消息直接指向日志文件(以节省磁盘空间),同时仍然能够打印控制台上的所有日志消...

hansonwong
13分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部