编程范式
# 编程范式
# 面向过程编程
面向过程编程 ,也称作结构化编程或过程式编程。
结构化编程主要表现在以下三个方面:
自顶向下,逐步求精。将编写程序看成是一个逐步演化的过程,将分析问题的过程划分成若干个层次,每一个新的层次都是上一个层次的细化。
模块化。将系统分解成若干个模块,每个模块实现特定的功能,最终的系统由这些模块组装而成,模块之间通过接口传递信息。
语句结构化。在每个模块中只允许出现顺序、选择和循环三种流程结构的语句。
结构化编程的优点:
贴近图灵机模型,可以充分调动硬件,控制性强。从硬件到 OS,都是从图灵机模型层累上来的。结构化编程离硬图灵机模型比较近,可以充分挖掘底下的能力,尽量变得可控。
流程清晰。从 main 函数看代码,可以一路看下去,直到结束。
结构化编程的缺点:
数据的全局访问性带来较高的耦合复杂度,局部可复用性及响应变化能力差,模块可测试性差。想单独复用一个 Procedure 比较困难,需要将该过程函数相关的全局数据及与全局数据相关的其他过程函数(生命周期关联)及其他数据(指针变量关联)一起拎出来复用,但这个过程是隐式的,必须追着代码一点点看才能做到。同理,想要单独修改一个 Procedure 也比较困难,经常需要将关联的所有 Procedure 进行同步修改才能做到,即散弹式修改。还有一点,就是模块之间可能有数据耦合,打桩复杂度高,很难单独测试。
随着软件规模的不断膨胀,结构化编程组织程序的方式显得比较僵硬。结构化编程贴近图灵机模型,恰恰说明结构化编程抽象能力差,离领域问题的距离比较远,在代码中找不到领域概念的直接映射,难以组织管理大规模软件。
# 面向对象编程
面向对象编程的核心特点是封装、继承和多态。
封装是面向对象的根基,它将紧密相关的信息放在一起,形成一个逻辑单元。我们要隐藏数据,基于行为进行封装,最小化接口,不要暴露实现细节。
继承分为两种,即实现继承和接口继承。实现继承是站在子类的视角看问题,而接口继承是站在父类的视角看问题。很多程序员把实现继承当作一种代码复用的方式,但这并不是一种好的代码复用方式,推荐使用组合。
对于面向对象而言,多态至关重要,接口继承是常见的一种多态的实现方式。正因为多态的存在,软件设计才有了更大的弹性,能够更好地适应未来的变化。只使用封装和继承的编程方式,我们称之为基于对象编程,而只有把多态加进来,才能称之为面向对象编程。可以这么说,面向对象设计的核心就是多态的设计。
面向对象编程的优点:
对象自封装数据和行为,利于理解和复用。
对象作为“稳定的设计质料”,适合广域使用。
多态提高了响应变化的能力,进一步提升了软件规模。
对设计的理解和演进优先是对模型和结构的理解和调整。不要一上来就看代码,面向对象的代码看着看着很容易断,比如遇到虚接口,就跟不下去了。通常是先掌握模型和结构,然后在结构中打开某个点的代码进行查看和修改。请记住,先模型,再接口,后实现。
面向对象编程的缺点:
业务逻辑碎片化,散落在离散的对象内。类的设计遵循单一职责原则,为了完成一个业务流程,需要在多个类中跳来跳去。
行为和数据的不匹配协调,即所谓的贫血模型和充血模型之争。后来发现可通过 DCI(Data、Context 和 Interactive)架构来解决该问题。
面向对象建模依赖工程经验,缺乏严格的理论支撑。面向对象建模回答了从领域问题如何映射到对象模型,但一般只是讲 OOA 和 OOD 的典型案例或最佳实践,属于归纳法范畴,并没有严格的数学推导和证明。
# 函数式编程
函数式编程有很多特点:
- 函数是一等公民。一等公民的含义:
(1)它可以按需创建;
(2)它可以存储在数据结构中;
(3)它可以当作参数传给另一个函数;
(4)它可以当作另一个函数的返回值。
- 纯函数。所谓纯函数,是符合下面两点的函数:
(1)对于相同的输入,返回相同的输出;
(2)没有副作用。
惰性求值。惰性求值是一种求值策略,它将求值的过程延迟到真正需要这个值的时候。
不可变数据。函数式编程的不变性主要体现在值和纯函数上。值类似于 DDD 中的值对象,一旦创建,就不能修改,除非重新创建。值保证不会显式修改一个数据,纯函数保证不会隐式修改一个数据。当你深入学习函数式编程时,会遇到无副作用、无状态和引用透明等说法,其实都是在讨论不变性。
递归。函数式编程用递归作为流程控制的机制,一般为尾递归。
函数式编程还有两个重要概念:高阶函数和闭包。
所谓高阶函数,是指一种比较特殊的函数,它们可以接收函数作为输入,或者返回一个函数作为输出。
闭包是由函数及其相关的引用环境组合而成的实体,即闭包 = 函数 + 引用环境。
函数式编程的优点:
高度的抽象,易于扩展。函数式编程是数据化表达,非常抽象,在表达范围内是易于扩展的。
声明式表达,易于理解。
形式化验证,易于自证。
不可变状态,易于并发。数据不可变不是并发的必要条件,不共享数据才是,但不可变使得并发更加容易。
函数式编程的缺点:
对问题域的代数化建模门槛高,适用域受限。现实是复杂的,不是在每个方面都是自洽的,要找到一套完整的规则映射是非常困难的。在一些狭窄的领域,可能找得到,而一旦扩展一下,就会破坏该狭窄领域,你发现以前找到的抽象代数建模方式就不再适用了。
在图灵机上性能较差。函数式编程增加了很多中间层,它的规则描述和惰性求值等使得优化变得困难。
不可变的约束造成了数据泥团耦合。领域对象是有状态的,这些状态只能通过函数来传递,导致很多函数有相同的入参和返回值。
闭包接口粒度过细,往往需要再组合才能构成业务概念。
参考:聊聊编程范式 https://zhuanlan.zhihu.com/p/354528902 (opens new window)