面向对象的历史

1950 年 MIT 实验室 谈论 objects 和 oriented 等技术名词。ALGOL 语言。

1960 年 Simula,类、对象、继承、动态绑定

1970 受 Simula 启发 Smalltalk 出现,Lisp 又受到 Smalltalk 影响,也接纳面向对象思想,出现了 Flavors (mixin)、CommonLoops(多继承)、又导致了 Common Lisp 对象系统出现。可以看出来面向对象和函数式不是对立的

1981 年 Adele Goldberg 撰文将 Smalltalk 和面向对象编程推广给更多的人
1986 年 第一届 面向对象编程峰会,后来 C++、Object-C 出现

1990 年 面向对象广泛接纳,甚至成为主流编程思想,尤其是用户编程领域。事件驱动编程在它的带领下也变得流行(Nodejs 就是事件驱动语言)
一些原本不支持面向对象的语言也引入面向对象(BASIC、Pascal),并引发了一些问题
一些从一开始就支持面向对象、也支持以前过程式编程的语言被发明出来,如 Python 和 Ruby
最重要的商业化面向对象语言是 Sun 公司开发的 Java 语言,以及微软开发的 C# 和 VB.NET

从这些历史可以看出面向对象是一种思潮,并没有明确定义。比较公认的面向对象语言是 Smalltalk、C#、VB.NET。

面向对象语言的分类

  • 纯面向对象语言:一切都是对象、包括数字、字符串也是对象,Python、Ruby、Scala、Smalltalk
  • 完全支持面向对象,也支持过程式,Java、C++、C#
  • 本来不支持,后来加上的,PHP、Perl
  • 看起来像面向对象,但没有完全使用面向对象的:JavaScript(基于原型模拟面向对象)、Lua
  • 其他

设计模式是指对于某个广泛出现的问题提出适用性广、可复用的解决方法。

面向对象缺点:复用性和模块化并不一定能达到预期目标、过份强调模型,忽略了计算和算法。

对象间的交互也就是消息传递

比如 person.cut(‘jj’) 是指:

  • 给 person 对象发送一个 cut 消息
  • person 对象会响应这个消息

然后我们用 smalltalk 翻译一下:

person cut: 'jj'
person cut: 'jj'; cut: 'hands'

可以看出来第一句意思为 person 切 jj,第二句意思为 person 切 jj,剁 hands。

person.cut('jj')
person.cut('hands')

在 JavaScript 里 person.cut 是指 person 拥有了 cut。在 smalltalk 里是 person 说了一句 cut ‘jj’,说了两句话 cut jj、cut hands。你跟我说了一句话,然后我回复给你一句话。

面向对象的核心就是对象与对象之间的交互(消息传递),如下图:

总结为:

  1. 对象维护自己的状态和生命周期
  2. 每个对象独立
  3. 对象和对象之间通过消息传递来工作

那 React Element 为什么会有自己的生命周期呢,因为它要维护自己的状态,如果你要对一个 Element 做一个什么操作,你应该通过参数给它传一个消息给它,这个参数其实就是这个 Element 的状态。

Smalltalk 的名称由来,写代码就是和这些对象说话。

函数也能存储状态

(define money 100)
(define (take n)
(set! money (-money n))
money)

set 是给 money 第二次赋值,define 是第一次赋值

将 money 的操作定义一个局部变量

(define taker
(let (money 100)
(lambda (n)
(set! money (-money n))
money)))

(taker 25) // 结果为 75
(taker 25) // 结果为 50

翻译成 JavaScript

const taker = (() => {
let money = 100
return n => {
money = money - n
return money
}
})()

函数也是可以存储状态的,只要它可以赋值完全不用对象,也就是利用闭包存储状态。

对象是穷人的闭包,闭包是穷人的对象

JavaScript 这样的语言,它是很容易实现闭包的,于是它就用用闭包来实现的封装,而像 c# 这样的语言,它是很难用函数的,于是它就用对象来封装。

函数实现消息传递类似的形式

let makeAccount = (money) =>  {
let take = (n) => {
money = money - n
return money
}
let save = (n) => {
money = money + n
return money
}
let dispatch = (m) => {
return (
m === 'take' ? take :
m === 'save' ? save :
new Error('不支持')
)
}
return dispatch
}

let account = makeAccount(100)
account('take')(25) // 75
account('take')(25) // 50

翻译成 lisp 调用

((account 'take) 25)

函数式编程的面向对象编程是怎么样的 🤔️

不使用赋值就是函数式,比如 Lisp 的代入式求值

实现一个函数式分页 Demo 一个数组生成一个新的数组,很像一个概念,那就是迭代… 之前 justjavac 分享过一个 PPT:“函数式编程就是垃圾”。因为第一个数组生成第二个数组,第一个数组就是垃圾了,再第二个数组生成第三个数组,第二个数组就不再用了就是垃圾了。这些数组都是存在内存里面的。

赋值的本质

如果没有赋值,money 就只是一个符号是一个值的名字。如果有了赋值,money 是一个容器,它保存了不同的值,它本身是一个可变的状态。

会带来的问题,如果代码里面出现了很多赋值,那么这种程序设计是命令式 / 指令式程序设计。

命令式程序会遇到 「并发」(在 nodejs 里面就会出现并发)「克隆」就会特别麻烦。

函数式编程的特点

  1. 数学 (公理化和可证明)
  2. 更加强调程序可执行的结果而非过程
  3. 函数式一等公民(函数可以作为参数的高阶函数)
  4. 纯函数,拒绝副作用,不赋值,要是需要赋值,就留一个允许副作用的函数里面去处理,就是那个不是 1% 的函数编程
  5. 不可变数据,因为不能赋值,每次都是新的值
  6. 数据即代码,代码即数据
  7. 引用透明

函数式编程允许 1% 不是函数式编程,面向对象允许 1% 不是面向对象编程。

如何把上面取款程序改成函数式写法 ❓