整洁架构之道--三种经典的编程范式
Posted 网管叨bi叨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了整洁架构之道--三种经典的编程范式相关的知识,希望对你有一定的参考价值。
本文是《Clean Architecture》--整洁架构之道中关于编程范式相关章节的笔记,首发于公众号「Go 招聘」 这和软件架构的三大关注重点不谋而合:功能性、组件独立性以及数据管理。
的方式。回答此问题的同时另外还会搬出这三个词语:封装(encapsulation)、继承(inheritance)、多态(polymorphism)。其隐含意思就是说面向对象编程是这三项的有机组合,或者任何一种支持面向对象的编程语言必须支持这三个特性。函数和distance()
函数,但对它们来说,Point 这个数据结构体的内部细节,以及函数的具体实现方式都是不可见的。point.h
这个经验从何而来呢?因为一度所有程序都是设备相关的,但是后来我们发现自己其实真正需要的是在不同的设备上实现同样的功能。归根结底,多态其实不过就是函数指针的一种应用。自从 20 世纪 40 年代末期冯·诺依曼架构诞生那天起,程序员们就一直在使用函数指针模拟多态了。也就是说,面向对象编程在多态方面没有提出任何新概念。依赖反转可以使数据库模块和用户界面模块都依赖于业务逻辑模块。我们让用户界面和数据库都成为业务逻辑的插件。也就是说,业务逻辑模块的源代码不需要引入用户界面和数据库这两个模块。
这样一来,业务逻辑、用户界面以及数据库就可以被编译成三个独立的组件或者部署单元(例如 jar 文件、DLL 文件、Gem 文件等)了,这些组件或者部署单元的依赖关系与源代码的依赖关系是一致的,业务逻辑组件也不会依赖于用户界面和数据库这两个组件。
业务逻辑组件就可以独立于用户界面和数据库来进行部署了,我们对用户界面或者数据库的修改将不会对业务逻辑产生任何影响,这些组件都可以被分别独立地部署。
如果系统中的所有组件都可以独立部署,那它们就可以由不同的团队并行开发,这就是所谓的独立开发能力。
如果变量永远不会被更改,那就不可能产生竞争或者并发更新问题。如果锁状态是不可变的,那就永远不会产生死锁问题。作为一个软件架构师,当然应该要对并发问题保持高度关注。我们需要确保自己设计的系统在多线程、多处理器环境中能稳定工作。
可变性的隔离一种常见方式是将应用程序,或者是应用程序的内部服务进行切分,划分为可变的和不可变的两种组件。不可变组件用纯函数的方式来执行任务,期间不更改任何状态。这些不可变的组件将通过与一个或多个非函数式组件通信的方式来修改变量状态(参见图 6.1)。
由于状态的修改会导致一系列并发问题的产生,所以我们通常会采用某种事务型内存来保护可变变量,避免同步更新和竞争状态的发生。事务型内存基本上与数据库保护磁盘数据的方式 1 类似,通常釆用的是事务或者重试机制。
这里的要点是:一个架构设计良好的应用程序应该将状态修改的部分和不需要修改状态的部分隔离成单独的组件,然后用合适的机制来保护可变量。
软件架构师应该着力于将大部分处理逻辑都归于不可变组件中,可变状态组件的逻辑应该越少越好。
事件溯源这里举了个简单的例子,假设某个银行应用程序需要维护客户账户余额信息,当它放行存取款事务时,就要同时负责修改余额记录。
如果我们不保存具体账户余额,仅仅保存事务日志,那么当有人想查询账户余额时。我们就将全部交易记录取出,并且每次都得从最开始到当下进行累计。当然,这样的设计就不需要维护任何可变变量了。
事件溯源,在这种体系下,我们只存储事务记录,不存储具体状态。当需要具体状态时,我们只要从头开始计算所有的事务即可。
这种数据存储模式中不存在删除和更新的情况,我们的应用程序不是 CRUD,而是 CR。因为更新和删除这两种操作都不存在了,自然也就不存在并发问题。如果我们有足够大的存储量和处理能力,应用程序就可以用完全不可变的、纯函数式的方式来编程。
小结结构化编程是多对程序控制权的直接转移的限制。 面向对象编程是对程序控制权的间接转移的限制。 函数式编程是对程序中赋值操作的限制。 每个范式都约束了某种编写代码的方式,没有一个编程范式是在增加新能力。
我们必须面对这种不友好的现实:软件构建并不是一个迅速前进的技术。今天构建软件的规则和 1946 年阿兰·图灵写下电子计算机的第一行代码时是一样的。尽管工具变化了,硬件变化了,但是软件编程的核心没有变。
总而言之,软件,或者说计算机程序无一例外是由顺序结构、分支结构、循环结构和间接转移这几种行为组合而成的,无可增加,也缺一不可。
总结名言警句:
三个编程范式,它们分别是结构化编程(structured programming)、 面向对象编程(object-oriented programming)以及函数式编程(functional programming)。 结构化编程范式:对程序控制权的直接转移进行了限制和规范。 面向对象编程范式:对程序控制权的间接转移进行了限制和规范。 函数式编程范式:对程序中的赋值进行了限制和规范。 面向对象编程在封装性上得 0 分,在继承性上勉强可以得 0.5 分(满分为 1)。 多态是我们跨越架构边界的手段,函数式编程是我们规范和限制数据存放位置与访问权限的手段,结构化编程则是各模块的算法实现基础。 如果系统中的所有组件都可以独立部署,那它们就可以由不同的团队并行开发,这就是所谓的独立开发能力。 面向对象编程就是以对象为手段来对源代码中的依赖关系进行控制的能力。 所有的竞争问题、死锁问题、并发更新问题都是由可变变量导致的。 一个架构设计良好的应用程序应该将状态修改的部分和不需要修改状态的部分隔离成单独的组件,然后用合适的机制来保护可变量。 每个范式都约束了某种编写代码的方式,没有一个编程范式是在增加新能力。 软件,或者说计算机程序无一例外是由顺序结构、分支结构、循环结构和间接转移这几种行为组合而成的,无可增加,也缺一不可。 关于整洁架构之道的第二部分关于三种编程范式的记录今天就介绍到这里了。第三部分从设计原则(SOLID)开始,敬请期待。如果有不同见解欢迎留言讨论。
欢迎关注Go招聘公众号,获取Go专题、大厂内推、面经、简历、股文等相关资料。
架构整洁之道 3~6章读书笔记
第2部分 从基础构件开始:编程范式
第3章 编程范式总览
三个编程范式包括:结构化编程(structured programming)、面向对象编程(object-oriented programming)以及函数式编程(functional programming)。
结构化编程
结构化编程对程序控制权的直接转移进行了限制和规范。
面向对象编程
面向对象编程对程序控制权的间接转移进行了限制和规范。
函数式编程
函数式编程对程序中的赋值进行了限制和规范。
仅供思考
没有一个范式是增加新能力的。也就是说,每个编程范式的目的都是设置限制。这些范式主要是为了告诉我们不能做什么,而不是可以做什么。
这三个编程范式分别限制了goto语句、函数指针和赋值语句的使用。
本章小结
多态是我们跨越架构边界的手段,函数式编程是我们规范和限制数据存放位置与访问权限的手段,结构化编程则是各模块的算法实现基础。这和软件架构的三大关注重点不谋而合:功能性、组件独立性以及数据管理。
第4章 结构化编程
科学理论和科学定律的特点:它们可以被证伪,但是没有办法被证明。
我们可以说数学是要将可证明的结论证明,而与之相反,科学研究则是要将可证明的结论证伪。
Dijkstra曾经说过“测试只能展示Bug的存在,并不能证明不存在Bug”
结构化编程范式中最有价值的地方就是,它赋予了我们创造可证伪程序单元的能力。
无论在哪一个层面上,从最小的函数到最大组件,软件开发的过程都和科学研究非常类似,它们都是由证伪驱动的。软件架构师需要定义可以方便地进行证伪(测试)的模块、组件以及服务。为了达到这个目的,他们需要将类似结构化编程的限制方法应用在更高的层面上。
第5章 面向对象编程
封装
C程序在头文件中进行数据结构以及函数定义的前置声明(forward declare),然后在程序文件中具体实现。程序文件中的具体实现细节对使用者来说是不可见的。C++作为一种面向对象编程语言,反而破坏了C的完美封装性。
继承
继承的主要作用是让我们可以在某个作用域内对外部定义的某一组变量与函数进行覆盖。
虽然面向对象编程在继承性方面并没有开创出新,但是的确在数据结构的伪装性上提供了相当程度的便利性。
多态
归根结底,多态其实不过就是函数指针的一种应用。
采用面向对象编程语言让多态实现变得非常简单,让一个传统C程序员可以去做以前不敢想的事情。综上所述,我们认为面向对象编程其实是对程序间接控制权的转移进行了约束。
程序应该与设备无关
依赖反转
关系的方向和控制流正好是相反的,我们称之为依赖反转。这种反转对软件架构设计的影响是非常大的。
通过依赖反转,软件架构师可以完全控制采用了面向对象这种编程方式的系统中所有的源代码依赖关系,而不再受到系统控制流的限制。不管哪个模块调用或者被调用,软件架构师都可以随意更改源代码依赖关系。
简单来说,当某个组件的源代码需要修改时,仅仅需要重新部署该组件,不需要更改其他组件,这就是独立部署能力。
如果系统中的所有组件都可以独立部署,那它们就可以由不同的团队并行开发,这就是所谓的独立开发能力。
本章小结
面向对象编程就是以多态为手段来对源代码中的依赖关系进行控制的能力,这种能力让软件架构师可以构建出某种插件式架构,让高层策略性组件与底层实现性组件相分离,底层组件可以被编译成插件,实现独立于高层组件的开发和部署。
第6章 函数式编程
函数式编程语言中的变量(Variable)是不可变(Vary)的。
不可变性与软件架构
一切并发应用遇到的问题,一切由于使用多线程、多处理器而引起的问题,如果没有可变变量的话都不可能发生。
可变性的隔离
一种常见方式是将应用程序,或者是应用程序的内部服务进行切分,划分为可变的和不可变的两种组件。不可变组件用纯函数的方式来执行任务,期间不更改任何状态。这些不可变的组件将通过与一个或多个非函数式组件通信的方式来修改变量状态。
一个架构设计良好的应用程序应该将状态修改的部分和不需要修改状态的部分隔离成单独的组件,然后用合适的机制来保护可变量。
软件架构师应该着力于将大部分处理逻辑都归于不可变组件中,可变状态组件的逻辑应该越少越好。
事件溯源
在事件溯源体系下,我们只存储事务记录,不存储具体状态。当需要具体状态时,我们只要从头开始计算所有的事务即可。
这种数据存储模式中不存在删除和更新的情况,我们的应用程序不是CRUD,而是CR。因为更新和删除这两种操作都不存在了,自然也就不存在并发问题。
本章小结
软件,或者说计算机程序无一例外是由顺序结构、分支结构、循环结构和间接转移这几种行为组合而成的,无可增加,也缺一不可。
以上是关于整洁架构之道--三种经典的编程范式的主要内容,如果未能解决你的问题,请参考以下文章