OverView
软件价值观
- 行为价值:让程序完成指定的功能
- 大多数程序员认为这是主要工作——完成功能、发现并修正bug
- 架构价值:让程序保持易于修改
- 容易被忽视,但这种价值更符合“软件”的存在逻辑
- 软件之所以存在并发展,就是因为它比硬件“软”
- 用户的需求在变、股东的方向在变
历史
- 在20世纪90年代,有一个协调一致的努力来定义和编纂该学科的基本方面,研究工作集中在体系结构风格(模式)、体系结构描述语言、体系结构文档和形式化方法上。
- 卡内基梅隆大学的Mary Shaw和David Garlan在1996年写了一本名为《软件架构:新兴学科的视角》的书,这本书促进了诸如组件、连接器和风格等软件架构概念。
定义
Wikipedia
- Software architecture is the set of structures required for analyzing a software system and the field of creating such structures and systems.
- Each structure consists of software elements, the relationships between them, as well as the properties of both the elements and the relationships. IEEE 1471-2000 Software architecture is the fundamental organization of a system, embodied in its components, their relationships to each other and the environment, and the principles governing its design and evolution.
Programming Paradigms
- 结构化程序设计 (Structured Programming)
- 面向对象程序设计 (Object-Oriented Programming)
- 函数式程序设计 (Functional Programming)
结构化程序设计
Dijkstra是发现程序设计这个学科、并将其发展成一门科学的人之一 对于’不管多简单的程序,只要程序员稍稍忽视一点细节,程序就会看似正确,却以令人诧异的方式出错‘的问题,Dijkstra 的方案:程序的形式化证明
- 用欧式几何的思路:公理、引理、定理……
- 程序员可以用可证明正确的结构,搭建出更大的程序
- 顺序、分支、循环、调用、递归…… 结构化程序设计由此诞生了
- 结构化程序设计取得了巨大的成功
- 因为它使得我们能够将任务层层分解(直到无穷)
- 程序的形式化证明至今未能广泛应用(太累了!)
- 取而代之的是更“科学”的方法
面向对象程序设计
面向对象
- 数据与函数的组合
- 封装、继承、多态 对于软件架构师来说,面向对象是一种通过使用多态获得的,对系统中每个源代码依赖的绝对控制能力
- 允许软件架构师创建插件架构,让高级策略模块独立于低级细节模块
- 低级细节被下放到插件模块中,可以独立于高级策略模块进行部署和开发
函数式程序设计
- 函数式程序设计是以函数为核心的程序设计范式
- 所有的并发问题都是由于变量(内存里的值)可变引起的
- 函数式程序设计中,变量“不变”
- 将尽量多的功能实现在不可变组件中,尽量少的实现在可变组件中
总结
- 结构化程序设计
- 剥夺了程序员使用跳转语句的自由,赋予了软件模块化的能力
- 面向对象程序设计
- 剥夺了程序员使用函数指针的自由,赋予了软件插件化的能力
- 函数式程序设计
- 剥夺了程序员使用赋值语句的自由,赋予了软件并行化的能力
Principals
- 设计原则
- 如何将函数和数据组织为类
- 将砖拼装为房间
- 组件原则
- 如何将类组织为组件和软件
- 将房间拼装为大楼
设计原则
- Single Responsibility Principle
- 一个类应该只有一个引起它变化的原因。这意味着一个类应该只负责一个功能或职责,并且所有行为都与该职责紧密相关。
- Open-Closed Principle
- 软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
- Liskov Substitution Principle
- 子类对象应该能够替换其基类对象,并且不破坏程序的正确性。这个原则强调了继承关系中子类对基类行为的一致性。
- Interface Segregation Principle
- 可能被不同模块调用的函数,应抽象为不同的接口
- Dependency Inversion Principle
- 高层模块不应该依赖于低层模块,两者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。
组件原则
- 组件聚合原则:哪些类组织到哪个组件?
- Reuse/Release Equivalence Principle
- 重用的粒度,就是发布的粒度
- Common Closure Principle
- 把那些会因为相同原因而同时修改的类组成一个组件,把那些会因为不同原因而分别修改的类组分隔为不同组件。
- 组件层次的单一责任原则SRP。
- 可维护 > 可重用。
- 一次修改最好集中在一个组件,不希望横跨多个组件。
- 修改一个组件,不相关的组件无需重新编译部署。
- Common Reuse Principle
- 不要强迫组件的用户依赖它不需要的东西(类、接口等)
- 组件层次的接口隔离原则ISP
- Reuse/Release Equivalence Principle
- 组件耦合原则:组件之间如何交互?
- Acyclic Dependencies Principle
- 组件依赖图中不能有环
- 如何消除环?
- 使用依赖反转原则DIP
- 加入新组件管理权限
- 系统不是被自顶向下设计出来的,而是随着系统增长被演化出来的
- Acyclic Dependencies Principle
- Stable Dependencies Principle
- 依赖方向应从不稳定到稳定
- 不应让不稳定的组件被难于修改的组件依赖
- 不稳定性 I=Degree_out/(Degree_out + Degree_in)
- 依赖方向为𝐼值降序方向
- Stable Abstractions Principle
- 组件的稳定性应与它的抽象程度对应
- 越抽象越稳定,越稳定越抽象
- 依赖方向应该为具体依赖抽象
- 抽象度 A=N_abstract_class/N_class
- 组件应尽量接近“主序线”: 𝐷𝑖𝑠𝑡𝑎𝑛𝑐𝑒= |𝐴+𝐼-1| 组件历史
- 最初,程序所在的位置是固定的,后来,应用越来越大,编译时间越来越长,函数库诞生了。
- 由于位置是固定的,应用被限制在了函数库之前的位置,一旦超出预留的大小,应用程序就需要被切分。
- 人们终于意识到,程序需要能够重定位。如何实现程序的重定位?
- 加载顺序
- 动态链接
- 可单独编译、可独立发布、可动态加载、可动态链接的软件模块 ——组件
组件风格
组件(Component)
- 一组函数和数据的封装
- 组件间通过接口沟通
- 组件可被同样接口的组件替换
- 组件被充分测试,足够鲁棒
- 组件有足够的文档说明 组件风格
- 组件风格关注可复用的组件。
- 实际制造满足需要的组件非常困难。
- 组件模型为制造组件提供模板规范。 组件模型
- Enterprise JavaBeans (EJB) 模型
- Component Object Model (COM) 模型
- .NET模型
- X-MAN组件模型
- Common Object Request Broker Architecture (CORBA) 组件模型
管道过滤器风格
- 过滤器
- 每个组件都有其输入和输出
- 组件的处理过程就是根据输入产生输出
- 管道
- 连接两个组件,将一个组件的输出变成另一个组件的输入
- 过滤器通过管道连接为有向无环图,形成复杂功能
- 对比:
- 组件风格关注组件提供/需要的接口
- 管道-过滤器风格强调数据流的衔接
优势
- 弱耦合性:各个过滤器相互独立,设计者可以将整个系统的输入、输出特性理解为各个过滤器功能的简单合成。
- 易于重用:任意两个过滤器只要相互间传输的数据格式一致,就可以连接在一起。
- 易于维护扩展:新过滤器可以很容易加入到系统中,旧的过滤器可以很容易被新的过滤器替代。
- 易于分析测试:每个过滤器可以独立测试。
- 天然并发性:每个过滤器都可以独立运行,形成流水线pipeline。
缺点
- 交互性差:由于过滤器的传输特性,管道过滤器模式通常不适合于交互性很强的应用。尤其是在系统需要逐步显示数据流变化的过程时,因为增量显示和过滤器的输出数据差距太大。
- 维持通信困难:维持两个相对独立但存在某种关系的数据流之间的通信可能很困难。
- 通信效率低:设计者也需要在数据传输时被迫使用底层公共命名,导致过滤器必须对输入、输出管道中的数据进行解析或反解析的额外工作。
总结
- 管道-过滤器风格关注可连接的组件
- 将组件功能抽象为从输入数据到输出数据的过滤过程
- 通过管道,连接过滤器,形成复杂的数据处理功能
- 天然对分布式并发特性具有良好适应
- 管道中的传输过程有待改进
分层风格
优势
- 分层符合人类“分而治之”的思维方式。
- 结构化程序设计之所以能够取得巨大的成功,正是因为它使得我们能够将任务层层分解。
- 耦合性低。
- 重用性高。
- 可维护性高。
- 无环依赖原则(Acyclic Dependencies Principle,ADP)
- 组件依赖图中不能有环。
- 有向无环图一定能分层。
总结
- 分层风格关注组件间的偏序关系
- 将系统功能层层分解,降低耦合性,提升重用性
- 最常见的分层风格是三层架构
- 层与层之间隔离有利于快速部署
- 层与层之间协同有利于性能效率
事件驱动风格
- 以事件衔接程序之间的数据流
- 程序产生事件,触发其他程序的功能
- 事件驱动风格可以认为是广义的管道-过滤器风格
- 优缺点与管道-过滤器风格类似
- 事件驱动风格的程序框架通常是提供事件等待/触发机制的中间件
共享数据风格
黑板风格
- 黑板模块:全局存储空间
- 知识源模块:处理知识、更新黑板
- 控制模块:调度可用的知识源模块
总结
- 黑板风格:与其移动数据,不如共享数据!
- 数据中心风格:与其移动数据,不如移动程序!
客户端服务器风格
- 服务器:拥有资源,提供服务
- 客户端:建立会话,提出请求
总结
- 天然的两层分层风格
- 如何将三层风格变为两层风格?根据通信量切分
- 问题:随着资源规模和请求吞吐量需求增大,服务器成为瓶颈
- 解决方案
- 端到端风格
- 端-边-云架构
端到端风格
Peer-to-Peer, P2P
- 去中心:所有参与者在网络中是平等的
- 每个参与者都提供一部分资源(计算、存储、带宽等)
- 有效解决了客户端-服务器风格中,服务器的资源瓶颈 核心优势
- 全局内容发现
- 高效的内容索引
端到端架构将参与者重新组织为逻辑网络
- 非结构化网络
- Gnutella、Gossip、Kazaa……
- 易于组织,支持参与者随时加入离开
- 难以发现内容
- 结构化网络
- 分布式哈希表DHT
- 每个参与者、内容均与键值联系
- 易于全局内容发现,但存在均衡问题
面向服务风格
- 将软件分解为多个独立的服务
- 服务:独立的功能单元,可通过通信协议与之进行远程沟通
- 代表可重复的业务活动
- 具有相对完整的功能
- 对用户是黑盒,用户无需关心服务的内部实现
- 可以调用其他服务完成功能
对比
- 与组件风格对比
- 在组件风格中,通常支持多个组件在同一个进程中
- 服务更强调独立性,通信协议均为网络协议(支持跨进程通信)
- 与管道-过滤器风格对比
- 过滤器之间通过管道传递消息,不关心消息被用来做什么了
- 服务接受的是请求,并须要针对请求给出答复
- 与事件驱动风格也有类似的区别
- 与分层风格对比
- 只要服务与服务之间的“调用”关系不存在“递归”,就也属于分层风格
- 万一服务之间有递归呢?
- 与客户端-服务器风格对比
- 客户端-服务器风格中,通常服务器是整个系统中的单个中心
- 面向服务风格中,服务可以有很多
近亲
- 糅合(Mashup)
- 将两种以上使用公共或者私有数据库的Web应用加在一起,形成一个整合应用
- 软件即服务(SaaS,Software as a Service)
- 让用户能够通过互联网连接来使用基于云的应用程序
- 云计算(Cloud Computing)
- 将来自用户的任务请求分解成数个小程序,利用网络“云”上多部服务器的资源进行并行处理,得到结果并返回给用户
组成
- 服务提供者
- 向服务中间人注册自己提供的服务,答复服务请求者的请求
- 服务请求者
- 要求服务中间人查找所需服务,向服务提供者提出请求
- 服务中间人(服务注册、服务检索)
- 注册服务提供者,给服务请求者反馈服务提供者信息
- 可以分为公有中间人、私有中间人
无共享风格
Shared-Nothing Architecture
- 分布式系统风格
- 每个更新操作只由集群中的一个结点来完成
- 与Shared Everything相对,旨在消除数据竞争 数据库两大类应用
- 联机事务处理(OLTP,On-Line Transaction Processing)
- 联机分析处理(OLAP,On-Line Analytical Processing) 新兴数据库
- 特点
- 容量需求大
- 数据增加多
- 数据更新少
- 查询效率尽量快
- 数据仓库(Data Warehouse)
- 数据湖(Data Lake)