第七章:信仰之战:面向对象(OOP) vs. 函数式编程(FP)

欢迎回来,时空旅行者。在上一部分,我们建立了“对象工厂”(C++/Java)1,甚至还拿到了23份标准“蓝图”(设计模式)1。我们以为自己已经驯服了“代码山”。但我们只是把混乱隐藏在了漂亮的盒子后面。

现在,我们将要见证一场长达三十年的宗教战争。这场战争的激烈程度堪比“Vim vs. Emacs”,但赌注要大得多。这是一场关于如何管理复杂性的根本性“信仰之战”。

为什么会有“战争”?因为面向对象(OOP)和函数式编程(FP)都是为了解决同一个“原罪”而诞生的。这个“原罪”,我们在第四章1见过,就是C语言那种“过程式编程”带来的混乱。在那个世界里,数据(变量)和操作数据的函数(流程)是分离的、不受约束的,它们在全局状态的“变量海”里野蛮生长,导致了灾难2

这场战争的核心问题是:如何驯服“状态”(State)这个恶魔?

  • OOP的方案: 把恶魔(状态)关进一个坚固的盒子里(对象),然后只给它开几个小窗户(方法)对外交流。
  • FP的方案: 假装恶魔不存在。如果可能的话,直接一枪毙了它(不可变性)。

准备好你的爆米花。这场面曾经非常难看。

第一部分:原罪(以及为什么我们需要一位救世主)

1.1 过程式编程的原始汤

让我们先回到我们的“舒适区”:过程式编程(Procedural Programming)。在C和早期BASIC的世界里1,我们没有“范式”;我们只有一个闪烁的光标和一份即将到期的最后通牒。

这个世界的决定性特征就是“混乱”。代码的维护者,就像一个考古学家,试图从一堆纠缠不清的电线中理清思路。

“意大利面条”怪物

我们在第三章1谈论过GOTO如何制造了“意大利面条式代码”(Spaghetti Code)。但这只是问题的一半。GOTO制造了流程上的混乱。而一个更阴险的怪物,则在数据上制造混乱。

这个怪物,就是全局状态(Global State)。

在过程式编程中,数据是“全局”的,任何函数都可以随时随地读取和修改它2。这就像一个社区的公共冰箱。你放进去一个三明治,五分钟后回来,发现三明治没了,里面多了一只袜子。是谁干的?什么时候干的?你无从得知。

这就是维护一个大型过程式代码库的日常体验。你在一个函数里修改了一个全局变量 g_bIsUserLoggedIn,却神秘地导致了三千行代码之外的另一个模块(比如支付模块)彻底崩溃3。这种“幽灵般的远程行动”(Spooky Action at a Distance)让调试变成了一场噩梦。

“雪花”隐喻

Reddit上的一位天才诗人(或者说,是一个被伤透了心的程序员)4,用一个“雪花”的比喻完美地描述了这个问题:

“每个程序员,当夜深人静、独自一人的时候,都会写出一段代码。这段代码是完美的‘小雪花’4。它命名规范、逻辑简洁、优雅高效。

然后,星期五,老板告诉你,下周二之前要交付六百片雪花。所以你开始作弊,你复制粘贴,你把几片雪花粘在一起。接着,你的同事为了赶工,把你的雪花拿去‘融化’了一半。

最后,所有人的雪花都被倾倒在一个不知所谓的巨大土堆上,有人在上面靠了一幅毕加索的画,因为没人想看到那堆混杂着猫尿、在日光下融化的、破碎的雪花4。”

这就是规模化(Scale)的诅咒。你的“完美小雪花”(单个函数)在与数百个其他函数共享同一个混乱的“全局状态”时,必然会崩溃5。我们迫切需要一种新的哲学。

1.2 混乱的“双子恶魔”

我们所面对的这场混乱,有两个源头:

  1. 不可预测的流程: 以GOTO为首的无限制跳转,让我们无法在静态阅读代码时,预测它在运行时的执行路径1
  2. 不可预测的数据: 以“全局变量”为首的共享可变状态,让我们无法在任何时刻,预测一个变量的当前值2

一个程序的核心就是“流程控制 + 数据控制”。而在过程式世界里,这两者都处于无政府状态。这不再是“Bug谷”1;这是“Bug地狱”。

为了解决这个“双子恶魔”,人类的智慧分裂成了两个截然不同的宗教。

  • 宗教A(OOP): 让我们通过建造高墙来驯服混乱。我们将数据(状态)锁在坚固的盒子里,严格控制谁可以访问它。
  • 宗教B(FP): 让我们通过放逐来消除混乱。我们将彻底禁止“可变状态”的存在。如果数据永远不能被“改变”,那么“不可预测的改变”自然就消失了。

第二部分:OOP福音书(用“盒子”驯服混乱)

于是,面向对象编程(OOP)登场了。它闻起来像新车的塑料味,手里拿着一份企业销售合同。它向饱受折磨的C程序员们承诺了一个更有序、更文明的世界。它的“福音书”基于四大信条,或者说,“四大支柱”6

2.1 新宗教的四大支柱(和它们的Meme版解释)

让我们用(从研究中扒出来的)现代Meme和滑稽类比,来翻译一下这些当年听起来无比庄严的术语。

  1. 封装 (Encapsulation)

    • Meme版解释: “全自动咖啡机”7
    • 解说: 这是OOP的核心卖点。你(程序员A)不需要知道咖啡机(对象)内部是如何工作的。你不用关心水温、豆子研磨度或气压(私有状态private)。你只需要按下那个标着“拿铁”的按钮(公共方法public),然后咖啡就会出来7。它把“状态”和“操作状态的行为”捆绑在一起,锁进一个盒子里,保护数据不会被外部的野蛮人(程序员B)随意篡改8
  2. 抽象 (Abstraction)

    • Meme版解释: “躲在柱子后面的人”9
    • 解说: 封装说“我把复杂的实现藏起来”;抽象说“我只给你看一个必要的、简化的界面”9。你只需要看到柱子后面那个人在跟你招手(接口interface)就行了。你不需要(也不想)知道他柱子后面的部分(实现implementation)是否穿着裤子。
  3. 继承 (Inheritance)

    • Meme版解释: “我爸是李刚”(或者“龙生龙,凤生凤”)10
    • 解说: 这是一个关于“代码复用”的承诺。它基于一个“is-a”(是一个)的关系11。Dog is-a Animal12。Man is-a Person10。因此,Dog可以自动“继承”Animal的所有(非私有)属性和方法,比如eat()sleep()。这听起来非常直观,非常符合生物学分类,不是吗?(剧透警告:这个柱子是纸糊的,里面全是白蚁。)
  4. 多态 (Polymorphism)

    • Meme版解释: “万能遥控器”12或“会跳舞的机器人”13
    • 解说: 这是四大支柱里最坚固、最有用的一根。它意味着“多种形态”。你有一个统一的接口(遥控器上的“播放”按钮),但它对不同的对象(电视机、DVD播放机、音响)会产生不同的行为12。在代码里,你有一个Animal类型的数组,你遍历它并调用animal.speak()。Dog对象会“汪汪”叫,而Cat对象会“喵喵”叫12

为了让你(我们这种老派程序员)快速理解,这里有一份“营销材料 vs. 现实”的速查表:

四大支柱 营销口号(Meme版) 资深老鸟看到的“小字说明”
封装 “全自动咖啡机” 7 “保证数据安全……直到某个不孝子类(subclass)从地基挖地道进来。”
抽象 “柱子后的人” 9 “把烂摊子藏起来了……但烂摊子还在那里。”
继承 “Dog is-a Animal” 12 “Square is-a Rectangle……直到它翻车。”(LSP警告)
多态 “万能遥控器” 12 “唯一真正有用的支柱,但‘继承’总想抢它的功劳。”

隐喻背后的致命缺陷

请注意,这些类比本身就暴露了OOP的致命缺陷。

“咖啡机”(封装)和“柱子后的人”(抽象)的类比是强大的。它们描述的是“边界”和“隐藏复杂性”,这是坚实的工程原则。

但“Dog is-a Animal”(继承)的类比是脆弱的。它描述的是“分类学”,一个生物学或哲学上的概念10

在实践中,继承(分类学)恰恰是封装(边界)的头号敌人。子类(Dog)对于父类(Animal)来说,并不是一个“黑盒”。子类拥有“受保护的”(protected)访问权限,它实际上是在父类的边界之内的。

OOP的营销手册上写着四大支柱,但其中一个(继承)正积极地试图推倒另一个(封装)。这个根本性的内部矛盾,就是日后所有灾难——“脆弱的基类”14和“大猩猩-香蕉”15问题——的震中。

2.2 先知的哀叹(艾伦·凯到底在想什么?)

最讽刺的部分来了。那个发明了“面向对象编程”这个词的人——艾伦·凯(Alan Kay)——打心底里鄙视我们今天称之为“OOP”的东西(尤其是C++和Java)16

这就像你发明了“摇滚乐”,结果全世界都以为你说的是“二人转”。

艾伦·凯的原始愿景,深受生物细胞的启发17,并在Smalltalk语言中实现18,根本不是关于“类”和“继承”的。他的核心思想是:

  1. 消息传递 (Messaging)
    对象之间不“调用方法”。它们像生物细胞一样,互相传递异步消息17。一个对象(比如按钮)只是“通知”另一个对象(比如窗口):“嘿,我被点击了。”19 至于窗口收到这个消息后是关闭、变色还是爆炸,那是窗口自己的事。这是“通知”,而不是“命令”。

  2. 绝对封装 (Total Encapsulation)
    不只是封装数据。艾伦·KAY想封装的是整个运行环境,包括编程语言和硬件20。他设想的每个“对象”,都像一台微型、独立的计算机。

  3. 极限后期绑定 (Extreme Late-Binding)
    所有的决策都尽可能推迟到运行时再进行21

我们得到了什么?

C++和Java彻底歪曲了他的意思。它们没有实现“消息传递”,而是实现了“早期绑定的方法调用”。它们把OOP变成了“类(Class)导向的编程”21,或者用艾伦·凯的原话说,只是“更智能的数据结构”22

真正的OOP是“微服务”

这就是最精彩的“时空笑话”。

  • 艾伦·凯的“真OOP”愿景是什么?
    独立的实体(对象)、完全封装(不共享状态/数据库)、通过异步消息(他甚至提到了“URL”)20进行通信、与语言无关。

  • 我们今天“时髦”的“微服务架构”是什么?
    独立的服务(Service)、完全封装(每个服务有自己的数据库)、通过异步消息(HTTP/JSON/gRPC)进行通信、与语言无关。

它们是完全相同的概念。

整个行业采纳了“假OOP”(C++/Java巨石应用),制造了“假OOP”的史诗级灾难(紧耦合)。为了解决这些灾难,我们(在40年后)发明了“微服务”,却意外地“重新发明”了“真OOP”。

OOP的解药……竟然是OOP。这种宇宙级的黑色幽默,足以让我们这些老家伙笑出眼泪。

第三部分:大分裂(来自长老的咆哮与OOP的耻辱柱)

这个“假OOP”宗教的裂痕很快就出现了。计算科学的先知们(我们的老同事)开始(非常大声地)咆哮。

3.1 长老的咆哮(OOP审判大会的A、B、C号证物)

证物A:艾兹格·迪杰斯特拉 (Edsger Dijkstra) 的诅咒

就是那位在第三章1绞杀了GOTO的荷兰先知。他对OOP的评价同样刻薄:

“面向对象编程是一个极其糟糕的想法,它只可能起源于加利福尼亚。” 23

(更有趣的是,这个笑话本身就是错的。OOP起源于挪威(Simula语言)24,但加州(Xerox PARC)确实将其发扬光大了。)

迪杰斯特拉的真正愤怒在于,他认为OOP是一种“蛇油”(snake oil)25,一种分散注意力的营销噱头,它让程序员们沉迷于构建拙劣的模型,而忽视了真正严肃的、数学化的“形式化验证”。

证物B:莱纳斯·托瓦兹 (Linus Torvalds) 的暴怒

Linux和Git的创造者,一个以“脾气好”而闻名的芬兰绅士(不)。当被问及为什么Git不用C++时,他回复了一封堪称“咆哮艺术”的邮件:

“你(提问者)才充满了狗屎(bullshit)。C++是一门糟糕的语言……它吸引了大量不入流的程序员,以至于用它制造出彻头彻尾的垃圾变得轻而易举……所以对于Git这样以效率为首要目标的项目,C++的‘优势’就是个巨大的错误。而我们能顺便惹毛那些看不清这一点的人,则是一个巨大的额外优势。” 26

他的理由非常务实:C++“在背后隐藏了内存分配”27,“异常处理机制从根本上就是坏的”(尤其对内核代码而言)27,它“只是在C语言之上添加了一些渣滓”28

证物C:乔·阿姆斯特朗 (Joe Armstrong) 的“香蕉”

Erlang语言的创造者乔·阿姆斯特朗,贡献了编程史上最伟大的隐喻。当被问及OOP的缺点时,他说:

“面向对象语言的问题在于,它们总是携带着一堆隐式的环境。你想要一根香蕉,但你得到的却是一只拿着香蕉的大猩猩,以及大猩猩身后的整片丛林。” 15

这个比喻完美地控诉了“紧耦合”(Tight Coupling)和“继承”的失败。

你只是想在你的小程序里用一下Banana类。但不幸的是,Banana类 extends Gorilla(继承自大猩猩类),而Gorilla类又 extends Jungle(继承自丛林类)。现在,你那个只想显示“Hello World”的小程序,为了得到这根香蕉,被迫 import (导入)了整个热带雨林生态系统15

3.2 OOP耻辱柱(他们到底在咆哮什么?)

这些长老们不是在无能狂怒。他们是在回应OOP范式中真实存在的、教科书级别的设计缺陷。

耻辱柱一:“正方形‘是’一个矩形”的谎言(里氏替换原则, LSP)

这是对“is-a”继承的经典处刑。它揭示了“里氏替换原则”(Liskov Substitution Principle, LSP)的违反29

  • 逻辑是这样的:
    1. 一个Rectangle(矩形)类的“契约”是:它的setWidth(10)setHeight(5)方法是独立的29
    2. 从数学上讲,一个Square(正方形)is-a Rectangle(是一个矩形)30。所以我们让Square继承Rectangle
    3. 但是,为了维持自己“正方形”的特性,Square类必须违反父类的契约。当你调用setHeight(5)时,它必须同时强制将Width也改为5 29
    4. 灾难发生: 一个函数开心地接收了一个Rectangle(它以为的)。但你传进去一个Square。函数调用rect.setWidth(10),然后调用rect.setHeight(5)。它期望rect.getArea()返回50 (10 * 5)。
    5. 它得到了25 (5 * 5)。 29
    6. 程序崩溃,客户尖叫,项目经理秃顶。
  • 结论: 基于“分类学”(它是什么)的继承是一个陷阱。继承必须基于“行为学”(它做什么)31

耻辱柱二:脆弱的基类(The Fragile Base Class)

这是证明“大猩猩-香蕉”问题的噩梦场景14。它证明了封装(在继承面前)就是个笑话。

  • 逻辑是这样的:
    1. V1.0版本: Base(基类)有两个方法:inc1() { counter++ }inc2() { counter++ }
    2. 你写了一个Derived(派生类),重写了inc2(),使其“更高效”:inc2() { inc1() }。没问题,代码运行良好。
    3. V2.0版本: 几个月后,Base类的作者决定“重构”代码,遵循“DRY”(Don't Repeat Yourself)原则。他“安全地”修改了Base类,让inc1()去调用inc2()inc1() { inc2() }。他认为这只是一个内部实现细节的修改。
    4. 灾难发生: 你的Derived类(你根本没碰过)现在在调用inc2()时,会触发无限递归:
      • Derived.inc2() 调用 Base.inc1()
      • Base.inc1() 调用 Base.inc2()
      • Base.inc2() 被动态派发(dynamic dispatch)到你重写的 Derived.inc2()
      • Derived.inc2() 再次调用 Base.inc1()...
    5. StackOverflow(栈溢出)。14
  • 结论: 父类的一个“安全的”、“内部的”修改,摧毁了子类。OOP的“封装”承诺(“你不需要知道内部实现”)在继承面前碎了一地。基类是“脆弱的”14

耻辱柱三:企业级笑话(AbstractSingletonProxyFactoryBean)

这是OOP在“文化”上的彻底失败。

“设计模式”1本应是大师的蓝图,最后却沦为新手的教条32。尤其是在Java企业界,“工厂模式”(Factory)1泛滥成灾,导致了“工厂的工厂,无处不在”的Meme 33

34提供了一个完美的例子:在Java里,你可能需要10行(DocumentBuilderFactory.newInstance().newDocumentBuilder().parse()...)令人作呕的“工厂”样板代码,只为了解析一个XML文件。而C#(具有讽刺意味的是,也是OOP语言)只需要2行34

这就是我们为了创建对象,而创建FactoryFactory(工厂的工厂)35的官僚主义编程。这就是莱纳斯27所鄙视的“低效的抽象模型”。

连接所有线索:继承是万恶之源

现在,把这三根耻辱柱连起来看。

LSP(正方形问题)29是“is-a”继承的问题。
脆弱的基类14是实现继承的问题。
大猩猩-香蕉15是继承导致的紧耦合问题。

你发现了吗?OOP的四大支柱中,有一个支柱(继承)是腐烂的。

那么,“设计模式”和“组合优于继承”11的口号是怎么来的?它们就是变相的道歉!它们是OOP社区为了绕过“继承”这个破碎的柱子而发明出来的“脚手架”。

OOP社区(心照不宣地)承认了它的核心机制之一已经破产,并花了二十年时间发明各种“模式”来避免使用它。

一场“宗教改革”已经不可避免。

第四部分:函数式(FP)宗教改革(回归冰冷、美丽的数学)

如果说OOP是一个充满了潜规则、密室政治和官僚主义的臃肿帝国,那么函数式编程(FP)就是一座寂静、极简、人人平等的修道院。

它不是什么新思想。它是一个非常古老的思想(源自20世纪30年代的“Lambda演算”36和50年代的LISP语言37),只是在OOP帝国即将崩溃时,它才作为“革命纲领”被重新发现。

4.1 新教规:汝不可“改变”任何事物

FP的教义建立在两条“铁律”之上,旨在从根源上铲除“不可预测性”。

铁律一:不可变性 (Immutability)

  • 教义: “数据皆为花岗岩”38
  • 解说: 你永远不能“修改”一个变量。永远。
  • 问: 那我怎么给变量x加1?
  • 答: 你不能。你只能创建一个全新的变量y,它的值等于x+139。旧的x(值是5)永远是5,直到宇宙毁灭。
  • 意义: 这听起来效率低下得令人发指,但它用一种极其野蛮(也极其优雅)的方式,彻底解决了“全局状态”问题40。如果任何数据都不能被改变,那么“幽灵般的远程行动”就无从谈起。这也顺便让“并发编程”(第八章1)从地狱难度瞬间降到了简单模式38

铁律二:纯函数 (Pure Functions)

  • 教义: “你的函数必须是一只‘好狗’”41
  • 解说: “纯函数”有两个严格的条件:
    1. 只吃自己的狗粮: 它所有的计算只依赖于它的输入参数。
    2. 不随地大小便: 它不会对函数外部的世界造成任何“副作用”(Side Effects)42
  • 举例:
    • 纯函数: Math.cos(x)。给定x=0,它永远返回1。它不读写全局变量,不联网,不打印到屏幕。
    • 不纯函数: FileReader.read("config.txt")。给定相同的输入("config.txt"),它今天可能返回"A",明天可能返回"B"(如果文件被修改了)。而且它有一个可怕的“副作用”:它移动了文件系统的“文件指针”。
  • 43中有一个绝妙的比喻:“水本身不是幼虫的孵化器,停滞不动(stagnant)的水才是。副作用也是如此。” FP不禁止副作用,但要求你把它们(比如数据库写入)严格“隔离”和“控制”起来。

4.2 神圣三位一体:Map, Filter, Reduce

OOP有它的“四大支柱”(名词),FP也有它的“三大支柱”(动词)。它们是函数式程序员的“圣三位一体”,是处理列表(Collection)的万能工具。

让我们用4444中那个简单、强大的“形状”类比来翻译它们:

  • map (映射)
    • 类比: “把所有方块变成圆圈”44
    • 解说: 这是一个 1-to-1 的转换器。你给它一个有5个“方块”的列表,它返回一个全新的、有5个“圆圈”的列表。原列表不变45
  • filter (过滤)
    • 类比: “只保留所有三角形”44
    • 解说: 这是一个“子集”选择器。你给它一个有5个各种形状的列表,它返回一个全新的、只包含(比如)2个“三角形”的列表。原列表不变45
  • reduce (规约)
    • 类比: “滚雪球”44或“把所有小圆圈融合成一个大圆圈”。
    • 解说: 这是一个“聚合”器。你给它一个有5个“圆圈”的列表,它返回一个单独的值(比如所有圆圈的面积总和)45

“圣三位一体”的真相:它们只是for循环的“马甲”

这“三大法术”听起来很神奇,但对我们这些老家伙来说,这玩意儿一点都不新鲜。它们只是我们用了三十年的三种最常见的for循环的抽象马甲46

  1. map 是什么?
    它是“转换型for循环”:

    for (i=0; i<len; i++) { newList[i] = transform(oldList[i]); }
    
  2. filter 是什么?
    它是“筛选型forB循环”:

    for (i=0; i<len; i++) { if (test(oldList[i])) { newList.add(oldList[i]); } }
    
  3. reduce 是什么?
    它是“累加型for循环”:

    for (i=0; i<len; i++) { accumulator = combine(accumulator, oldList[i]); }
    

FP的“圣三位一体”并不是什么外星科技。它只是用一种更干净、更声明式(Declarative)的方式,来命名我们早已在命令式(Imperative)世界中重复了无数次的三种模式。

4.3 驯服上古巨兽(那些吓跑新手的术语)

FP改革之所以花了这么久才成功,因为它带来了一群“学术神棍”。他们不说人话,满口都是“范畴论”、“幺半群”和“函子”。让我们来戳穿他们。

巨兽一:Lambda 演算

这是FP的“汇编语言”。我们不需要懂数学。我们只需要知道那个关于“鳄鱼蛋”的搞笑故事就够了47

这个类比把“Lambda演算”描绘成一个游戏:

  • “饥饿的鳄鱼” 代表 “Lambda抽象”(即一个函数定义)。
  • 它们会“吃掉” 旁边的 “鳄鱼家族”(即函数参数)。
  • 当鳄鱼吃掉参数后,它家族里和它同色的“鳄鱼蛋”(即变量)就会“孵化”成它刚刚吃掉的东西(即“参数代换”或“Beta-reduction”)。

这个荒诞的故事,比任何教科书都能更好地解释FP的底层计算模型47

巨兽二:Monad (单子)

啊,Monad。编程界最大的智商过滤器。

道格拉斯·克罗克福德(JSON的推广者)曾定义过“Monad的诅咒”:“一旦你顿悟了它是什么,你就立刻失去了向别人解释它的能力”48

让我们来打破这个诅咒。

  • 最简单的解释: Monad是一个“包装盒”48。它是个“墨西哥卷饼”49。它是一个“可编程的句号”50
  • 它到底有什么用? 它是一个标准化的“包装盒”,用来安全地“容纳”那些“不纯洁”或“有危险”的值,以便让你的其他代码可以保持“纯洁”。
  • 杀手级范例:Maybe Monad

我们都写过这种防御性代码:48

if (user!= null && user.getAddress()!= null && user.getAddress().getStreet()!= null) {... }

Maybe(或称Optional)48 Monad就是为了消灭这种代码而生的。它是一个“盒子”,这个盒子里要么装着Just(value)(“有东西”),要么装着Nothing(“啥也没”)。

你不需要再写if (user!= null)。你直接在“盒子”上“链接”你的操作。如果盒子里是Nothing,“盒子”的map方法会自动跳过你的操作,直接返回Nothing

Maybe(user).map(u => u.getAddress()).map(a => a.getStreet())

这个Maybe“盒子”,就是一种用来安全处理“空值”(null)这种不洁物的标准化容器。这就是Monad的全部意义:用一个“纯洁的”包装盒,去隔离和控制“不纯洁的”副作用(如null、异常、I/O等)。

FP的终极讽刺:Monad就是个“好对象”

现在,请你退后一步,用我们“恐龙”的眼睛审视一下这个叫Maybe的“Monad”:

  1. 它是一个“包装盒”48
  2. 它封装了一个“状态”(Just(value)Nothing)。
  3. 它提供了一套公共的方法(如 .map(), .bind())来让你与这个状态交互。

一个封装了状态、并提供了公共方法来操作这个状态的“盒子”……

老天爷,这不就是“对象”(Object)的定义吗?!

这就是最大的讽刺。FP的死忠粉们,在他们逃离“邪恶的”C++/Java OOP的漫长征途中,最终(再次)意外地“重新发明”了“纯正的”OOP——艾伦·凯最初设想的那个“基于消息传递的、完全封装的”对象模型。

他们只是给它起了个吓人的数学名字。

第五部分:混合主义的胜利(战争结束,人人有糖吃)

事实证明,OOP vs. FP 并不是一场你死我活的战争,而是一场长达三十年的“观念融合”51。火焰节(flame war)结束了,灰烬之中站起来的是“多范式”(Multi-paradigm)的现代程序员52

我们(老家伙们)赢了,因为我们现在有了两套工具。

5.1 停战协定A:C# 和 LINQ“特洛伊木马”

C# 是微软对标Java的、终极的、企业级的OOP语言。然而,FP就是通过这门语言,对企业界发动了最成功的“特洛伊木马”攻击。

这个“木马”的名字叫LINQ (Language-Integrated Query) 53

微软的天才们把FP的“圣三位一体”伪装成了C#的方法,无缝地植入了这门OOP语言54

FP“圣三位一体” C# LINQ“特洛伊木马” “恐龙”翻译手册(它在干啥)
map (映射) .Select() 55 “把这堆方块挨个变成圆圈。”
filter (过滤) .Where() 56 “把不是三角形的都给我扔了。”
reduce (规约) .Aggregate() 57 “滚雪球。把这一堆玩意儿给我融成一个。”

LINQ的真正革命性,甚至不在于这些方法。而在于它的声明式语法(Declarative Syntax)58

命令式 (Imperative) for循环(我们过去的写法):

“嘿,CPU。创建一个新列表。然后从0循环到100。检查customers[i].City是不是等于"London"。如果是,就把customers[i].Name加到新列表里。循环完了告诉我。”

声明式 (Declarative) LINQ(FP的写法):58

from c in customers where c.City == "London" select c.Name 

这看起来就像SQL58。你没有告诉计算机“如何”去做。你只是声明了你“想要什么”(What)59

LINQ是一个伪装成OOP功能的FP引擎。它在十年间,不知不觉地把数百万企业级Java/.NET程序员,训练成了半个函数式程序员。

5.2 停战协定B:JavaScript/React 的悖论

JavaScript是多范式语言的终极体现——一个塞满了OOP、过程式和函数式思想的、混乱而又充满活力的工具箱60

然而,当今世界上最流行的UI库——React——在骨子里是极度函数式的。

  1. 组件即纯函数: React要求你的UI组件必须是“纯函数”61。给它相同的props和state(输入),它必须返回完全相同的UI(JSX输出)62
  2. 副作用被严格隔离: 你不准在渲染(render)过程中搞小动作(比如API请求或修改localStorage)。所有的“不纯”行为都必须被关在useEffect这个“隔离病房”里63
  3. 不可变性即法律: 你不准直接修改状态(this.state.name = "Bob")。React强制你使用useStateuseReducer64。你必须调用setTodos()64。这正是FP的“不可变性”铁律。

React用实践证明:在处理UI这种极度复杂的、不可预测的“状态”时,FP的原则(纯洁、不可变)远比OOP(封装的可变状态)更健壮、更易于推理。

5.3 停战协定C:Python,光荣的实用主义者

如果说C#是“披着OOP外衣的FP”,React是“藏在UI里的FP”,那么Python就是那个光荣的、坦诚的“大杂烩”。

Python自豪地宣称自己是“多范式”语言65。它有强大的class(OOP),但它也“在LISP的传统中支持函数式编程”59

  • 想用OOP?没问题,class关键字随你用。
  • 想用FP?也没问题。列表推导式([x*x for x in list])就是map。lambda(匿名函数)是一等公民。
  • Python不强迫你加入任何一个宗教。它只是给了你一个装满工具的院子,让你自己挑顺手的用66

结论:欢迎回来,旅行者。你现在是房间里唯一的大人。

从过程式的GOTO地狱,到OOP的“工厂”官僚主义,再到FP的“数学修道院”,我们终于绕了一圈回来了。

这场长达50年的“信仰之战”,最终揭示了一个比OOP或FP更深刻的真相:

真正的战争,一直是“声明式” vs. “命令式”。67

  • 我们的“恐龙”代码(BASIC, C, 汇编)是100%的命令式(Imperative):我们必须告诉CPU“如何”一步一步地执行任务。
  • OOP试图创建抽象(对象)来隐藏“如何”的细节。
  • FP试图创建抽象(Monad, map)来隐藏“如何”的细节。
  • 而SQL1、LINQ58、React61,它们是声明式(Declarative):我们只告诉系统我们“想要什么”,让系统自己去弄清“如何”做。

OOP和FP,只是两条通往同一个“声明式”罗马的、截然不同的道路。

作为“复活”的程序员,我们是这个房间里唯一看过整部史诗的人。我们见证了“原罪”,我们目睹了“OOP福音书”的失败,我们理解了“FP宗教改革”的动机。

我们不再是任何一个范式的狂热信徒。我们是掌握了所有范式的实用主义大师。

欢迎回来,旅行者。我们不再是恐龙。我们是导游。


引用的著作


  1. 程序员复活手册:从BASIC到AI(大纲) ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. What problems of procedural programming does OOP solve in practice?, 访问时间为 十月 27, 2025, https://cs.stackexchange.com/questions/22867/what-problems-of-procedural-programming-does-oop-solve-in-practice ↩︎ ↩︎ ↩︎

  3. Global variables Memes - ProgrammerHumor.io, 访问时间为 十月 27, 2025, https://programmerhumor.io/memes/global-variables ↩︎

  4. Can you give examples of spaghetti code and non-spaghetti code ..., 访问时间为 十月 27, 2025, https://www.reddit.com/r/learnprogramming/comments/a8b6e7/can_you_give_examples_of_spaghetti_code_and/ ↩︎ ↩︎ ↩︎

  5. Software Quality: The Horror of Spaghetti Code - Teklibri, 访问时间为 十月 27, 2025, https://www.teklibri.com/software-quality-spaghetti-code/ ↩︎

  6. Exploring the Four Pillars of OOP with Fun Real-Life Examples | by Nozibul Islam, 访问时间为 十月 27, 2025, https://medium.com/@nozibulislamspi/exploring-the-four-pillars-of-oop-with-fun-real-life-examples-9750504bb972 ↩︎

  7. Object Oriented Programming in Memes - DEV Community, 访问时间为 十月 27, 2025, https://dev.to/abbeyperini/object-oriented-programming-in-memes-4kck ↩︎ ↩︎ ↩︎

  8. Encapsulation. : r/ProgrammerHumor - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/ProgrammerHumor/comments/707ql4/encapsulation/ ↩︎

  9. Abstraction Memes · ProgrammerHumor.io, 访问时间为 十月 27, 2025, https://programmerhumor.io/memes/abstraction ↩︎ ↩︎ ↩︎

  10. A good Object-Oriented analogy - oop - Stack Overflow, 访问时间为 十月 27, 2025, https://stackoverflow.com/questions/3141957/a-good-object-oriented-analogy ↩︎ ↩︎ ↩︎

  11. The problem with Object Oriented Programming and Deep Inheritance - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/programming/comments/1nty75l/the_problem_with_object_oriented_programming_and/ ↩︎ ↩︎

  12. OOP Pillar #4: Polymorphism For Dummies - Sloth Bytes, 访问时间为 十月 27, 2025, https://slothbytes.beehiiv.com/p/polymorphism ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  13. [Help] can some quickly explain polymorphism like i'm 5? : r/csharp - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/csharp/comments/8qlxdr/help_can_some_quickly_explain_polymorphism_like/ ↩︎

  14. Fragile base class - Wikipedia, 访问时间为 十月 27, 2025, https://en.wikipedia.org/wiki/Fragile_base_class ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  15. Banana Gorilla Jungle — OOP. From the famous quote, | by tanut ..., 访问时间为 十月 27, 2025, https://medium.com/codemonday/banana-gorilla-jungle-oop-5052b2e4d588 ↩︎ ↩︎ ↩︎ ↩︎

  16. Alan Kay gave his thoughts on C++ for OOP. | by Ianjoyner | Medium, 访问时间为 十月 27, 2025, https://medium.com/@ianjoyner/alan-kay-gave-his-thoughts-on-c-for-oop-1321660f04a9 ↩︎

  17. Alan Kay's Vision of OOP - DEV Community, 访问时间为 十月 27, 2025, https://dev.to/muhammad_salem/alan-kays-vision-of-oop-k5h ↩︎ ↩︎

  18. Smalltalk - Wikipedia, 访问时间为 十月 27, 2025, https://en.wikipedia.org/wiki/Smalltalk ↩︎

  19. object oriented - Alan Kay: "The Big Idea is Messaging" - Software ..., 访问时间为 十月 27, 2025, https://softwareengineering.stackexchange.com/questions/264697/alan-kay-the-big-idea-is-messaging ↩︎

  20. Alan Kay on his original thoughts when he came up with the term ..., 访问时间为 十月 27, 2025, https://www.reddit.com/r/programming/comments/bpb5v6/alan_kay_on_his_original_thoughts_when_he_came_up/ ↩︎ ↩︎

  21. What did Alan Kay mean by, 'I made up the term object-oriented, and I can tell you I did not have C++ in mind.'? - Quora, 访问时间为 十月 27, 2025, https://www.quora.com/What-did-Alan-Kay-mean-by-I-made-up-the-term-object-oriented-and-I-can-tell-you-I-did-not-have-C++-in-mind ↩︎ ↩︎

  22. Alan Kay on why he created object-oriented programming - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/programming/comments/googys/alan_kay_on_why_he_created_objectoriented/ ↩︎

  23. Memorable Edsger Dijkstra Quotes, 访问时间为 十月 27, 2025, https://www.scranton.edu/faculty/mccloskey/dijkstra_quotes.html ↩︎

  24. Ew Dijkstra Quotes - C2 wiki, 访问时间为 十月 27, 2025, https://wiki.c2.com/?EwDijkstraQuotes ↩︎

  25. Why did Dijkstra say that “Object-oriented programming is an exceptionally bad idea which could only have originated in California.”? - Quora, 访问时间为 十月 27, 2025, https://www.quora.com/Why-did-Dijkstra-say-that-%E2%80%9CObject-oriented-programming-is-an-exceptionally-bad-idea-which-could-only-have-originated-in-California-%E2%80%9D ↩︎

  26. c++ - YOU are full of bullshit. C++ is a horrible language ... - devRant, 访问时间为 十月 27, 2025, https://devrant.com/rants/1494462/you-are-full-of-bullshit-c-is-a-horrible-language-it-s-made-more-horrible-by-the ↩︎

  27. Linus Torvalds on C++ : r/learnprogramming - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/learnprogramming/comments/1dss1jo/linus_torvalds_on_c/ ↩︎ ↩︎ ↩︎

  28. Linus Torvalds: “C++ is really a terrible language!” | by Shingai Zivuku | Nerd For Tech, 访问时间为 十月 27, 2025, https://medium.com/nerd-for-tech/linus-torvalds-c-is-really-a-terrible-language-2248b839bee3 ↩︎

  29. The Square-Rectangle Problem: A Classic Example of Liskov ..., 访问时间为 十月 27, 2025, https://medium.com/@ikirezidivine21/the-square-rectangle-problem-a-classic-example-of-liskov-substitution-principle-violation-80ae95326407 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  30. Square, Rectangle and the Liskov Substitution Principle | by Alexandre Dutertre - Medium, 访问时间为 十月 27, 2025, https://medium.com/@alex24dutertre/square-rectangle-and-the-liskov-substitution-principle-ee1eb8433106 ↩︎

  31. Liskov Substitution: The Real Meaning of Inheritance - cekrem.github.io, 访问时间为 十月 27, 2025, https://cekrem.github.io/posts/liskov-substitution-the-real-meaning-of-inheritance/ ↩︎

  32. Why do some people say OOP is bad? : r/learnprogramming - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/learnprogramming/comments/x6rvk9/why_do_some_people_say_oop_is_bad/ ↩︎

  33. Why is '_____Factory' a meme when it comes to criticisms of Java ..., 访问时间为 十月 27, 2025, https://www.reddit.com/r/AskProgramming/comments/f4tj8a/why_is_factory_a_meme_when_it_comes_to_criticisms/ ↩︎

  34. How would another popular language avoid having to use the factory pattern while managing similar complexity as in Java/Java EE?, 访问时间为 十月 27, 2025, https://softwareengineering.stackexchange.com/questions/236105/how-would-another-popular-language-avoid-having-to-use-the-factory-pattern-while ↩︎ ↩︎

  35. Introducing the "Factory Factory" design pattern, tell me what's wrong, wrong answers only : r/ProgrammerHumor - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/ProgrammerHumor/comments/yl9fp1/introducing_the_factory_factory_design_pattern/ ↩︎

  36. Lambda Calculus and Lisp, part 1 - The Neo-Babbage Files ❚, 访问时间为 十月 27, 2025, https://babbagefiles.xyz/lambda-calculus-and-lisp-01/ ↩︎

  37. Fun with Lambda Calculus : r/programming - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/programming/comments/jdu26y/fun_with_lambda_calculus/ ↩︎

  38. At a low level, what is immutability, really? : r/ProgrammingLanguages - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/ProgrammingLanguages/comments/1cumxdd/at_a_low_level_what_is_immutability_really/ ↩︎ ↩︎

  39. Functional Programming in React: Key Concepts, Examples, and Best Practices - Medium, 访问时间为 十月 27, 2025, https://medium.com/@sobhanyazdanjoo/functional-programming-in-react-key-concepts-examples-and-best-practices-cb592335fcec ↩︎

  40. How is state handled differently between OOP and procedural programming? - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/learnprogramming/comments/1e5oh5i/how-is-state-handled-differently-between-oop-and/ ↩︎

  41. What are pure functions? - Eric Normand, 访问时间为 十月 27, 2025, https://ericnormand.me/podcast/what-are-pure-functions ↩︎

  42. What are Side-Effects in Programming? | by Saravanan M | Nerd For Tech - Medium, 访问时间为 十月 27, 2025, https://medium.com/nerd-for-tech/what-are-side-effects-in-programming-51f7ef340f98 ↩︎

  43. Chapter 03: Pure Happiness with Pure Functions - mostly-adequate-guide - GitBook, 访问时间为 十月 27, 2025, https://mostly-adequate.gitbook.io/mostly-adequate-guide/ch03 ↩︎

  44. Another Map, Filter, Reduce Article? Yes! Deep Dive — Part 1 | by ..., 访问时间为 十月 27, 2025, https://medium.com/@clocksmith/another-map-filter-reduce-article-yes-deep-dive-part-1-7ccc57425739 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  45. Main difference between map and reduce - javascript - Stack Overflow, 访问时间为 十月 27, 2025, https://stackoverflow.com/questions/49934992/main-difference-between-map-and-reduce ↩︎ ↩︎ ↩︎

  46. Reading 25: Map, Filter, Reduce - MIT, 访问时间为 十月 27, 2025, https://web.mit.edu/6.005/www/fa15/classes/25-map-filter-reduce/ ↩︎

  47. CS 463 - Lambda Calculus Resources, 访问时间为 十月 27, 2025, https://cs.gmu.edu/~marks/463/slides/1.lambda_calculus/lambda_intros.html ↩︎ ↩︎

  48. Explained in 5 minutes: Monads in plain JavaScript | by Piotr Jaworski, 访问时间为 十月 27, 2025, https://piotrjaworski.medium.com/explained-in-5-minutes-monads-89d54d230baf ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  49. What is MONAD? : r/functionalprogramming - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/functionalprogramming/comments/13cnx5e/what_is_monad/ ↩︎

  50. Monad (functional programming) - Wikipedia, 访问时间为 十月 27, 2025, https://en.wikipedia.org/wiki/Monad_(functional_programming) ↩︎

  51. r/ProgrammerHumor on Reddit: memeProudlyPresentedToYouByTheFunctionalProgrammingGang, 访问时间为 十月 27, 2025, https://www.reddit.com/r/ProgrammerHumor/comments/1j0alim/memeproudlypresentedtoyoubythefunctionalprogrammin/ ↩︎

  52. Is it bad practice to mix OOP and procedural programming in Python (or to mix programming styles in general) [closed] - Stack Overflow, 访问时间为 十月 27, 2025, https://stackoverflow.com/questions/47441017/is-it-bad-practice-to-mix-oop-and-procedural-programming-in-python-or-to-mix-pr ↩︎

  53. Is anyone doing functional programming in C# : r/dotnet - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/dotnet/comments/1i6nrwg/is_anyone_doing_functional_programming_in_c/ ↩︎

  54. c# - Map and Reduce in .NET - Stack Overflow, 访问时间为 十月 27, 2025, https://stackoverflow.com/questions/428798/map-and-reduce-in-net ↩︎

  55. Functional Linq tips for senior C# developers | by Dimitris Papadimitriou - Medium, 访问时间为 十月 27, 2025, https://functionalprogramming.medium.com/functional-linq-tips-for-senior-c-developers-bfb869547610 ↩︎

  56. Enumerable.Where Method (System.Linq) - Microsoft Learn, 访问时间为 十月 27, 2025, https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.where?view=net-9.0 ↩︎

  57. Functional programming with Linq - IEnumerable.Aggregate | theburningmonk.com, 访问时间为 十月 27, 2025, https://theburningmonk.com/2010/02/functional-programming-with-linq-ienumerable-aggregate/ ↩︎

  58. Language Integrated Query (LINQ) - C# | Microsoft Learn, 访问时间为 十月 27, 2025, https://learn.microsoft.com/en-us/dotnet/csharp/linq/ ↩︎ ↩︎ ↩︎ ↩︎

  59. Functional Programming HOWTO — Python 3.14.0 documentation, 访问时间为 十月 27, 2025, https://docs.python.org/3/howto/functional.html ↩︎ ↩︎

  60. JavaScript language overview - MDN Web Docs, 访问时间为 十月 27, 2025, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Language_overview ↩︎

  61. Keeping Components Pure – React, 访问时间为 十月 27, 2025, https://react.dev/learn/keeping-components-pure ↩︎ ↩︎

  62. What Is a Pure Function? Why Do React Function Components Need to Be Pure?, 访问时间为 十月 27, 2025, https://www.explainthis.io/en/swe/react-pure-function ↩︎

  63. Components and Hooks must be pure - React, 访问时间为 十月 27, 2025, https://react.dev/reference/rules/components-and-hooks-must-be-pure ↩︎

  64. Functional Programming In React | Saeloun Blog, 访问时间为 十月 27, 2025, https://blog.saeloun.com/2024/07/25/functional-programming-in-react/ ↩︎ ↩︎

  65. Exploring Three Major Programming Paradigms in Python | by Pickl.AI - Medium, 访问时间为 十月 27, 2025, https://medium.com/@deepanshi.pal/exploring-three-major-programming-paradigms-in-python-b39c712c507c ↩︎

  66. Is Python a functional programming language or an object oriented language?, 访问时间为 十月 27, 2025, https://stackoverflow.com/questions/63889627/is-python-a-functional-programming-language-or-an-object-oriented-language ↩︎

  67. Functional programming vs. imperative programming - LINQ to XML - .NET - Microsoft Learn, 访问时间为 十月 27, 2025, https://learn.microsoft.com/en-us/dotnet/standard/linq/functional-vs-imperative-programming ↩︎