概述
继上一篇文章讲了前两章的读感,已经归纳总结的重点,这章会继续跟进的看一下,深挖架构整洁之道。
编程范式
编程范式从早期到至今,提过哪些编程范式,结构化编程,面向对象编程,函数式编程。
结构化编程
结构化编程指的是条件选择,比如if/else/while等等具有跳转含义语句,它让程序的逻辑具有结构性,作者归结为对程序控制权的直接转移进行了限制和规范。
面向对象编程
而面向对象编程,这个在从事Android开发的我来说,太熟悉不过,面向对象的特性就是封装、继承、多态,其中多态的表达,就是对程序控制权的间接转移进行了限制和规范。
和以往我了解的Android的面向对象特性的解释有所不同的是,封装我们都知道是隐藏细节,对外暴露能力。继承则是在某个作用域内(类&接口)对外部定义的某一组变量和函数进行覆盖,这里解释的是覆盖,事实上还有使用。而多态,我们所理解的同一行为,有多种不同的实现,所表达的就是重载和重写,但在这里,行为抽离出来了,接口定义的就是行为,而对接口的不同实现类似我们所说的重载和重写。它抽离出来想要表达的是什么呢?就是插件特性,所有的实现都可以看成是插件。这点很重要!
再要说的一个点就是依赖关系和控制流。三层架构的软件是什么样子的,高层调用中层,中层调用底层,他们持有对方,可以看作是依赖,而且持有的是具体的实现。系统行为决定了控制流,所以依赖不可避免的跟随程序的控制流。当底层发生了改变,随之产生的就是中层改变,进而影响高层改变,这就是这种架构的弊端。
一旦我们使用多态(这里指接口的方式),依赖关系的方向和控制流的方向相反,这就是依赖反转
ML1和接口I在源代码上的依赖关系,或叫继承关系
面向对象编程的核心本质就是这样,依赖关系不再受控制流的限制。这种能力有什么作用呢?我们可以让数据库模块和用户界面模块都依赖于业务逻辑模块,而非相反。这是书中的原话。
回想到之前讲的多态体现的插件特性,用户界面和数据库都是作为业务逻辑模块的插件,可以随时替换。也就是说,业务逻辑模块的源代码不需要引入用户界面和数据库这两个模块
函数式编程
让我不解的是函数式编程,我最早接触函数式编程的时候,当时是看的第三方库Rxjava,一种函数式编程的概念提出,通过操作符,以及函数化的格式,简化了编程逻辑,后续又应用到kotin的开发中,有许许多多的操作符。而书中的函数式编程,让我不知其解。经过查询才发现,就是我所看到过的东西。书中归结为对程序中的赋值进行了限制和规范。这句话怎么理解呢,它指明函数式编程语言中应该是没有赋值语句的,即所有操作赋值,然后输出值的逻辑,就应该是将操作直接作为参数,传递给输出的方法。
书中表达函数式编程语言中变量是不可变的,这句话说出来可能很矛盾,用书中的例子来说。
#java语言
public class Squint {
public static void main(String args[]) {
for (int i=0; i<25; i++) {
System.out.println(i*i)
}
}
}
#Clojure语言
(println(take 25 (map (fn [x](*x x))(range))))
java语法上,需要打印前25个从0自然增长数的平方写法是这样的,可见i每时每刻都在变。但是用函数式编程来看,这里换了语言来展现,同样求前25个数的平方,但是此时x就是不可变的,为什么这么说呢。
- range是返回了一个从0开始的整数无穷列表
- map是针对列表元素求平方值,产生了一个无穷多的,包含平方值的列表
- take函数,返回一个仅包含前25个元素的新列表
而只有这些列表的元素在被访问时才会被创建,所以实际上只有前25个元素是真正被创建了。既然是被创建了25个元素,你还会认为变量x是可变的吗? 书中想表达的就是这个意思吧。
这个可变和不可变在架构中体现是什么,一切问题都是由于可变导致的,因为可变性,多线程安全问题才会发生,如果一切都不可变,就不可能出现问题。既然这样,开发者应该隔离可变性,一个架构设计良好的应用应该将状态修改的部分和不需要修改状态的部分隔离成单独的组件,然后用适合的机制来保护可变量。
架构师应该着力于将大部分处理逻辑都归于不可变组件中,可变状态组件的逻辑应该越少越好。
总结
而这三个编程范式,结构化、面向对象、函数式编程对应了软件架构的功能性、组件独立性以及数据管理。我不是很能和解释对应的上,但是对于软件架构关注的点,我还是十分认可的。
最后我们应该了解,过去50年的开发历程当中,每个范式都是约束,没有范式在增加新能力,主要学到的东西是 ---- 什么不应该做!