标签:
我认为"封装"的概念在面向对象思想中是最基础的概念,它实质上是通过将相关的一堆函数和一堆对象放在一起,对外有函数作为操作通道,对内则以变量作为操作原料。只留给外部程序员操作方式,而不暴露具体执行细节。大部分书举的典型例子就是汽车和灯泡的例子:你不需要知道不同车子的发动机原理,只要踩油门就可以跑;你不需要知道你的灯泡是那种灯泡,打开开关就会亮。我们都会很直觉地认为这种做法非常棒,是吧?
但是有的时候还是会觉得有哪些地方不对劲,使用面向对象语言的时候,我隐约觉得封装也许并没有我们直觉中认为的那么好,也就是说,面向对象其实并没有我们直觉中的那么好,虽然它已经流行了很多很多年。
函数就是做事情的,它们有输入,有执行逻辑,有输出。 数据结构就是用来表达数据的,要么作为输入,要么作为输出。
两者本质上是属于完全不同的东西,面向对象思想将他们放到一起,使得函数的作用被限制在某一个区域里,这样做虽然能够很好地将操作归类,但是这种归类方法是根据"作用领域"来归类的,在现实世界中可以,但在程序的世界中,有些不妥。
不妥的理由有如下几个:
在并行计算时,由于执行部分和数据部分被绑定在一起,这就使得这种方案制约了并行程度。在为了更好地实现并行的时候,业界的工程师们发现了一个新的思路:函数式编程。将函数作为数据来使用,这样就能保证执行的功能在时序上的正确性了。但你不觉得,只要把数据表达和执行部分分开,形成流水线,这不就能够非常方便地将并行数提高了么?
我来举个例子: 在数据和函数没有分开时,程序的执行流程是这样:
A.function1() -> A.function2() -> A.function3() 最后得到经过处理的A
当处于并发环境时,假设有这么多任务同时到达
A.f1() -> A.f2() -> A.f3() 最后得到经过处理的A
B.f1() -> B.f2() -> B.f3() 最后得到经过处理的B
C.f1() -> C.f2() -> C.f3() 最后得到经过处理的C
D.f1() -> D.f2() -> D.f3() 最后得到经过处理的D
E.f1() -> E.f2() -> E.f3() 最后得到经过处理的E
F.f1() -> F.f2() -> F.f3() 最后得到经过处理的F
...
假设并发数是3,那么完成上面类似的很多个任务,时序就是这样
| time | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|------|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|
| A | A.1 | A.2 | A.3 | | | | | | | | | |
| B | B.1 | B.2 | B.3 | | | | | | | | | |
| C | C.1 | C.2 | C.3 | | | | | | | | | |
| D | | | | D.1 | D.2 | D.3 | | | | | | |
| E | | | | E.1 | E.2 | E.3 | | | | | | |
| F | | | | F.1 | F.2 | F.3 | | | | | | |
| G | | | | | | | G.1 | G.2 | G.3 | | | |
| H | | | | | | | H.1 | H.2 | H.3 | | | |
| I | | | | | | | I.2 | I.2 | I.3 | | | |
| J | | | | | | | | | | J.1 | J.2 | J.3 |
| K | | | | | | | |