【CSDN 编者按】作为一种程序设计思想,OOP 在最初诞生之际就收到了广大开发者的喜爱。但是在技术革新日益实践过程中,不少人发现面向对象的设计会使代码复杂化,难以理解而且难以测试,对此,后来有网友更是将 OOP 称之为是反模块化、反并行的,从而开启了一波又一波的吐槽模式。而论 OOP 在各个领域中的应用时,其是否真的有想象中那么糟糕?接下来,本文将带领大家一读 OOP 的兴衰成长史。
以下为译文:不,面向对象编程(OOP)并没有消亡。但它远没有以前那么流行了。
我记得当时在90年代,关于面向对象编程的教科书和计算机科学课程很多。当时那就是“风口”,下一波潮流。如果你没有以那种方式编程,你就不是一个优秀的程序员,或者至少是可悲地落后于时代发展潮流了。
当时,CS专业的学生以非常严格和教条化的方式学习OOP。从业者们不仅被鼓励以对象和类的形式构建他们的应用程序,甚至被认为应该根据对象和类来考虑问题空间。这样的做法被称为“面向对象的分析和设计”。
然而,随着时间的推进,人们开始意识到严格的面向对象方法会带来许多问题。这些问题往往会使代码复杂化,难以理解而且难以测试。
事实证明,OOP在某些问题领域确实比其他方法更出色。例如,OOP仍然是构建用户界面(窗口和按钮)的最自然的方式。但是,试图使面向对象适应关系数据库一直以来都简直是一场灾难。
以下是我所观察到的一些问题:
“鸭嘴兽”效应
现实世界并不总是能被整洁地划分为具有明确属性定义的类(class)。例如,假设你创建了一个代表动物王国的类层次结构。现在,有爬行动物——冷血,有鳞片,卵生等等。还有哺乳动物——温血,有绒毛,胎生。以及鸟类,两栖动物,无脊椎动物等等
然后鸭嘴兽出现了,它似乎不适合你的任何类别。你该怎么做呢?你是创建一个全新的类别呢,还是重新考虑整个分类方案?这两种方法在工作量和程序复杂性方面都会产生巨大的成本。
(感谢Anselm Hook创造了“鸭嘴兽效应”一词。)
深层次结构
我记得我在谷歌工作时,当时我们有一个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种方法(method)。它具有设置状态的方法,即使特定的子类是无状态的; 它有添加和删除子元素的方法,即使对control来说子元素没有意义。
这种复杂性的一个重要原因是,该库的作者试图组织组件的不同方面——例如组件是按钮还是滑块,或者它是否有颜色——并通过将它们放入类的不同层次来实现这一点。
但实际上,这些不同方面彼此之间无关。咖啡杯是红色的,和它是由陶瓷制成的,这是两个独立的特性。将红色咖啡杯划入“红色物品”类别,还是将其放入“陶瓷制品”甚至“家居用品”类别中都是同样正确的。任何一个选择都是任意的,因为类别是由人头脑中的反映和社会结构决定的。
在Google工作的最后几年里,我创建了一个名为“Quantum Wiz”的新用户界面工具包,旨在替代goog.ui。我们采用的规则之一(以典型的Google风格,以方程式编写)是:
composition > inheritance
用直白的英语解释的话,这说的是:
“更偏向于采用组合的思路——也就是说,能够用更小的构建块来组装组件的功能——而不是继承作为代码重用的手段。”
因此,举个栗子,如果按钮有颜色,你将采用常规的“Button”对象(object)并向其添加“Color”方面(aspect),作为属性或子对象,而不是创建一个新的“Color Button”类。
作为这项任务的结果,新工具包的类层次结构相对较浅,如果我没记错的话,只有两三个级别。而且更容易理解和使用,也更强大。
(感谢Malte Ubl向我介绍了组合大于继承的概念。)
对象不是真实的
Buckminster Fuller曾经说过:“事物并不存在”。他的意思是,事物之间的区别主要由人的偏见导致。
例如,我坐的沙发是由分子力束缚在一起的原子的集合。然而,这些原子也会受到房间内其他物体的影响——地毯,茶几,甚至是房间内的空气。沙发本身由各种部件组成——织物,木材,金属弹簧等等——它们也受到分子力的约束。那么沙发是一个对象,还是很多对象?也许世上只有一个对象——即我们所在的这个宇宙。
因为人类的视觉和触觉在很大程度上只对表面属性有响应——比如颜色和质感——我们倾向于基于表面现象对世界进行分类。相反,想象一下,如果我们能够直接感知周围物体内的分子组成。我们可能会看到一个“铜”对象,代表着房屋中的所有布线和管道,一个“氮”对象,代表着房间的气体,一个“水”对象,一个“木头”对象,等等。
Fuller的观点是,我们将世界“解析”为离散的“事物”的能力是任意的,这更多地反映了我们的人类心理而不是物理现实。
因为面向对象的继承涉及将事物组织成类,所以它不能很好地模拟现实世界; 但它能很好地模拟人类思考现实世界的方式。
方法(method)也不真实
我记得大约二十年前的一段小插曲,一位软件供应商的技术代表试图向我司的工程人员解释OOP。他试图争辩说,面向对象是一种模拟现实世界的方式,他给出的例子就像上面说的咖啡杯那种。他说杯子可能有个“drink”的方法。
我记得的是,我对此有一个非常强烈的反应——我认为他所说的完全是胡说八道。除了它的特定目的之外,一个物理对象可以有许多用途。我可以用咖啡杯作为镇纸或门挡; 这是否意味着它有一个“holdDownPapers”或“keepDoorOpen”方法?我可以将它用作武器,玩具或艺术品。我甚至可以将杯子碎成碎片,或将其研磨成粉末,并以创造性的方式使用其残余物。
(我认为这个可怜的家伙对我的反驳感到吃惊。)
内部逻辑与外部逻辑
严格的OOP风格的一个原则是,永远不可能从外部改变对象的内部状态。任何改变对象状态的业务或应用程序逻辑都必须作为对象本身的方法实现。因此,举个栗子,如果要删除文本字段中的所有文本,则不能简单地进行:
textField.value = ""; // Set to empty string
这将违背规则。相反,你不得不这样:
textField.clear; // Clear the content of the field
对于简单的操作,这没关系。但是这很容易过火,特别是正在进行的操作处理的是不同对象之间的复杂关系时。
有时候,如果算法独立于任何对象之外,反而更好。这真的是一个重要的问题:对于这个问题集,你更关心名词还是动词?
下面这是一个具体的例子:最近我开始研究编译器(编写编译器是我的一个爱好; 在我做游戏开发的时候,我发明了许多脚本语言)。在过去,当我编写编译器时,我会采用非常严格的OOP方法来设计内部数据结构。有各种类层次结构表示抽象语法树,表达式图,类型等。
通常,编译器通过一系列阶段或“传递”来处理这些数据结构,每一步的输出被送到下一步的输入中。
在过去,我倾向于按照推荐的OOP样式为每个操作中的每个对象设置一些逻辑。这带来不好的后果,当我添加更多步骤时,对象变得越来越复杂。
更糟糕的是,这使得给这些对象写单元测试异常困难。这些复杂的对象在被创建出来之前就需要大量的基础结构。这意味着为了测试一个对象,我必须创建大量的脚手架来满足所有先决条件。
结果,我的测试覆盖率往往很差,因为编写测试是一项耗费大量精力的工作。
最近,我采取了另一种方法。在我最新的编译器中,所有这些内部数据结构都是“傻瓜型”的,意思是说它们所做的只是保存数据而已,没有别的。用于操作和转换对象的所有代码都在这些对象的外部。
这对代码的组织有很大的好处。每个算法都集中在一个地方,而不是分散在一堆源文件中。当我想测试一个特定的编译器操作时,我可以轻松地创建一些示例对象并将其提供给该操作。因此,我的测试写起来更容易了,所以我就能写更多的测试了,从而就能有比以前更好的测试覆盖率了。
关系数据库
前面我提到过,以面向对象的方式处理关系数据库会有问题。对象关系映射(ORM)被一位评论家称为计算机科学领域的越战。(警告——那篇文章很长,很深奥,而且有点倾向性。)
我的大致感觉是,在处理大数据时,你不应将你的记录视为“对象”。关系数据库非常强大,但它们提供的强大功能并不是非常“类似对象”。我倾向于认为关系数据流更像流体,你可以使用代数运算的方式来划分,转换和组合数据。
(感谢Guido Van Rossum指出上面关于ORM的文章的链接。)
函数式编程
在过去十年左右的时间里,人们越来越关注函数式编程(FP)。与OOP一样,函数式编程不仅仅是单纯的一件事物,而是一套整体的风格上的原则。然而,它的要点是,虽然OOP专注于与对象进行交互或通信,但在FP中,重点在于对它们的转换。这里的“转换”,意思是你获取一些对象,将它传递给一个函数,结果是一个全新的对象,代表着对输入内容所做的一些转换。原始对象要么被保留,要么被丢弃,但不会以任何方式被更改或修饰。
在我自己的编程过程中,我更喜欢“混合”方法,在某些地方使用FP技术,而在其他地方使用OOP技术。在解决某些问题上FP确实能大放异彩,但也有另一些问题上OOP是更明智的选择。
我知道很多FP爱好者都热衷于“纯粹”的函数式语言,其中所有对象都是不可变的,并且只能被转换,而不能被修改。然而,我发现纯粹的方法倾向于把某些相对简单的编程实践变成谜题。我的意思是一些聪明的,但不那么显而易见的技巧,这能吸引那种喜欢脑筋急转弯的人,但是对其他人来说却是完全无法理解的。
因此,我倾向于在合理的范围内使用FP,使阅读我代码的普通程序员都能理解。如果我想做任何抖机灵的事情,我会写一篇长篇评论来解释我所做的事情,以及它是如何work的(这满足了我炫耀的心理——我经常认为编程应该是一种表演艺术。)
所以,面向对象编程不再有昔日的辉煌了。它仍然是一个很好的工具,仍然值得学习。但它已跌下神坛,你很难再看到有人能像 25 年前那样,以宗教般的狂热来吹捧它了。
原文:https://medium.com/machine-words/the-rise-and-fall-of-object-oriented-programming-d67078f970e2
本页共69段,4994个字符,12449 Byte(字节)