编程范式
三个编程范式:结构化编程,面向对象编程以及函数式编程。每种编程范式都从某一方面限制和规范了程序员的能力,没有一个范式是增加新能力的。这些范式主要是为了告诉我们不能做什么,而不是可以做什么。三个编程范式分别限制了goto语句,函数指针和赋值语句的使用。
1 结构化编程
结构化编程对程序控制权的直接转移进行了限制和规范。
结构化编程是第一个被普遍采用的编程范式,由Dijkstra最先提出:无限制跳转的goto语句会损害程序的整体结构,主张使用条件判断语句和循环语句来代替跳转语句。
1.1 goto是有害的
goto语句的某些用法会导致模块无法被拆分成更小的可证明的单元,这会导致无法采用分解法来将大型问题进一步拆分成更小的可证明的部分。
1.2 功能性降解拆分
可以将一个大型问题拆分为一系列高级函数的组合,而这些高级函数各自又可以继续被拆分为一系列低级函数,如此无限递归。每个被拆分出来的函数也都可以采用结构化编程范式来书写。
2 面向对象编程
面向对象编程对程序控制权的间接转移进行了限制。
利用多态来限制用户对函数指针的访问。
2.1 封装
经常提及的对数据和函数的封装,并不是面向对象编程特有的。C语言的头文件和实现文件分离的方式,提供了更好的封装性。java/C++通过在编程语言层面引入public,private和protected这些关键词,部分维护了封装性。
2.2 继承
继承的主要作用是让我们可以在某个作用域内对外部定义的一组函数或者变量进行覆盖。
C语言可以通过指针类型强制转换来达成此类效果,这种方式并不像继承这样便利易用,在真正的面向对象编程语言中,类型的向上转换通常是隐形的。
2.3 多态
C语言可以使用函数指针模拟多态。用函数指针显示实现多态的问题就在于函数指针的危险性。函数指针的调用依赖于一系列需要认为遵守的约定。程序员必须严格按照固定约定来初始化函数指针,并同样严格地按照约定来使用这些函数指针。只要有一个程序员没有遵守这个约定,整个程序就会产生难以排查和消除的bug。面向对象编程语言消除了人工遵守这些约定的必要,让多态变得更加安全和易于使用了。
2.3.1 多态的强大
依赖反转
多态之前的依赖关系:高层模块依赖底层模块,源代码层面的依赖跟随程序的控制流。
使用多态的依赖:高层模块和低层模块都依赖与抽象,该依赖的方向和控制流相反,称之为依赖反转。通过多态,无论怎样的源代码级别的依赖关系,都可以将其反转。
面向对象编程的含义:面向对象编程就是以多态为手段来对源代码中的依赖关系进行控制的能力,这种能力让软件架构师可以构建出某种插件式架构,让高层策略性组件与低层实现性组件相分离,低层组件可以被编译成插件,实现独立于高层组件的开发和部署,实现OCP的目标(对扩展开放,对修改封闭);
3 函数式编程
函数是编程对程序中的赋值进行了限制和规范。
函数式编程由λ演算衍生出来。λ演算法的一个核心思想是不可变性 -- 某个符号所对应的值是永远不变的。大部分函数式编程语言只有在非常严格的限制条件下,才可以更改某个变量的值。
3.1 不可变性与软件架构
所有的竞争问题,死锁问题和并发更新问题都是有可变变量导致的。一切并发应用遇到的问题,如果没有可变变量的话都不会发生。
3.2 可变性的隔离
一种常见方式是将应用程序划分为可变的和不可变的两种组件。不可变组件用纯函数的方式来执行任务,期间不更改任何状态。不可变组件通过与可变组件通信的方式来修改变量状态。
3.3 事件溯源
只存储事务记录,不存储具体状态。当需要具体状态时,从头开始计算所有的事务即可。以银行账户为例,不保存具体账户余额,仅保存操作日志,当查看余额时,取出全部交易记录,并且每次从头开始累计。我们将需要无限容量的存储和无限的处理能力。但是可能我们并不需要这个设计永远可行,而且在整个程序的生命周期内,我们有足够的存储和处理能力。这种模式不存在删除和更新的情况,不是CRUD,而是CR。