GTLC全球技术领导力峰会·上海站,首批讲师正式上线! 了解详情
写点什么

面向对象编程的兴衰

2019 年 8 月 02 日

面向对象编程的兴衰

面向对象编程(OOP)并没有消亡。但与过去相比,它确实没有那么普及了。在 90 年代时,有很多面向对象编程相关的教科书和计算机科学课程。它就是“流行趋势”。然而,随着时间的流逝,人们开始意识到,严格的面向对象方法会带来很多问题。这些问题往往会使代码更复杂、更难以理解且更难以测试。


在 90 年代时,有很多面向对象编程相关的教科书和计算机科学课程。它就是“流行趋势”,是计算机的下一个浪潮。如果没有以这种风格编程,那么就是一个糟糕的程序员,或者至少落后于潮流。


当时,计算机科学(CS)专业的学生学习 OOP 的方法非常严格和教条。不仅鼓励实践者以对象和类的形式来构建应用程序,甚至还要求他们从对象和类的角度来考虑问题空间,这种实践方法被称为“面向对象的分析和设计”。


然而,随着时间的流逝,人们开始意识到,严格的面向对象方法会带来很多问题。这些问题往往会使代码更复杂、更难以理解且更难以测试。


事实证明,OOP 更适合某些特定的问题领域。例如,OOP 仍然是构建用户界面(窗口和按钮)最自然的方式 。但是,试图将面向对象技术应用于关系数据库一直是一场灾难。


以下是我观察到的一些问题。


“鸭嘴兽”效应

现实世界并不总是能被整齐地划分成具有明确属性定义的类别。例如,假设我们创建了一个代表动物王国的类层次结构。该类层次结构中既包含爬行动物(冷血、有鳞片、产卵等等),又包含哺乳动物 (恒温、有毛、生育等等),还包含鸟类、两栖动物、无脊椎动物等等。


然而,对于鸭嘴兽,它似乎不属于我们上述定义的任何类别。我们要做什么呢?我们是创建一个全新的类别,还是重新考虑整个分类方案呢?就工作量和程序复杂性而言,这两种方法都会产生显著的成本。


深层次结构

我记得当我还在谷歌工作的时候,有一个 JavaScript 库 goog.ui,它可以用于创建基于 Web 的用户界面。以下是这个库中某个 UI 组件的继承层次结构:


class ToolbarColorMenuButton* inherits from ColorMenuButton  * inherits from MenuButton    * inherits from Button      * inherits from Control        * inherits from Component          * inherits from EventTarget            * inherits from Disposable              * inherits from Object
复制代码


九层的类继承好多。然而,情况还会变得更糟糕。


在这些高层类中,有许多都被只与少数子类相关的方法和属性“污染”了。例如,“Control”类有90多个方法。它具有设置状态的方法,即使特定的子类是无状态的;它有添加和删除子元素的方法,即使控件包含子元素也没有任何意义。


造成这种复杂性的一个重要原因是,该库的作者试图通过将组件放到类层次结构的不同位置来组织组件的不同功能 ,例如,组件是按钮还是滑块,或者组件是否有颜色 。


但实际上,这些不同功能的组件彼此之间无关。咖啡杯是红色的,和它是用陶瓷制成的,基本上是独立的属性。将红色咖啡杯归入“红色物品”的类别,并不比将其归入“陶瓷制品”甚至“家居用品”的类别更正确。任何一个都是任意选择的,因为类别是精神和社会的结构。


在谷歌工作的最后几年里,为了替代 goog.ui,我创建了一个名为“Quantum Wiz”的新用户界面工具包。我们采用的规则之一(以典型的谷歌风格,写成方程式)是:


组合 > 继承
复制代码


简单地说,这句话的意思是:


“更倾向于使用组合(组合是,用更小的构建块来组装组件功能的能力)而不是继承来作为代码重用的手段。”


因此,举例来说,如果一个按钮具有颜色,我们将通过向常规的“按钮”对象中添加一个“颜色”属性或子对象的形式来实现, 而不是再创建一个新的“颜色按钮”类。


由于这一强制要求,新工具包的类层次结构相对较浅,如果我没记错的话,只有两三层。而且它更容易理解和使用,也更强大了。


对象并不真实

Buckminster Fuller 曾经说过:“there are no things”。他的意思是,事物之间的区别很大程度上是人类的偏见。


例如,我所坐的沙发是被分子力束缚在一起的原子集合。然而,这些原子也会受到房间内其他物体的影响,如地毯、茶几、甚至是房间内的空气。沙发本身由各种部件组成,如纺织物、木材、金属弹簧等,这些部件也受到分子力的约束。沙发是一个对象,还是很多个对象?也许只有一个对象 ,即整个宇宙。


由于人类的视觉和触觉在很大程度上仅响应表面属性,如颜色和纹理 。我们更倾向于基于表面来对世界进行分类。相反,想象一下,如果我们能够直接感知周围物体内的分子组成。我们可能会看到一个“铜”对象,代表房子里的所有布线和管道;一个“氮”对象,代表房子里的气体;一个“水”对象;一个“木头”对象等等。


Fuller 的观点是,我们将世界“解析”为离散的“事物”的能力是任意的,与其说是物理现实,不如说是人类心理的反映。


因为面向对象的继承是将事物组织成类,所以它不能很好地模拟现实世界;它只能很好地模拟人类思考现实世界的方式。


方法也不真实

我记得大约 20 年前,一位软件供应商的技术代表试图向我公司的工程人员解释 OOP。他试图论证面向对象是一种模拟现实世界的方式,并给出一个类似咖啡杯的例子。他说这个杯子可能有一个“drink()”方法。


我记得我对此反应强烈,我认为他完全是胡说八道。物理对象,除了构建它的特定目的之外,还有许多用途。我可以用咖啡杯作为镇纸或门挡;这是否意味着它应该有一个“holdDownPapers()”或“keepDoorOpen()”方法呢?我可以将它用作武器、玩具或艺术品。我甚至可以将杯子摔成碎片,或将它研磨成粉末,然后以创造性的方式使用残余物。


内部逻辑 vs 外部逻辑

严格 OOP 风格的一个原则是,永远不可能从外部变更对象的内部状态。任何变更对象状态的业务或应用程序逻辑都必须作为对象本身的方法实现。因此,举例来说,如果要删除文本框中的所有文本,就不能简单地进行如下设置:


textField.value = ""; // 设置成空字符串
复制代码


这将违反规则。相反,我们必须这样设置:


textField.clear(); // 清空文本框的内容
复制代码


对于简单的操作来说,这很好。但是它很容易被带偏,特别是当正在处理的对象之间具有复杂关系时。


有时候,如果算法存在于任何对象之外会更好。这确实是一个值得强调的问题:对于这个问题集,我们更关心名词还是动词呢?


下面是一个具体的例子:最近我开始研究编译器(编写编译器是我的一个爱好;作为一名游戏开发人员,我发明了许多脚本语言)。在过去,当我编写编译器时,会采用非常严格的 OOP 方法来设计内部数据结构。有各种表示抽象语法树、表达式图、类型等的类层次结构。


通常,编译器通过一系列逐步执行或“传递”来处理这些数据结构,每一步的输出将作为输入传递到下一步中。


在过去,我倾向于按照推荐的 OOP 风格,为每个操作中的每个对象设置一些逻辑。这样做的结果很糟糕,添加得操作步骤越多,对象就变得越复杂。


更糟糕的是,它还使得为这些对象编写单元测试变得更困难。这些复杂的对象在创建之前需要大量的基础设施。这意味着为了测试这个对象,我必须创建大量的脚手架来满足所有的前提条件。


因此,我的测试覆盖率往往很低,因为编写测试是一项累人的工作。


最近,我采取了不同的方法。在我最新的编译器中,所有这些内部数据结构都是“哑”的,这意味着它们所做的只是保存数据,而不再是其他的了。所有用于操作和转换对象的代码都在这些对象的外部实现。


这对代码组织结构有很大的好处。每个算法都集中在一个地方,而不是分散在一堆源文件中。当我想测试某个给定的编译器操作时,我可以轻松地创建一些示例对象并将其提供给该操作。因此,我可以更容易得编写测试了,这意味着我可以编写更多的测试来提升测试覆盖率。


关系数据库

前面我提到了,以面向对象的方式处理关系数据库是有问题的。对象关系映射(ORM)被一位评论家称为“越南的计算机科学”。(注意:那篇文章很长,深奥难懂,而且有一定的倾向性。)


我的总体感觉是,在处理大数据时,不应将记录视为“对象”。关系数据库非常强大,但它们提供的强大功能并不是“类似对象”的。我更倾向于认为关系数据流是一种流体,在里面,我们可以使用代数运算来分割、转换、组合数据。


函数式编程

在过去十年左右的时间里,人们越来越关注函数式编程(FP)了。与 OOP 一样,函数式编程不仅仅是一件事,而是一组风格原则的集合。然而,它的要点是,OOP 侧重于与对象的交互或通信,而 FP 则侧重于对象的转换。通过“转换”,我的意思是获取某个对象,并将它传递给一个函数,结果将是一个全新的对象,这代表对输入进行了某些转换。原始对象要么被保留,要么被丢弃,但它不会以任何方式被修改或变更。


在我自己的编程过程中,我更喜欢使用“混合”的方式,在某些地方使用 FP 技术,而在其他地方使用 OOP 技术。 对于某些类型的问题来说,使用 FP 会事半功倍,而另一些问题,使用 OOP 则是明智的选择。


我知道很多 FP 爱好者都是“纯”函数式语言的狂热拥护者,在纯式函数语言中,所有对象都是不可变的,并且只能被转换,而不能被修改。然而,我发现纯方法往往会将一些相对简单的编程实践变成了谜题,我的意思是一些聪明而非显而易见的技巧,它们对喜欢脑筋急转弯的人很有吸引力,也很有趣,但是对其他人来说完全是不可理解的。


因此,我更倾向于在我认为有意义的地方使用 FP,并且以普通程序员阅读我的代码就能理解的方式。如果我想做一些聪明的事情,我会写一篇长篇评论来解释我所做的事情以及它是如何工作的(这满足了我的炫耀需求,我一直认为编程应该是一门表演艺术。)


总结

因此,面向对象编程已经今非昔比了。它仍然是一个很好的工具,值得学习。但它已经从基座上被拆除了,你很难看到有人像 25 年前那样以宗教般的狂热来吹捧它了。


原文链接:


https://medium.com/machine-words/the-rise-and-fall-of-object-oriented-programming-d67078f970e2


2019 年 8 月 02 日 08:0925760
用户头像

发布了 137 篇内容, 共 56.0 次阅读, 收获喜欢 348 次。

关注

评论 6 条评论

发布
用户头像
没有认真读完全文,不过文章的个别观点确实有待研讨。
2019 年 08 月 26 日 09:33
回复
用户头像
面向对象可以有很多角度去谈的,本文以面向对象编程来讲。暂不评论该文章,先提供一点背景知识。实际上面向对象就是个形容词,那么这个词最早出现在哪里呢?最早大概1970年左右,已经提出了“面向对象系统”这个概念,面向对象系统有两层含义,第一,系统是由对象组成的;第二,对象之间可以互相发消息。从这个角度来讲这两点才是“面向对象”的第一义,后来的封装、继承、多态则是非第一义,或者称为最佳实践。
2019 年 08 月 26 日 09:32
回复
用户头像
这个例子就写的不对,怕不是作者对分析的方法论存在什么误解。否则怎么可能得到杯子有drink谓语的结论。杯子作为容器只应该包含杯子自己的属性和状态,最多增加一个包含物的ref,drink是人的上层业务过程,操作了杯子、水和人的嘴。

所以有一种说法叫做DDD本质就是把OO做对,实际上面为什么你的系统不适合微服务、不适合敏捷等等,本质就是你压根没做对OO。然后你却得出结论,OO不好用、不正确。这是无能的基本标志。
2019 年 08 月 12 日 09:28
回复
用户头像
非常反感绝对化地讨论问题。OOP的核心思想之一就是模块划分,之上是对象划分。OOP的思想绝对不会主张把drink方法加入到杯子对象上。模块或对象的划分是OOP开发的基本功,作者对OOP的核心基础思想都没有掌握并以此举例是可笑的。OOP的一切皆对象一切皆接口不是教条地要你只能使用继承的方法,那些设计模式什么的都学习理解了吗?要说OOP的问题,我认为是掌握难度比较大,没有多年的实践很难掌握。但这其实不是OOP的锅,是所有编程都面临的问题,如何解耦,如何划分模块或对象,如何易于维护,如何拥抱变化。
2019 年 08 月 07 日 20:12
回复
用户头像
C++的标准库设计的就很漂亮。像容器类vector map等以及字符串类string。它们显然是一个个class。但是使用起来又不局限于纯面向对象。算法、容器和迭代器完美的融合在一起。
2019 年 08 月 05 日 11:17
回复
用户头像
对于有些观点不敢苟同
2019 年 08 月 02 日 12:36
回复
没有更多了
发现更多内容

《迅雷链精品课》第七课:以太坊数据存储分析

迅雷链

区块链

原创 | 使用JPA实现DDD持久化-数据库连接配置:persistence.xml

编程道与术

Java hibernate 编程 mybatis jpa

智慧园区管理平台app系统开发,智慧楼宇系统搭建

13530558032

纷享销客罗旭:拐点下的中国SaaS

ToB行业头条

SaaS

容器化时代到来!跳转机分配问题终于“有救”了

华为云开发者社区

容器 镜像 网络

都是“算法”惹的祸,字节三面处处坑,我的offer要凉了?

Java~~~

字节跳动 编程语言 算法和数据结构 面试数据结构与算法

区块链、AI与大数据加持,电子合同更安全、效率更高

CECBC区块链专委会

区块链 大数据

原创 | 使用JPA实现DDD持久化-只要O,忘记R & Maven配置

编程道与术

Java hibernate 编程 mybatis jpa

三分钟带你搞懂分布式链路追踪系统原理

Java架构师迁哥

高性能网关原来是这样设计出来的

周老师

Java 编程 程序员 架构 面试

区块链防伪溯源平台搭建,助力企业品牌安全体系升级

13530558032

架构师训练营 1 期 -- 第十周总结

曾彪彪

极客大学架构师训练营

《华为数据之道》读书笔记:第 3章 差异化的企业数据分类管理框架

方志

数据中台 数据仓库 数据治理 元数据

区块链医疗应用场景有哪些?区块链医疗解决方案

13530558032

快三走势分析判断技巧《走势图预测》

陈北

SELinux 安全上下文配置

性能测试界“网红”云性能测试服务,了解一下?

华为云开发者社区

CloudTest 沙箱实验 云性能测试

为什么程序员不做外包

Java架构师迁哥

论“万剑归宗”思想对开发设计的一点“肤浅”作用

八苦-瞿昙

随笔杂谈 设计实践

快三怎么看走势技巧《如何看走势图》

陈北

php

原创 | TDD工具集:JUnit、AssertJ和Mockito (二十七)运行测试-在构建工具中运行测试

编程道与术

Java 编程 TDD 单元测试 JUnit

数字人民币快来了,但多数人可能会有四大误解

CECBC区块链专委会

数字人民币

IPFS四币连发系统开发技术(四币循环模式)

薇電13242772558

区块链 数字货币

接口请求(get、post、head等)详解

测试人生路

HTTP

打工人、打工魂、高效MES助力打工者都是人上人

Marilyn

敏捷开发 快速开发 MES系统

.NET5发布,这个微软“全家桶”会是.NET的春天吗?

力软.net/java开发平台

.net

架构师训练营第 1 期-week10

习习

数字经济发展势头强劲

CECBC区块链专委会

数字经济

智慧警务大数据可视化平台智慧公安警务研判系统开发

13530558032

大厂都是怎么用Java8代替SimpleDateFormat?

Java架构师迁哥

理解三值逻辑与NULL,你离SQL高手更近了一步

华为云开发者社区

sql null 逻辑

Alibaba最新《Java架构核心宝典》限时开放下载,互联网主流技术详解总结,提升技术能力的必备宝典!

Java架构之路

Java 程序员 架构 面试 编程语言

DNSPod与开源应用专场

DNSPod与开源应用专场

面向对象编程的兴衰-InfoQ