计算机编程发展至今,一共只有三个编程范式:

  • 结构化编程
  • 面向对象编程
  • 函数式编程

编程范式和软件架构的关系

  • 结构化编程是各个模块的算法实现基础
  • 多态(面向对象编程)是跨越架构边界的手段
  • 函数式编程是规范和限制数据存放位置与访问权限的手段

软件架构的三大关注重点功能性组建独立性以及数据管理,和编程范式不谋而合

结构化编程

限制控制权的直接转移,禁止 goto,用 if/else/while 替代

  • Dijkstra 发现:goto 语句的某些用法会导致模块无法被递归拆分成更小的、可证明的单元,这会导致无法采用分解法将大型问题进一步拆分成更小的、可证明的部分。
  • Bohm 和 Jocopini 证明了:可以用顺序结构、分支结构、循环结构构造出任何程序
  • 测试只能证明 Bug 的存在,并不能证明不存在 Bug
  • 结构化编程范式的价值:赋于我们创构建可证伪程序单元的能力。如果测试无法证伪这些函数,就可以认为这些函数足够正确
  • 在架构设计领域,功能性讲解拆分仍然是最佳实践之一

面向对象编程

限制控制权的间接转移,禁用函数指针,用多态替代

什么是面向对象?
  • 数据与函数的组合?

    • o.f() 和 f(o) 没有区别
  • 对真实世界进行建模的方式?
    • 到底如何进行?为什么这么做?有什么好处?
    • 面向对象编程究竟是什么?
  • 封装、继承、多态?
    • 面向对象编程语言必须支持这三个特性
封装

把一组关联的数据和函数管理起来,外部只能看见部分函数,数据则完全不可见。

封装并不是面向对象语言特有的,C 语言也支持,而且是完美的支持。

point.h

  1. struct Point;
  2. struct Point* makePoint(double x, double y);
  3. double distance(struct Point *p1, struct Point *p2)

利用 forward declaration,Point 的数据结构、内部实现对 point.h 的使用者完全不可见。

而后来的 C++ 虽然是面向对象的编程语言,但却破坏了封装性:

point.h

  1. class Point {
  2. public:
  3. Point(double x, double y);
  4. double distance(const Point& p1, const Point& p2);
  5. private:
  6. double sqrt(double x);
  7. private:
  8. double x;
  9. double y;
  10. };

C++ 编译器需要知道类的对象大小,因此必须在头文件中看到成员变量的定义。虽然 private 限制了使用者访问私有成员,但这样仍然暴露了类的内部实现。(C++ 的 PIMPL 惯用法可以在一定程度上缓解这个问题)

Java 和 C# 抛弃了头文件、实现分离的编程方式,进一步削弱了封装性,因为无法区分类的声明和定义。

继承

C 语言也支持继承

namedPoint.h

  1. struct NamedPoint;
  2. struct NamedPoint* makeNamedPoint(double x, double y, char* name);
  3. void setName(struct NamePoint *np, char* name);
  4. char* getName(struct NamedPoint *np);

namedPoint.c

  1. #include "namePoint.h"
  2. struct NamedPoint {
  3. double x;
  4. double y;
  5. char* name;
  6. };
  7. // 或者
  8. #include "point.h"
  9. struct NamePoint {
  10. Point parent_;
  11. char* name;
  12. };
  13. // 省略其他函数实现

main.c

  1. #include "point.h"
  2. #include "namedPoint.h"
  3. int main() {
  4. struct NamePoint* p1 = makeNamedPoint(0.0, 0.0, "origin");
  5. struct NamePoint* p2 = nameNamePoint(1.0, 1.0, "upperRight");
  6. // C 语言中的继承需要强制转换 p1、p2 的类型
  7. // 真正的面向对象语言一般可以自动将子类转成父类指针/引用
  8. distance((struct Point*)p1, (struct Point*)p2);
  9. }

在 main.c 中,NamePoint 被当作 Point 来使用。之所以可以,是因为 NamePoint 是 Point 的超集,且共同成员的顺序一致。C++ 中也是这样实现单继承的。

多态

在面向对象语言发明之前,C 语言也支持多态。

UNIX 要求每个 IO 设备都提供 open、close、read、write、seek 这 5 个标准函数:

  1. struct FILE {
  2. void (*open)(char* name, int mode);
  3. void (*close)();
  4. int (*read)();
  5. void (*write)(char);
  6. void (*seek)(long index, int mode);
  7. };

这里的 FILE 就相当于一个接口类,不同的 IO 设备有各自的实现函数,通过设置函数指针指向不同的实现来达到多态的目的。上层的功能逻辑只依赖 FILE 结构体中的 5 个标准函数,并不关心具体的 IO 设备什么。更换 IO 设备也无需修改功能逻辑的代码,IO 只是功能逻辑的一个插件

C++ 中每个虚函数的地址都记录在一个叫 vtable 的数据结构中,带有虚函数的类会有一个隐藏的指向 vtable 的虚表指针,每次调用虚函数都会先查询 vtable,子类构造函数负责将子类虚函数地址加载到对象的 vtable 中。

多态本质上就是函数指针的一种应用。用函数指针实现多态的问题在于函数指针的危险性。依赖人为遵守一系列的约定很容易产生难以跟踪和调试的 bug。面向对象编程使得多态再不需要依赖人工遵守约定,可以更简单、更安全地实现复杂功能。面向对象编程的出现使得“插件式架构”普及开来。

此外,面向对象编程的带来的另一个重大好处是依赖反转:通过引入接口,源码的依赖关系不再受到控制流的限制,软件架构师可以轻易地更改源码的依赖关系。这也是面向对象编程范式的核心本质(关于依赖反转,后面会单独用一篇来介绍)。

函数式编程

限制赋值操作

  • 函数式编程中的变量不可变

  • 不可变性是软件架构需要考虑的重点,因为所有的并发、死锁、竞争问题都是可变变量导致的,如果变量不可变,就不会有这些问题

  • 架构设计良好的程序应该拆分成可变、不可变两种组件,其中可变状态组件中的逻辑越少越好

  • 事件溯源:只存储事务记录,不存储具体状态;需要状态时,从头计算所有事务。

    • 例如银行程序只保存每次的交易记录,不保存用户余额,每次查询余额时,将全部交易记录取出累计
    • 这种模式只需要 CR (Create & Retrieve),不需要 UD (Update & Delete),没有了更新和删除操作,自然也不存在病发问题
    • 缺点:对存储和处理能力要求较高(但随着技术的发展,这方面将越来越不成问题)
    • 应用:git

总结

所有三个范式都是限制了编码方式,而不是增加新能力

  • 结构化编程:限制控制权的直接转移,禁止 goto,用 if/else/while 替代
  • 面向对象编程:限制控制权的间接转移,禁用函数指针,用多态替代
  • 函数式编程:限制赋值操作

三个编程范式都是在 1958 - 1968 年间提出,此后再也没有新的范式提出,未来几乎不可能再有新的范式。因为除了 goto 语句、函数指针、赋值语句之外,也没有什么可以限制的了。

软件编程的核心没有变:计算机程序无一例外是由顺序结构、分支结构、循环结构和间接转移这几种行为组合而成的,无可增加, 也缺一不可。

《架构整洁之道》学习笔记 Part 2 编程范式的更多相关文章

  1. 孙鑫VC学习笔记:多线程编程

    孙鑫VC学习笔记:多线程编程 SkySeraph Dec 11st 2010  HQU Email:zgzhaobo@gmail.com    QQ:452728574 Latest Modified ...

  2. Hadoop学习笔记(7) ——高级编程

    Hadoop学习笔记(7) ——高级编程 从前面的学习中,我们了解到了MapReduce整个过程需要经过以下几个步骤: 1.输入(input):将输入数据分成一个个split,并将split进一步拆成 ...

  3. WCF学习笔记之事务编程

    WCF学习笔记之事务编程 一:WCF事务设置 事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元: WCF通过System.ServiceModel.TransactionFlowA ...

  4. java学习笔记15--多线程编程基础2

    本文地址:http://www.cnblogs.com/archimedes/p/java-study-note15.html,转载请注明源地址. 线程的生命周期 1.线程的生命周期 线程从产生到消亡 ...

  5. java学习笔记14--多线程编程基础1

    本文地址:http://www.cnblogs.com/archimedes/p/java-study-note14.html,转载请注明源地址. 多线程编程基础 多进程 一个独立程序的每一次运行称为 ...

  6. Python学习笔记6 函数式编程_20170619

    廖雪峰python3学习笔记: # 高阶函数 将函数作为参数传入,这样的函数就是高阶函数(有点像C++的函数指针) def add(x, y): return x+y def mins(x, y): ...

  7. postgresql修炼之道学习笔记(1)

    好好学习吧. 本笔记 仅作为摘要记录 前两章,主要是数据库对比和安装等. 对比,就比多说了,总是和别人比较,会显得自己身价低,呵呵. 安装也有很多文章,不多说. 第二章提到了一些简单的配置, 其在 d ...

  8. 《Hadoop大数据架构与实践》学习笔记

    学习慕课网的视频:Hadoop大数据平台架构与实践--基础篇http://www.imooc.com/learn/391 一.第一章 #,Hadoop的两大核心:     #,HDFS,分布式文件系统 ...

  9. postgresql修炼之道学习笔记(2)

    随后的章节  介绍了基础的sql,这个我略过了,我喜欢在开发的时候,慢慢的研究,毕竟有oracle的基础. 现在,学习psql工具 使用create database创建数据库的时候,出现如下问题: ...

  10. 我读<代码整洁之道>--读书笔记整理

    第一章 整洁代码 "我可以列出我留意到的整洁代码的所有特点,但其中有一条是根本性的,整洁的代码总是看起来像是某位特别在意他的人写的.几乎没有改进的余地,代码作者设么都想到了,如果你企图改进它 ...

随机推荐

  1. 【Java SE】IO流

    1.File类 ①File类的一个对象代表一个文件或一个文件目录 ②File类声明在java.io下 1.1 FIle类的声明 路径分隔符 Windows和DOS系统默认使用'',UNIX和URL使用 ...

  2. Django框架——手写web框架、wsgiref模块、动静态网页、jinja2模块、主流web框架、Django简介、基本使用、app概念、目录结构、三板斧

    web应用 '''通过浏览器访问的应用程序!!!''' 1.两种模式c/s b/s B/S:browser---------------->server 2.web应用程序的有点 2.1 只需要 ...

  3. vue导入Excel数据并展示成表格

    前言: 用到的库参考链接: FileReader:https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader    这个在之前的下载exce ...

  4. IE盒模型和标准盒模型之间的差别

    1.W3C标准盒子模型 w3c盒子模型的范围包括margin.border.padding.content,并且content部分不包含其他部分 2.IE盒子模型 IE盒子模型的范围包括margin. ...

  5. [C++基础入门] 1、C++初识

    文章目录 1 C++初识 1.1 第一个C++程序 1.1.1 创建项目 1.1.2 创建文件 1.1.3 编写代码 1.1.4 运行程序 1.2 注释 1.3 变量 1.4 常量 1.5 关键字 1 ...

  6. VMware虚拟机---Ubuntu无法连接网络该怎么解决?

    在学习使用Linux系统时,由于多数同学们的PC上多是Windows系统,故会选择使用VMware创建一个虚拟机来安装Linux系统进行学习. 安装完成之后,在使用时总是会遇到各种各样的问题.本片随笔 ...

  7. 雪球 app 实战(1)

    开头 因为理论篇结束之后,需要一个实战,估选用了雪球app作为一个作业 业务场景: 雪球 app 自选设置(入口位于 行情 模块) 作业内容 使用 百度脑图 编写 思维导图 [自选设置]模块的测试用例 ...

  8. 2022-12-20:二狗买了一些小兵玩具,和大胖一起玩, 一共有n个小兵,这n个小兵拍成一列, 第i个小兵战斗力为hi,然后他们两个开始对小兵进行排列, 一共进行m次操作,二狗每次操作选择一个数k,

    2022-12-20:二狗买了一些小兵玩具,和大胖一起玩, 一共有n个小兵,这n个小兵拍成一列, 第i个小兵战斗力为hi,然后他们两个开始对小兵进行排列, 一共进行m次操作,二狗每次操作选择一个数k, ...

  9. 2021-11-23:规定:L[1]对应a,L[2]对应b,L[3]对应c,...,L[25]对应y。 S1 = a, S(i) = S(i-1) + L[i] + reverse(invert(S(

    2021-11-23:规定:L[1]对应a,L[2]对应b,L[3]对应c,-,L[25]对应y. S1 = a, S(i) = S(i-1) + L[i] + reverse(invert(S(i- ...

  10. 2021-08-31:去除重复字母。给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。力扣316。

    2021-08-31:去除重复字母.给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次.需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置).力扣316. 福大大 答案 ...