在上一篇文章 《再谈榔头和钉子》,提到了设计模式和编程范型,相较于设计模式,编程范型往往和语言本身强相关,一种特定的语言,只适用于一种或者几种编程范型。它类似于一种编程风格,也决定了程序员是如何去认识程序的结构、交互和执行的。编程范型是程序员大脑中在设计编码阶段预先考虑到的内容,但是相较于满街跑的设计模式,这个过程往往下意识地被忽略。另外,如果你现在在思考编程范型的时候,脑海里只有“ 面向对象” 和“ 面向过程” 这两者跳出来,那可能是真的被糟糕的面向对象教材毒害太深了。
在维基百科的编程范型页面右侧,有一个相对比较完整的列表:
- Action
- Agent-oriented
- Aspect-oriented
- Automata-based
- Concurrent computing
- Data-driven
- Declarative (contrast: Imperative)
- End-user programming
- Event-driven
- Expression-oriented
- Feature-oriented
- Function-level (contrast: Value-level)
- Generic
- Imperative (contrast: Declarative)
- Language-oriented
- Metaprogramming
- Non-structured (contrast: Structured)
- Nondeterministic
- Parallel computing
- Point-free style
- Semantic
- Structured (contrast: Non-structured)
- Value-level (contrast: Function-level)
- Probabilistic
- Concept
下面我把这些编程范型中常用的几种介绍一下,希望对对于编程范型有兴趣的朋友有帮助。
看英文蛋疼的同学,这里还有一个中文版,链接。
结构化(Structured)和非结构化编程
结构化编程的最大特征是使用子程序、代码块、for/while 循环结构等等来代替 goto,因此,成熟的现代编程语言大多是结构化的。结构化编程相较于非结构化编程来说,代码的易理解性和可维护性有非常显著的提高。非结构化程序语言典型的包括 Basic、COBOL、机器语言和汇编语言。
命令式(Imperative)和声明式(Declarative)编程
几乎所有计算机的执行都是命令式的,这也是更接近编译-执行思维的方式,写出来的代码会编译成相应的机器执行语句。相应地,声明式编程并不直接告诉机器要执行的步骤或者流程,而是描述目标性质,也就是说,根据描述而选择执行的算法是独立于使用语言的程序员之外的。
举例来说,XML、SQL 这些都是声明式的,因为这些语言都不会写出编译成机器码的指令语句,而是配置、条件等等描述性语句。再举具体的例子来说,使用 HTML 标记语言组织页面结构,这就是声明式的,但是如果自己用 JavaScript 去操纵 DOM 树,则回到了传统命令式的方式上。值得一提的是,正是因为 HTML 标记语言和 JavaScript 这样的编程语言的协助,很多 JavaScript 库同时支持声明式和命令式,比如 dojo:
<button type="button" id="myBtn" data-dojo-type="dijit/form/Button"> <span>Click!</span> </button>
在 DOM 上定义属性,甚至定义新的 DOM 的方式,这很常见,比如 Angular,甚至 Bootstrap,都可以见到。在结构化数据的表示和绘制上面,声明式语言都要易于理解和形象得多。
事件驱动(Event-driven)编程和基于线程(Thread-based)编程
我在 《从 JavaScript 的单线程执行说起》这篇文章里面已经提到过,我们可以找到大量“event loop+单线程执行” 的经典搭配,除了 JavaScript 以外,还有 JDK 的 GUI 线程模型,还有 Mac 系统的 Cocoa 等等。
从上图(来自 《The Case of Threads vs. Events》)可以清晰地看出二者之间的差别。另外,事件驱动编程还可以和传统的轮询方式相比较。
函数式(Functional)编程
函数式编程是最近几年炒得火热的话题,我在 《函数式编程》这篇文章中已经简要地介绍过了,它和一般的命令式编程最本质的区别在于“ 没有状态”(关于状态,请移步这篇文章),即像数学函数一样 ,输出值仅仅依赖于输入参数。正因为“ 没有状态”,这才有“ 必须有返回值”、“ 没有副作用”、“ 透明引用” 和“ 惰性计算” 等等特点。除了这一条最本质的以外,还要加上一条“ 函数是一等公民”,这就意味着函数本身可以像普通变量一样作为参数传递了。
基于类(Class-based)和基于原型(Prototype-based)编程
我们的面向对象概念几乎都是从类和对象起步的,这也对我们最初对各种的编程范型的理解造成了相当的局限性(这也造成了我在 2007 年开始使用 Groovy 写代码的时候,其实写出来的只不过是语法强化过了的 Java 代码而已),再后来要跳出这个圈子时,难免不断去拿面向对象去和这些新的编程范式放到一起联想和比较。类是对象的抽象,描述了对象所具备共同的属性和方法。大多数传统的面向对象语言都是严格把类和对象相区别的,比如理解 Java 的类型需要理解类、对象、原语类型和对象元信息这样普适的概念,也才有了面向对象封装、继承和多态三个最重要的概念。
但是面向对象编程并不只有基于类这一种经典的方式,例如在这里就根据有没有类的存在分成了基于原型和基于类这两种方式;而根据关注点分离(Separation Of Concerns,关注点分离指的是把程序员编写代码的关注点从传统的业务逻辑中分离出来,原有业务逻辑代码中不再包含这部分问题领域的代码调用)的不同,又可以分成面向方面(Aspect-oriented)、面向主题(Subject-oriented)和面向角色(Role-oriented)。
在基于原型的编程中,类不是实时的,而且对象的产生是通过原型(通常也是对象)复制自己而实现的。JavaScript 本身就是基于原型的(还记得 JavaScript 实现继承中那个经典的 prototype chain 吧),只是有许多人把它用基于类的方式来理解和使用。另外,基于原型的还有一门稍小众一点的语言——Io,我曾经介绍过它,语法极其简洁干净,没有关键字,没有声明语句,非常喜欢。
还有一些也非常常用的编程泛型,没有展开讲,但是非常有用,
- 前面提到了面向方面编程,做 web 项目应该都有涉及,常常用于日志记录,性能统计,安全控制,事务处理,异常处理这些和主业务无关的行为;
- 还有基于状态机(Automata-based)编程,几乎所有编译器的实现都是基于有限状态机的;
- 管道(Pipeline)编程,如同 Linux 里的管道;
- 和基于逻辑(Logic-based)编程,比如曾经介绍过的 Prolog。
最后推荐一本书,《七周七语言:理解多种编程范型》,很适合快速开阔眼界,不要守着自己最熟悉的那门语言和框架不放了:)
文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》
命令式 (Imperative)
声明(Declarative)
not vice versa
已修正,谢谢
怪不得我一直觉得 sql 那么别扭,原来脑子被命令式编程给养成了一直惯性思维模式。