编程思想

编程范式

泛型编程

类型的本质:

  • 类型是对内存的一种抽象。不同的类型,会有不同的内存布局和内存分配的策略。
  • 不同的类型,有不同的操作。所以,对于特定的类型,也有特定的一组操作。

要做到泛型,我们需要做下面的事情:

  • 标准化掉类型的内存分配、释放和访问。
  • 标准化掉类型的操作。比如:比较操作,I/O 操作,复制操作...
  • 标准化掉数据容器的操作。比如:查找算法、过滤算法、聚合算法...
  • 标准化掉类型上特有的操作。需要有标准化的接口来回调不同类型的具体操作...

命令式

命令式编程的主要思想是关注计算机执行的步骤,即一步一步告诉计算机先做什么再做什么。

比如:如果你想在一个数字集合 collection(变量名) 中筛选大于 5 的数字,你需要这样告诉计算机:

  • 第一步,创建一个存储结果的集合变量 results;
  • 第二步,遍历这个数字集合 collection;
  • 第三步:一个一个地判断每个数字是不是大于 5,如果是就将这个数字添加到结果集合变量 results 中。
List<int> results = new List<int>();
foreach(var num in collection)
{
    if (num > 5)
          results.Add(num);
}
1
2
3
4
5
6

声明式编程

声明式编程是以数据结构的形式来表达程序执行的逻辑。它的主要思想是告诉计算机应该做什么,但不指定具体要怎么做。

SQL 语句就是最明显的一种声明式编程的例子,例如:

SELECT * FROM collection WHERE num > 5
1

除了 SQL,网页编程中用到的 HTML 和 CSS 也都属于声明式编程。

特点

  • 它不需要创建变量用来存储数据。
  • 它不包含循环控制的代码如 for, while。

函数式编程

函数式编程的基础模型来源于λ 演算。它的理念就来自于数学中的代数。

f(x)=5x^2+4x+3
g(x)=2f(x)+5=10x^2+8x+11
h(x)=f(x)+g(x)=15x^2+12x+14
1
2
3

假设 f(x) 是一个函数,g(x) 是第二个函数,把 f(x) 这个函数套下来,并展开。然后还可以定义一个由两个一元函数组合成的二元函数,还可以做递归,下面这个函数定义就是斐波那契数列

f(x)=f(x-1)+f(x-2)
1

特征

  • stateless:函数不维护任何状态。函数式编程的核心精神是 stateless,简而言之就是它不能存在状态,打个比方,你给我数据我处理完扔出来。里面的数据是不变的。
  • immutable:输入数据是不能动的,动了输入数据就有危险,所以要返回新的数据集。

优势

  • 没有状态就没有伤害。
  • 并行执行无伤害。
  • Copy-Paste 重构代码无伤害。
  • 函数的执行没有顺序上的问题。

劣势

  • 数据复制比较严重。

函数式编程用到的技术

  • first class function(头等函数) :这个技术可以让你的函数就像变量一样来使用。也就是说,你的函数可以像变量一样被创建、修改,并当成变量一样传递、返回,或是在函数中嵌套函数。
  • tail recursion optimization(尾递归优化) : 我们知道递归的害处,那就是如果递归很深的话,stack 受不了,并会导致性能大幅度下降。因此,我们使用尾递归优化技术——每次递归时都会重用 stack,这样能够提升性能。当然,这需要语言或编译器的支持。Python 就不支持。
  • map & reduce :这个技术不用多说了,函数式编程最常见的技术就是对一个集合做 Map 和 Reduce 操作。这比起过程式的语言来说,在代码上要更容易阅读。(传统过程式的语言需要使用 for/while 循环,然后在各种变量中把数据倒过来倒过去的)这个很像 C++ STL 中 foreach、find_if、count_if 等函数的玩法。
  • pipeline(管道):这个技术的意思是,将函数实例成一个一个的 action,然后将一组 action 放到一个数组或是列表中,再把数据传给这个 action list,数据就像一个 pipeline 一样顺序地被各个函数所操作,最终得到我们想要的结果。
  • recursing(递归) :递归最大的好处就简化代码,它可以把一个复杂的问题用很简单的代码描述出来。注意:递归的精髓是描述问题,而这正是函数式编程的精髓。
  • currying(柯里化) :将一个函数的多个参数分解成多个函数, 然后将函数多层封装起来,每层函数都返回一个函数去接收下一个参数,这可以简化函数的多个参数。在 C++ 中,这很像 STL 中的 bind1st 或是 bind2nd。
  • higher order function(高阶函数):所谓高阶函数就是函数当参数,把传入的函数做一个封装,然后返回这个封装函数。现象上就是函数传进传出,就像面向对象对象满天飞一样。这个技术用来做 Decorator 很不错。

使用到的技术

  • 修饰
  • 管道
  • 拼装

函数式编程写法

  • 它们之间没有共享的变量;
  • 函数间通过参数和返回值来传递数据;
  • 在函数里没有临时变量。

面向对象编程

面向对象的编程有三大特性:封装、继承和多态。

优点:

  • 能和真实的世界交相辉映,符合人的直觉。
  • 面向对象和数据库模型设计类型,更多地关注对象间的模型设计。
  • 强调于“名词”而不是“动词”,更多地关注对象和对象间的接口。
  • 根据业务的特征形成一个个高内聚的对象,有效地分离了抽象和具体实现,增强了可重用性和可扩展性。
  • 拥有大量非常优秀的设计原则和设计模式。
  • S.O.L.I.D(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转,是面向对象设计的五个基本原则)、IoC/DIP…… 缺点:
  • 代码都需要附着在一个类上,从一侧面上说,其鼓励了类型。
  • 代码需要通过对象来达到抽象的效果,导致了相当厚重的“代码粘合层”。
  • 因为太多的封装以及对状态的鼓励,导致了大量不透明并在并发下出现很多问题。

用到的技术

  • 委托
  • 策略
  • 桥接
  • 修饰
  • IoC/DIP
  • MVC

基于原型的编程范式


// 一种构造函数写法
function Foo(y) {
  this.y = y;
}
 
// 修改 Foo 的 prototype,加入一个成员变量 x
Foo.prototype.x = 10;
 
// 修改 Foo 的 prototype,加入一个成员函数 calculate
Foo.prototype.calculate = function (z) {
  return this.x + this.y + z;
};
 
// 现在,我们用 Foo 这个原型来创建 b 和 c
var b = new Foo(20);
var c = new Foo(30);
 
// 调用原型中的方法,可以得到正确的值
b.calculate(30); // 60
c.calculate(40); // 80
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

在内存中如何表现呢

内存表现


b.__proto__ === Foo.prototype, // true
c.__proto__ === Foo.prototype, // true
 
b.constructor === Foo, // true
c.constructor === Foo, // true
Foo.prototype.constructor === Foo, // true
 
b.calculate === b.__proto__.calculate, // true
b.__proto__.calculate === Foo.prototype.calculate // true
1
2
3
4
5
6
7
8
9
10

编程的本质

程序 = 逻辑 + 控制 + 数据结构

所有的编程都围绕下面这些事来做:

  • 就像函数式编程中的 Map/Reduce/Filter,它们都是一种控制。而传给这些控制模块的那个 Lambda 表达式才是我们要解决的问题的逻辑,它们共同组成了一个算法。最后,我再把数据放在数据结构里进行处理,最终就成为了我们的程序。
  • 就像我们 Go 语言的委托模式的那个 Undo 示例一样。Undo 这个事是我们想要解决的问题,是 Logic,但是 Undo 的流程是控制。
  • 就像我们面向对象中依赖于接口而不是实现一样,接口是对逻辑的抽象,真正的逻辑放在不同的具现类中,通过多态或是依赖注入这样的控制来完成对数据在不同情况下的不同处理。

所有编程范式都在尝试解决下面问题:

  • Control 是可以标准化的。比如:遍历数据、查找数据、多线程、并发、异步等,都是可以标准化的。
  • 因为 Control 需要处理数据,所以标准化 Control,需要标准化 Data Structure,我们可以通过泛型编程来解决这个事。
  • 而 Control 还要处理用户的业务逻辑,即 Logic。所以,我们可以通过标准化接口 / 协议来实现,我们的 Control 模式可以适配于任何的 Logic。

代码复杂度的原因:

  • 业务逻辑的复杂度决定了代码的复杂度;
  • 控制逻辑的复杂度 + 业务逻辑的复杂度 ==> 程序代码的混乱不堪;
  • 绝大多数程序复杂混乱的根本原因:业务逻辑与控制逻辑的耦合。
Last Updated: 11/1/2020, 5:35:20 PM