编程范式的发展以及主流编程范式的特点和优缺点

编程范式(Programming paradigm)是指某种编程语言典型的编程风格或编程方式。 编程范式是编程语言的一种分类方式,它并不针对某种编程语言。就编程语言而言,一种编程语言也可以适用多种编程范式。

编程范式有很多种,常见的就几种,包括面向过程(结构化),函数式(声明式),面向对象。

{% asset_img relationship.png 编程范式关系 %}

但是为什么会有这么多种编程范式?

通过将现实问题映射至数学问题,在利用图林机就可以使用程序解决现实生活中问题

但是现实问题到图灵机中程序之间存在着映射为数学问题的过程,这一过程中的问题就有程序员来实现,但是在实现的方法上,也就是编程范式就会存在不同,因此诞生了多种编程范式。 这些编程范式作为编程的思维底座,程序员在其之上进行编程解决问题;思维底座越高,程序员需要进行的操作越少。

但是不同编程范式都有优劣,同时对硬件的消耗也不同,应当更根据不同的需求进行选择。

编程范式的发展

{% asset_img paradigm-history.png 发展历史 %}

非结构化编程

从最底层的开始说就是机械语言,即 0 和 1。使用这种二进制的序列进行指令的表达和数据的存储。基本没有阅读性。 然后向上就是汇编,使用助记符号进行指令表示,虽然有了可读性,但是仍然很痛苦。

这两种语言硬说编程范式,那就是非结构化编程。程序中充斥着 goto,难以维护,最终达成共识 goto 有害。

{% asset_img none-struct.png 非结构化编程 %}

结构化编程

结构化编程中最重要也是最主要的也就是 c 语言了,它对计算机进行了恰当的抽象,并且掩盖了诸多细节。

当高级语言大行其道以后,人们开发的程序规模逐渐膨胀,这时如何组织程序变成了新的挑战。有一种语言搭着 C 语言的便车将面向对象的设计风格带入主流视野,这就是 C++,它完全兼容 C 语言。在很长一段时间内,C++ 风头十足,成为行业中最主流的编程语言。后来,计算机硬件的能力得到了大幅提升,Java 语言脱颖而出。Java 语言假设程序的代码空间是开放的,在 JVM 虚拟机上运行,一方面支持面向对象,另一方面支持 GC 功能。

{% asset_img structed.png 结构化编程 %}

函数式编程

前面的编程范式发展是程序语言发展的主要路径,但是还有一只非主流的,即函数式编程。 作为代表的就是 Lisp。主要理论基础就是 Lambda 演算。 它使用的是抽象代数的思维。更贴近自然科学,更靠近使用数学问题解决现实问题。

但是在实际情况中,越是抽象的计算,离计算机硬件越远,其执行效率也越低,在以往的环境下,运行性能并不能得到支撑,所以很长一段时间内函数式编程被认作工程上不成熟的编程范式。

当硬件的性能不再成为阻碍,如何解决问题开始变得越来越重要时,函数式编程终于和编程语言发展的主流路径汇合了。促进函数式编程引起广泛重视还有一些其他因素,比如多核 CPU 和分布式计算。

{% asset_img functional-programm.png 函数式编程 %}

多范式编程

无论在以结构化编程为主的语言中引入面向对象编程,还是在以面向对象编程为主的语言中引入函数式编程,在一个程序中应用多范式已经成为一个越来越明显的趋势。 不仅仅在设计中,越来越多的编程语言逐步将不同编程范式的内容融合起来。C++ 从 C++ 11 开始支持 Lambda 表达式,Java 从 Java 8 开始支持 Lambda 表达式,同时新诞生的语言一开始就支持多范式,比如 Scala,Go 和 Rust 等。

面向过程编程(procedure oriented programming)

分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。

将程序抽象成算法+数据结构 数据结构描述待处理数据的组织形式,而算法描述具体的操作过程。

优点

  • 性能高,比如单片机、嵌入式开发、 Linux/Unix 等一般采用面向过程开发,性能是最重要的因素
  • 符合自然逻辑,更易理解

缺点

  • 临时变量多
  • 不易维护
  • 复用程度低
  • 不易扩展
  • 可读性低
  • 由于设计问题可能存在大量耦合

面向对象编程(object oriented programming)

把构成问题的事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

将程序抽象为对象+关系

面向对象的特点

  • 封装:属性和方法封装在类里,掩盖细节,某种程度上的解耦,面向对象的根基
  • 继承:类型系统,子类对父类的属性和方法的继承,子类也会有自己的属性和方法,代码的复用
    • 实现继承:从子类角度看
    • 接口继承:从父类的角度看
  • 多态:子类对父类继承来的属性和方法进行调整,重写,接口继承是常见的一种多态的实现方式,面向对象的关键

正因为多态的存在,软件设计才有了更大的弹性,能够更好地适应未来的变化。只使用封装和继承的编程方式,我们称之为基于对象编程,而只有把多态加进来,才能称之为面向对象编程。可以这么说,面向对象设计的核心就是多态的设计。

优点

  • 易维护、易复用、易扩展
  • 由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统
  • 更加灵活、更加易于维护  缺点
  • 性能比面向过程低,因为类调用时需要实例化,开销比较大,比较消耗资源

函数式编程(functional programming)

虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。 其一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

函数式编程是一种抽象程度很高的编程范式,==纯粹的函数式编程语言编写的函数没有变量==, 因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。 而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

函数式编程的特点

  • 函数是一等公民
    • 它可以按需创建
    • 它可以存储在数据结构中
    • 它可以当作参数传给另一个函数
    • 它可以当作另一个函数的返回值
  • 纯函数
    • 对于相同的输入,返回相同的输出
    • 没有副作用
  • 惰性求值:一种求值策略,它将求值的过程延迟到真正需要这个值的时候
  • 不可变数据:不变性主要体现在值和纯函数上
    • 值一旦创建,就不能修改,除非重新创建
    • 值保证不会显式修改一个数据,纯函数保证不会隐式修改一个数据
    • 当你深入学习函数式编程时,会遇到无副作用、无状态和引用透明等说法,其实都是在讨论不变性
  • 递归:函数式编程用递归作为流程控制的机制,一般为尾递归

高阶函数和闭包

所谓高阶函数就是能够接受函数作为参数,以及将函数作为返回值的函数。

而闭包则是函数+引用环境组成的实体 闭包的特点有:

  • 闭包有独立生命周期,能捕获上下文(环境)
  • 闭包的设计粒度更小,创建成本更低,很容易做组合式设计
  • 易于组合易于重用的,并且是易于应对变化的

闭包是穷人的对象,对象是穷人的闭包 有的语言没有闭包,你没有办法,只能拿对象去模拟闭包。又有一些语言没有对象,但单一接口不能完整表达一个业务概念,你没有办法,只能将多个闭包组合在一起当作对象用。


优点

  • 高度的抽象,易于扩展:函数式编程是数据化表达,非常抽象,在表达范围内是易于扩展的
  • 声明式表达,易于理解
  • 形式化验证,易于自证
  • 不可变状态,易于并发:数据不可变不是并发的必要条件,不共享数据才是,但不可变使得并发更加容易

缺点

  • 对问题域的代数化建模门槛高,适用域受限:现实是复杂的,不是在每个方面都是自洽的,要找到一套完整的规则映射是非常困难的。在一些狭窄的领域,可能找得到,而一旦扩展一下,就会破坏该狭窄领域,你发现以前找到的抽象代数建模方式就不再适用了。
  • 在图灵机上性能较差:函数式编程增加了很多中间层,它的规则描述和惰性求值等使得优化变得困难。
  • 不可变的约束造成了数据泥团耦合:领域对象是有状态的,这些状态只能通过函数来传递,导致很多函数有相同的入参和返回值。
  • 闭包接口粒度过细,往往需要再组合才能构成业务概念

参考资料

面向过程编程(pop),面向对象编程(oop),函数式编程(fp)_大海和天空的博客-CSDN博客_面向过程编程 前端60s 一分钟了解编程范式 - 掘金 聊聊编程范式 - 简书