前言
本课内容:
- 了解不同编程范式的起源和适用场景。
- 掌握JavaScript在不同的编程范式特别是函数式编程范式的使用。
- 掌握创建领域特定语言的相关工具和模式。
编程语言的发展
1.机器语言
第一代计算机的通用语言(1940年代末至1950年代初)语言是二进制的,非常难以阅读和编写,代表如下图:
2.汇编语言
在上一代的基础上,新增了字母代替数字的过程,使用单词代替一些运算过程。示例代码如下:
3.中级语言以及高级语言
C语言是结构化的语言, "中级语言"
1.可对位,字节,地址直接操作
- 代码中的
*(&x) = 20;语句可以直接修改变量x的值,说明C语言可以对位、字节、地址进行直接操作
- 代码和数据分离倡导结构化编程
- 代码中的
#include <stdio.h>语句引入了标准输入输出库,说明C语言倡导代码和数据分离,支持结构化编程
3.功能齐全:数据类型和控制逻辑多样化
- 代码中声明了整型变量
x和字符指针变量str,使用了printf函数进行输出,说明C语言的数据类型和控制逻辑非常多样化,功能齐全
4.可移植能力强
- 代码中使用了标准输入输出库,这使得代码可以在不同的平台上运行,说明C语言具有很强的可移植能力
C++最初是作为C语言的一种扩展,其基本语法与C语言相同,但增加了类、继承、多态等面向对象的特性,因此C++也被称为C with Classes
-
继承
-
权限控制
-
虚函数
-
多态
Lisp:函数式语言代表
- 与机器无关
- 列表:代码即数据
- 闭包
JavaScript
基于原型和头等函数的多范式语言
-
过程式
- 过程式编程(Procedural Programming)是一种基于“过程”的编程方式,其核心思想是将问题拆分为多个小问题,然后再将这些小问题拆分成更小的问题,直到最终可解决的问题。在一个程序中,每个过程都是由从上到下依次执行的语句组成的。过程式编程关注数据的处理过程,强调顺序结构,使用变量来存储数据,Bug 越难查,代码越容易耦合。JavaScript 作为一门脚本语言,最初也是过程式编程语言。在 JavaScript 中,你可以通过定义函数、变量等来进行过程式编程。
-
面向对象
- 面向对象编程(Object-Oriented Programming,OOP)是一种基于对象的编程方式,其核心思想是把所有事物都看作对象,并把对象之间的交互关系作为程序的基础,从而构建具有一定逻辑结构的程序。在面向对象编程中,每个对象都可以拥有自己的属性和方法,并且可以直接与其他对象进行交互。常见的面向对象编程语言有 Java、C++ 等。在 JavaScript 中,你可以通过使用 class、constructor、extends 等关键字来定义类、实例化对象等操作,完成面向对象编程。
-
函数式
- 函数式编程(Functional Programming)是一种基于数学中函数概念的编程方式,强调把计算过程看作是对函数执行的一系列变换,而不是对数据的一系列操作。函数式编程的优点在于可重用性和可维护性高,在多线程和并发编程中相对容易实现,同时也很适合应用于需要大量数据处理的场景。在 JavaScript 中,你可以通过高阶函数、箭头函数等来实现函数式编程。
-
响应式
- 响应式编程(Reactive Programming)是一种基于数据流的编程方式,其核心思想是将应用程序中的任何状态以数据流的形式表示,并使用响应式编程库实现对这些数据流进行监听和响应。响应式编程的优点在于能够提高应用程序的响应速度、简化代码实现、降低复杂度,并且支持异步处理等。在 JavaScript 中,你可以使用 RxJS 等库来实现响应式编程。
编程范式
编程范式(Programming Paradigm)是指编程语言所支持的编程方法、理念和思想等,通常可以分为多个种类。每种编程范式都有自己的特点和优势,并且通常也需要开发者拥有相应的知识和技能才能进行实际编程。
常见的编程范式包括:
- 过程式编程:以过程为核心,以数据流转为重点.
特点:自顶向下,结构化编程 - 面向对象编程:以对象为核心,以封装、继承和多态为特征
- 函数式编程:将计算看作函数之间的关系,避免造成状态改变
- 声明式编程:基于声明式语法,通过抽象、封装、组合等方式实现计算
- 响应式编程:通过对数据流的监听和响应达到目的,适用于多个异步事件同时发生或者交叉发生的场景
- 并发式编程:指并发执行的编程模式,可简化复杂的线程和进程编程
编程范式可以指导开发者如何更好地解决问题,更好地利用编程语言和工具提高开发效率和质量。不同的编程范式也让开发者能够在不同的场景中更加灵活地应对问题,因此了解不同的编程范式也是提高编程素养、提升开发能力的重要一环。
编程范式的分类
过程式编程
自顶向下
自顶向下(Top-down)是一种针对问题求解的方法。它的核心思想是:先从整体出发,把一个大问题分解成若干个小问题,然后逐层细化,最后得到具体的解决方案。
自顶向下的开发方式通常包含以下步骤:
- 确定问题:定义问题或需求,并确定所需要的功能。
- 分析问题:将整个问题的复杂度降低,将问题拆分为更小的子问题。这里需要明确每个子问题的功能和输入输出。
- 规划方案:设计解决每个子问题的方案。
- 实施方案:从最高级别的过程开始实现方案,然后逐层细化实施,直至完成所有子问题的实现。
- 测试调试:对整个系统进行测试,验证系统是否遵循了预设的规范和要求,是否有错误。
自顶向下的开发方式可以使程序员有条理地进行代码编写,提高代码的可读性和可维护性;可以降低代码耦合度,提高代码的重用性和扩展性;可以让不同的任务和模块分配给不同的团队成员,协同完成项目。
结构化编程
结构化编程是一种过程式编程方法,其核心思想是使用结构化的控制流程来编写程序。在结构化编程中,程序由顺序、选择和循环三种基本结构组成,其设计的目标是提高程序的可读性、可维护性以及可靠性。
具体来说,结构化编程需要满足以下三点原则:
- 顺序结构:程序中的每个操作都按照预定义的顺序执行,不会跳过或跨越任何步骤。
- 选择结构:根据特定条件选择执行不同的程序分支,例如if-else语句、switch-case语句等。
- 循环结构:多次执行相同的操作,例如for循环、while循环等。
结构化编程通常都能使程序看起来更加简洁、清晰,而且有助于程序员理清逻辑思路、发现和纠正错误。在过去,结构化编程曾经被视为一种革命性的编程范式,因为它的规范化和标准化使得程序易于理解和修改。然而,随着计算机科学的发展,人们也意识到结构化编程并非完美的,它忽略了许多计算机科学中的重要概念,例如面向对象编程的封装、继承和多态等
JS中的面向过程
export let car = {
meter:100,
speed:10
}; //数据
export function advanceCar(meter){
while(car < meter){
car.meter += car.speed;
}
} //计算过程
import { car , advanceCar } from ".car"//导入模块
function main(){
console.log('before',car);
advanceCar(1000)
console.log('after',car)
}
面向过程编程出现的缺点
- 可扩展性差:当程序规模增大时,面向过程的程序往往难以扩展。因为过程之间的相互依赖性很高,一个模块的修改可能会影响到整个程序的运行结果。
- 缺乏抽象和封装:面向过程讲究的是过程和函数的线性组合,而忽略了数据和算法之间的关系。这导致面向过程的程序缺乏抽象和封装,代码重复率高,可读性差。
- 代码维护困难:由于面向过程的代码重复率高、耦合度高,导致程序难以维护和修改。当一个过程出现问题的时候,需要在多个地方修改代码,这会带来极大的工作量和风险。
- 难以描述复杂流程:对于复杂流程和业务逻辑,面向过程的程序难以清晰地将其表达出来。这是因为面向过程需要将数据和算法分开处理,难以适应复杂的业务场景。
- 缺少代码复用性:由于面向过程缺乏抽象和封装,代码重复率高,导致代码复用性很低,每个程序员都需要手动编写大量重复的代码。
面向对象编程
-
封装(Encapsulation):封装是指将对象的内部状态和行为隐藏起来,并提供公共的接口来访问这些信息。封装可以保护对象的内部状态不被非法的访问和修改,同时也提高了代码的安全性和可维护性。
-
继承(Inheritance):继承是指从已有的类派生出新的类,新的类会继承原来的类的属性和方法。继承可以减少代码的重复,同时也可以为程序员提供一种层次化的设计思路。
-
多态(Polymorphism):多态是指同一操作在不同对象上有不同的行为。在多态的实现过程中,不同的对象可以对相同的消息做出不同的响应,这样可以提高程序的灵活性和可扩展性。
-
依赖注入(Dependency Injection)也是面向对象编程中的一个重要概念。依赖注入是指将一个对象所需要的依赖关系从外部进行注入,而不是直接在内部创建。这样可以降低类之间的耦合度,使得程序更加灵活和可扩展。
五大原则:
面向对象编程的五大原则也称为 SOLID 原则,它们是:
- 单一职责原则(SRP,Single Responsibility Principle):一个类只负责单一的职责,即只有一个引起它变化的原因。这样可以使得类具有高内聚性,降低类之间的耦合度,提高代码的可维护性。
- 开闭原则(OCP,Open-Closed Principle):对扩展开放,对修改关闭。这意味着在系统需要改变时,应该尽可能地通过扩展已有的代码来实现,而不是直接修改已有的代码。
- 里氏替换原则(LSP,Liskov Substitution Principle):任何基类可以出现的地方,子类一定可以出现。这意味着子类应当能够替换掉它们的父类并且保证程序不出错。
- 接口隔离原则(ISP,Interface Segregation Principle):客户端不应该强制依赖于它们不需要的接口。这意味着应该把大的接口分成小的更具体的接口,以提高代码的灵活性和可重用性。
- 依赖倒置原则(DIP,Dependency Inversion Principle):高层模块不应该依赖于底层模块,二者都应该依赖于抽象。这意味着在程序设计中应该依赖于抽象接口,而不是具体实现类。
面向对象编程的缺陷
- 大量的抽象和接口设计可能会增加代码的复杂性。面向对象编程需要在类、对象的层次上进行抽象和接口设计,而这些设计的过程可能非常复杂并且容易出错,可能会导致代码的可读性和可维护性降低。
- 面向对象编程可能会造成运行效率降低。在面向对象编程中,对象之间的通信需要通过消息传递的方式实现,这会带来额外的开销,可能会对程序的性能造成影响。
- 面向对象编程需要大量的设计和分析工作。在面向对象编程中,需要进行类、对象的设计和分析,这个过程可能需要大量的时间和精力,在开发周期紧张的项目中可能不太适用。
- 面向对象编程可能会造成过度设计。由于面向对象编程需要进行抽象和接口设计,程序员可能会为了达到完美的设计而过度设计,这会导致代码的复杂性增加,同时可能会对项目进度造成一定的影响。
- 面向对象编程对开发者的要求较高。面向对象编程需要开发人员具备较高的抽象思维能力和面向对象设计的能力,这会对开发者的素质提出更高的要求。
函数式编程
函数式编程(Functional Programming,简称 FP)是一种编程范式,它的核心思想是把计算过程看作是一系列函数之间的输入和输出的转换。在函数式编程中,函数被看作是一等公民,被视为一个“黑箱”,它接受输入,对其进行计算并返回输出,而不考虑它们所处的上下文环境。
函数式编程的主要特点包括:
- 纯函数:函数的输出只依赖于参数,相同的输入永远会产生相同的输出。这样可以避免副作用产生,使函数具有更好的可重用性、可移植性和可测试性。
- 不可变性:在执行某个操作时,原始数据结构不会被改变,而是返回一个新的数据结构。这样可以避免在多线程环境中出现的竞态条件,并使得代码更加简洁、易于理解。
- 高阶函数:函数可以作为参数传递给其他函数,也可以作为返回值返回到其他函数中。这样可以使得代码更加灵活、具有更高的抽象能力和可组合性。
- 延迟计算:计算过程只在需要结果的时候进行,而不是在定义时或者赋值时立即运行。这样可以提高代码的执行效率,减少资源浪费。
- 更少的状态:尽可能采用无状态(stateless)的函数,减少可变状态的使用,避免常见的副作用问题。
函数式编程适用于处理复杂的数据转换和数据处理任务,可以提高代码的可读性、可维护性和可测试性。然而,在一些对性能要求较高的场景下,函数式编程可能不太适用,因为它需要频繁地进行对象创建和销毁,并且在处理大数据量时可能需要开销很大的内存空间。