您现在的位置是:首页 >技术杂谈 >简洁架构之道_读书笔记网站首页技术杂谈
简洁架构之道_读书笔记
1. 编程范式
1.结构化编程
Bohm和jocopini证明了可以使用顺序结构、分支结构、循环结构构造出任何程序
goto是有害的
顺序结构、分支结构和循环结构均可以证明其正确性,并且用这三种结构可以构造出任何程序。那么这就意味着可以将模块可以按功能进行降解拆分为一系列函数,而这些函数可以用结构化编程范式来写。只要这些函数是正确的,那么整个程序就是正确的。
Dijstra:测试只能展示bug的存在,不能证明bug不存在
结构化编程方式最有价值的地方在于它赋予了我们创造可以证伪程序单元的能力。这就是现代编程语言一般不支持无限制goto语句的原因。更重要的是,这也是为什么在架构设计领域,功能降解性拆分仍是最佳实现之一。
无论在哪一个层面上,从最小的函数到最大的组件,软件开发过程都是由证伪驱动的。软件架构师需要定义可以方便的进行证伪的模块、组件以及服务。为了达到这个目的,它们需要将类似结构化编程的限制方法应用在更高的层面上。
具体的限制性方法待补充。。。。
2.面向对象编程
什么是面向对象?
回答1:面向对象是数据与函数的组合。这种回答方式并不贴切。因为他似乎暗示了o.f()与f(o)之间的有区别。
回答2:面向对象编程时一种对真实世界进行建模的方式。这句话似乎意味着“由于采用面向对象方式构建的软件与真实世界的关系更加紧密,所以面向对象编程使软件开发更加容易”。但是该回答并没有说清楚——面向对象编程就是什么
回答3:面向对象编程就是封装、继承、多态。这只是面向对象编程的特性。
封装:
封装特性并不是面向对象编程所独有的。其实C语言也支持完成的封装,并且更加完美。而C++作为一种面向对象语言,反而破坏了C的完美封装性。因为C++编译器要求类的成员变量必须在类的头文件中声明(可以使用IMPL方式避免,但终归是一种规避方式)。
基于上述原因,很难说封装是面向对象的必要条件,事实上,有很多面向对象编程语言对封装性没有强制性的要求。
继承:
继承的主要作用是让我们可以在某个作用域内对外部定义的一组变量与函数进行覆盖。这事实上也是C程序员在面向对象发明之前一直做的事。只不过面向对象编程对原有的继承方式进行了拓展,支持了多重继承。
多态:
多态其实不过是函数指针的一种应用。自20世纪40年代冯诺依曼架构诞生的那一天起,程序员们就一直在使用函数指针模拟多态了。所以面向对象编程在多态方面没有提出任何新的概念,但是面向对象编程让多态变更更加安全,更加便于使用。
面向对象编程其实是对程序间接控制权的转移进行了约束。也就是说,非面向对象编程实现多态时是直接使用函数指针,只要符合规则,可以在使用函数指针在任意的函数之间转换。而面向对象编程使多态只能在继承关系中进行使用,从而达到了对程序间接控制权的转移进行了约束。
依赖反转: 具体实现模块与接口在源代码上的依赖关系(或者叫继承关系)与控制流正好是相反的,我们称之为依赖反转。
一般情况下,在软件架构中,系统行为决定了控制流,控制流决定了源代码的依赖关系。但是这种软件架构拓展性差,死板、不够灵活。如果利用面向对象编程提供的多态实现,我们可以将这种依赖关系进行反转。
通过多态,软件架构师可以完全控制采用里面向对象编程方式的系统中所有的源代码依赖关系。而不再受到系统控制流的限制。——这是面向对象编程范式的核心本质
对于软件架构师来说,面向对象的含义非常明确:面向对象编程就是以多态为手段对源代码中的依赖关系进行控制的能力,这种能力让软件架构师可以构建出某种插件式架构,让高层策略性组件与底层实现性组件分离,底层组件可以被编译成插件,实现独立于高层组件的开发和部署。
3.函数式编程
为什么不可变性是软件架构设计需要考虑的重点问题?
所有的竞争问题、死锁问题、并发更新问题都是由可变变量导致的。如果变量永远不变,那就不可能产生竞争或者并发更新问题。
总结:
- 结构化编程对程序直接控权的转移进行了限制
- 面向对象编程对程序间接控制权的转移了控制
- 函数式编程对程序中赋值操作进行了限制
2. 设计原则
1.设计原则
SOLID原则的主要作用就是告诉我们如何将数据和函数组织成类,以及如何将这些类连接起来称为程序。
SOLIID原则应该直接紧贴于具体的代码逻辑之上,这些原则是用来帮助我们定义软件架构中组件和模块的。
1.SRP(单一职责原则):每个软件模块都有且只有一个需要被修改的理由
2.OCP(开闭原则):对拓展开放对修改关闭
3.LSP(里氏替换原则):如果想用可替换的组件来构建软件系统,这些组件就必须遵守共同约定,以便让这些组件可以相互替换。
4.ISP(接口隔离原则):在设计中应当避免不必要的依赖。
5.DIP(依赖反转原则):高层策略性的代码不应该依赖实现层细节的代码,相反,实现底层细节的代码应当依赖高层策略性的代码
1.1 单一职责原则
单一职责原则:
- 对于底层实现细节,要求一个函数只完成一个功能。
- 对于一个软件模块而言,要求任何一个软件模块都应该支队某一类行为者负责。
此处的软件模块一组紧密相关的函数和数据结构。
违反单一职责的案例:Employee类–> 解决方式: 使用facade设计模式
单一职责原则主要讨论的是函数和类之间的关系,但是在不同的讨论层面上会以不同的形式出现。在组件层面上,我们可以将其称为共同闭包原则,在软件架构层面,它则是用于奠定架构边界的变更轴心。
1.2 开闭原则
一个良好的计算机系统应该是在不需要修改的前提下就可以轻易被拓展————这是研究软件架构的根本目的。
软件架构师可以根据相关函数被修改的原因,修改的方式及修改的时间来对其进行分组隔离,并将这些相互隔离的函数分组整理成组件结构,使得高阶组件不会因为低阶组件被修改而受到影响。
OCP是我们进行系统架构设计的主导原则,其目的是让系统更加易于拓展,同时限制其每次被修改所影响的范围。实现方式是通过将系统划分为一系列组件,并将这些组件间的依赖关系按层次结构进行组织,使得高阶组件不会因为低阶组件被修改而受到影响。
1.3 里氏替换原则
里氏替换原则应用于类设计时,表达的意思是:子类对象可以在程序中替换父类对象。
违反里氏替换原则的案例: 长方形作为正方形的父类
里氏替换原则可以且应该被应用于软件架构层面,因为一旦违反了可替换性,该系统架构就不得不为此添加大量复杂的应对机制。
1.4 接口隔离原则
接口(抽象类)隔离原则要求在开发过程中设计接口时,使用多个专门的接口而不是使用单一的庞大臃肿的总接口,一个类对另一个类的依赖应该是建立在最小的接口上的。要做到接口与角色一一对应,不应该让一个接口承担多个角色,同时也不应该让一个角色由多个接口承担。
在一般情况下,任何层次的软件设计如果依赖于不需要的东西,都是有害的。从源代码层次来讲,这样的依赖关系会导致不必要的重新编译和重新部署,对于更高层次的软件架构设计而言,也是一样的道理。
1.5 依赖反转原则
依赖反转原则主要的意思是:如果想要设计一个灵活的系统,在源代码层次的依赖关系中就应该多引用抽象类型,而非具体实现。
我们在修改抽象接口的时候,一定会去修改对应的具体实现。但是返回来,当我们修改具体实现时,却很少需要修改相应的抽象接口。所以我们可以认为接口比实现稳定。
想要在软件架构上追求稳定,那么就应该使用稳定的抽象接口,少依赖多变的具体实现。
依赖反转原则归结为具体的编码守则:
- 应在代码中多使用抽象接口,尽量避免使用多变的具体实现类。
- 不要在具体实现类上创建衍生类。
- 不要覆盖包含具体实现的函数。
- 应该避免在代码中写入与任何具体实现相关的名字,或者是其他容易变动的事务的名字。
3. 组件构建原则
1.组件
组件:组件是软件的部署单元,是整个软件系统在部署过程中可以独立完成部署的最小实体。
程序运行时插入某些动态链接文件。这些动态链接文件所使用的就是软件架构中的组件概念。组件化的插件式架构已经称为我们习以为常的软件构建形式了。
2.组件聚合
究竟哪些类应该被组合成一个组件? 一般情况下,组件的构建应当遵循三个基本原则。
- REP: 复用/发布等同原则
- CCP: 共同闭包原则
- CRP: 共同复用原则
2.1 复用/发布等同原则
软件复用的最小粒度应当等同于它发布的最小粒度。
复用发布等同原则就是指组件中的类和模块必须是彼此紧密相关的。也就是说,一个组件不能由一组毫无关联的类和模块组成,它们之间应该有一个共同的主体或者大方向。
2.2 共同闭包原则
我们应当将那些会同时修改,并且为相同目的而修改的类放在同一个组件中。而将不会同时修改,并且不会为了相同目的而修改的那些类放到不同的组件中。
2.3 共同复用原则
通常情况下,类很少会被单独复用,更通常的情况是多个类同时作为某个可复用的抽象定义被共同复用。共同复用原则指导我们将这些类放在同一个组件中。
不要依赖不需要用到的东西。
2.4 组件聚合张力图
三个原则之间存在竞争关系。
REP和CCP原则是粘合性原则,他们会让组件变得更大。而CRP原则是排除性原则,它会尽量让组件变小。软件架构师的任务是在三个原则之间做取舍。
3.组件耦合
组件耦合主要关注的是组件之间的关系。
3.1 无依赖环原则(ADP)
组件依赖关系环中不应该出现环。
当组件依赖关系图中出现环时,可以有两种方式打破循环依赖:
- 应用依赖反转原则。
- 创建一个新的组件。
注意: 组件依赖结构图并不是用来描述应用程序功能的,它更像是应用程序在构建性和维护性方面的一张图。
组件结构图一个重要目标就是指导如何隔离频繁的变更。
3.2 稳定依赖原则
组件依赖关系必须要指向更稳定的方向。
任何一个我们预期会经常发生变化的组件都不应该被一个难于修改的组件所依赖,否则这个多变的组件也将会变得非常难以修改。
稳定性指标:
I = Fan-out / (Fan-out + Fan-in), 其中 I 表示不稳定性,Fan-in指入向依赖,Fan-out表示出向依赖。
当 I = 0 时,表示组件是最稳定的,I = 1 时,表示组件是最不稳定的。
稳定依赖原则要求每个组件的 I 指标都必须大于其所依赖的组件的 I 指标。 也就是说 组件依赖结构图中各个组件的 I 指标必须要按其依赖关系方向递减。
3.3 稳定抽象原则
一个组件的抽象化程度应该与其稳定性程度保持一致。
4. 软件架构
软件架构
1. 什么是软件架构
软件架构师自身需要是程序员,并且必须是一直坚持做一线程序员。
软件架构这项工作的实质就是规划如何将系统切分成组件,并安排好组件之间的排列关系,以及组件之间相互通信的方式。
设计软件架构的目的,就是为了在工作中更好地对这些组件进行研发、部署、运行和维护。
对于一个软件系统来讲,真正的麻烦往往不会在我们运行软件的过程中出现,而是会出现在这个软件系统的开发、部署以及后续的补充开发中。
软件架构设计的主要目标是支撑软件系统的全生命周期,设计良好的架构可以让系统便于理解、易于修改、方便维护并且能够轻松部署。软件架构设计的终极目标就是让最大化程序员的生产力、同时最小化系统的总运营成本。
软件被发明出来就是因为我们需要一种灵活和便捷的方式俩改变机器的行为。而软件的灵活和便捷则取决于系统的整体情况、组件的部署以及组件之间的连接方式。
基本上,所有软件系统都可以降解为策略和细节两种主要元素。
策略:体现的是软件中所有的业务规则与操作过程,因此它是系统真正的价值所在。
细节:指那些让操作该系统的人、其他系统以及程序员们与策略及进行交互,但是又不会影响到策略本身的行为。
软件架构师的目标是创建一种系统形态,该形态会以策略为最基本的元素,并让细节与策略脱离关系。
一个优秀的软件架构师应该致力于最大化可选项数量。
2. 独立性
一个良好的软件架构必须支持以下几点:
- 系统的用例与很长运行
- 系统的维护
- 系统的开发
- 系统的部署