(一)课程简介&概述
对于大型、复杂、软件密度高(software-intensive)的系统而言,系统的宏观结构设计成为系统成败的决定性关键因素。
一、什么是软件体系结构
软件体系结构(Software Architecture)是:
- 解决日益复杂的软件系统的分析不设计问题
- 刻画大型复杂软件系统的静态结构与动态行为
- 构造更大更复杂软件系统的有效工程方法
的一种软件设计技术与软件工程方法。
一个软件系统的体系结构是指它所包含的计算构件和这些构件间的交互作用。
即:Software Architecture= SA = {components, connectors}
Component(构件/组件):系统的逻辑与功能结构组成单元。
Connector(连接件):构件间相互交互的机制与规则。
二、各个时期的软件
(1)软件的机器指令时代:
这个时代的程序就是若干条机器指令所组成的一段顺序执行的计算指令,软件的结构表现为指令之间的顺序结构。
(2)软件的汇编与早期的高级语言时代:
这个时代的软件的宏观结构体现为主-子程序的调用与被调用的结构。
(3)软件的面向结构时代:
在面向结构的软件开发时代,模块的聚集和嵌套形成层层调用的软件宏观结构。人们在设计软件时,开始先从软件模块的抽象结构粒度上考虑软件系统的宏观结构。
(4)软件的面向对象时代:
对于面向对象的软件系统,包(Package)或名字空间(Namespace)是对象的结构化组织,而系统的包结构或名字空间的划分来体现软件的一种宏观静态结构。
三、理解软件体系结构
(1)宏观与微观
系统体系结构设计(宏观):体系结构模型
系统详细设计(微观):详细设计模型
(2)自顶向下和自底向上
对系统的设计过程:由总体到局部(Top-down)
系统实现的过程:先局部再整体(Bottom-up)
(二)软件体系结构基础
一、软件体系结构的核心概念模型
(1)Component(构件)
Component(构件):系统的逻辑与功能结构单元。
构件是一种抽象概念表述。是对以各种具体实现技术的系统结构组成元素(如子系统、模块、包等)的统称。
构件是对系统功能集中的一个功能子集的结构化封装与实现。每个构件对于系统而言,应具有一定功能性。
构件通过其端口(Port)与外部环境交互。构件的端口(Port)表示了构件和外部环境的交互作用点。
端口(Port):构件与外界的交互作用点,是构件的外部可见特征(Visible Properties)。
构件的可分解性:
构件的可分解性:
原子(atom)构件:不再需要进行分解的构件。
复合(composite)构件:需要再进行分解的构件。
分类:
根据功能层次:基础构件、中层构件、高层构件。
根据复用度:通用构件、专用构件。
根据功能类别:数据构件、界面构件、控制构件、安全构件。
(2)Connector(连接件)
Connector(连接件):构件间相互交互的机制或规则。
机制:是指连接件的具体实现形式:如过程调用、共享存储区。
规则:是指构件使用连接件应遵循的规范。如对“过程调用”这种连接件其规则是指调用的接口参数形式、共享存储区则是指其本身的数据存储结构。
构件在交互过程中体现为不同的角色。
一个连接件所涉及的角色可能是二元的,也有多元的。
方向性:单向和双向。有返回值的函数调用为双向连接。
性能特性:同步/异步连接、开放/安全连接、串行/并行 …
(3)Configuration(配置)
Configuration(配置):构件与连接件的装配规约(Assemble Specification)。
描述了构件通过连接件相互交互所形成的逻辑拓扑结构(Topology)。
定义了构件、连接件的使用规则与约束。
相同的构件集与连接件集,通过不同的配置可形成不同结构形态的软件体系结构:
构件的端口与连接件的角色之间的关联关系;复合构件与其子构件的嵌套包含关系。
二、软件体系结构分析设计的两种基本方法:
(1)分解(Decomposition)
按照系统功能分解的方式,将系统功能进行分解,每一个相对独立的功能分解模块形成系统结构的一个功能构件。
(2)组合(Composition)
考虑系统的各功能构件间的相互关系,通过什么样的连接机制实现将各个独立的构件形成一个整体系统。
三、系统架构的多维度分解模型:
业务域分解、功能域分解、技术域分解、涉众域分解。
架构分解过程模型:迭代的过程模型。
四、软件体系结构的评价
(1)软件体系结构的功能特性
软件体系结构的功能特性(Functional Property of the SA):软件体系结构对软件需求规范中所定义的功能需求满足程度。
(2)软件体系结构的非功能特性
软件体系结构的非功能特性(Non-functional Property of the SA):软件体系结构对软件需求规范中所定义的非功能需求满足程度。
非功能特性体现了软件的质量,决定了一个软件系统的架构设计优劣。
分为:运行期的非功能特性、开发期的非功能特性。
运行期的非功能特性:性能、安全性、易用性、可用性、可伸缩性、互操作性、可靠性、健壮性。
开发期的非功能特性:易理解性、可扩展性、可重用性、可测试性、可维护性、可移植性。
(三)软件体系结构建模
一、体系结构的模型
从不同的视角(view-point),可以刻画系统的体系结构不同侧面。
每一个不同视角的刻画均是对系统体系结构的一个描述,我们往往需要从不同视角来描述一个系统的体系结构。
二、“4+1”视图模型
By: Philippe Kruchten (1995)
使用多个并发的视图来组织软件架构的描述,每一个视图只关心软件体系结构的一个侧面,5个视图结合在一起才能完整反映软件体系结构的全部内容。
(1)Logic View(逻辑视图)
Logic View(逻辑视图):基于功能需求抽象,刻画系统的静态结构模型。
视角:功能需求的分析理解与抽象;
关注点:基于软件的功能性需求,是系统功能的抽象结构表述,关注系统提供给最终用户的功能。
(2)Development View(开发视图)
Development View(开发视图):考虑开发技术、过程与组织,刻画系统的开发管理结构模型。
视角:软件的开发实现;
关注点:是软件体系结构的逻辑视图在具体实现阶段的表示,关注软件实现的技术与组织管理要求及约束。
(3)Process View(进程视图)
Process View(进程视图):刻画系统的运行时的结构模型。
视角:软件运行时(Run-time)的结构形态;
关注点:基于软件的非功能性需求,是软件系统运行时的动态结构,关注的是系统非功能性需求的满足。
(4)Physical View(物理视图)
Physical View(物理视图):逻辑视图中的各功能构件在安装部署环境中的映射,刻画系统的安装部署结构模型。
视角:软件在实际安装部署环境中的结构形态;
关注点:基于软件的非功能性需求,是软件系统安装运行时的动态结构,关注的是系统非功能性需求的满足。
(5)Scenarios View(场景视图)
Scenarios View(场景视图):从系统使用的角度对系统结构的描述。它反映的是在完成一个系统功能时,系统各功能构件间的交互关系。
视角:用户视角;
关注点:基于软件的功能性需求,关注的是在完成一个系统功能时,系统各功能构件间的协作关系,增加设计的可理解性,为其它视图的分析设计服务。
(四)典型的软件体系结构风格
一、管道-过滤器体系结构风格 (Pipe-Filter Style):
构件类型: 过滤器(Filter),数据处理构件
连接件类型: 管道(Pipe),过滤器间的连接件
- 每个处理步骤封装在一个过滤器构件中
- 数据通过过滤器之间的管道传输
- 重组过滤器可以建立相关的系统族(不同数据处理顺序或功能)
逻辑视图:
系统输入:文本文件,其他的数据源(Data Source)
系统输出:数据宿/数据池(Data Sink)
管道:负责实现相邻步骤之间的数据流动
过滤器:数据流的独立处理构件,负责丰富、提炼或转换它的输入数据
过滤器的工作方式:
【被动过滤器(Passive Filter)】
- 过滤器从前一个过滤器或数据源中拉出(pull)数据
- 过滤器把输出数据压入(push)后一个过滤器或数据池(Data Sink)
【主动过滤器(Active Filter)】
- 过滤器以循环的方式工作,从前一个过滤器或数据源拉出输入数据并将其输出数据压入下一个过滤器或数据池(Data Sink)
管道(Pipe):
表示过滤器之间的连接;数据源和第一个过滤器之间的连接;以及最后的过滤器和data sink之间的连接
如果管道连接两个主动过滤器,那么管道需要迚行缓冲和同步
管道可以实现数据在过滤器之间的数据转换,将一个过滤器的输出数据格式转换为其后接的过滤器的数据输入格式
应用场景(Context):
- 处理或者转换输入数据流
- 对数据的处理可以容易地分成几个处理步骤
- 系统的升级要求可以通过替换/增加/重组处理步骤实现,有时甚至由使用者完成操作
- 不同的处理步骤间不共享信息
优点:
- 高内聚和低耦合
- 支持过滤器配置实现可扩展性/可伸缩性(Flexibility)
- 支持过滤器构件的重用
- 有利于系统的维护与演化更新(Evolution)
- 可支持局部的并行处理以提高效率
缺点:
- 增量式处理数据时,存在效率问题
- 数据格式转换的问题:数据转换额外开销
- 不适合交互式应用系统
二、基于事件的隐式调用风格 (Event-Based Implicit Invocation Style)
又称:事件驱动架构(Event Driven Architecture, EDA)
显式调用(直接耦合)
构件之间的交互是建立在彼此知道对方端口
构件的交互是固定的、预先设计的
隐式调用(间接耦合)
构件间的交互不是固定的、预先未知
构件间交互无需彼此感知(不知道对方的端口)
采用离散、异步的事件机制实现系统的构件间的松散、间接耦合的交互:
- 系统预先设计定义一套事件主题(Event Scheme)
- 系统中的其它构件预先注册一个或多个事件主题
- 系统中的某个构件触发一个或多个事件
- 当一个事件被触发,系统将事件发布给事先注册了该事件主题的所有构件
- 这样,某个构件上的一个或多个事件的触发就导致了系统中其它构件对事件的响应
构件类型: 事件源(Event Source)、事件处理者(Event Handler)、事件分发者(Event Dispatcher)
连接件类型:事件(Event)机制
应用场景(Context):适用于异步、并发性的系统
- 对事件的处理顺序无要求的系统
- 事件的处理要求具有很好的灵活性
- 非集中式控制的软件系统
优点:
系统具有很好的灵活性,系统易于伸缩扩展
缺点:
- 系统控制权的问题:无中心控制系统
- 数据的交换问题:一些情况下,基于事件的系统必须依靠一个共享的仓库进行交互。在这些情况下,全局性能和资源管理便成了问题。
- 事件处理顺序控制问题
三、分层体系结构风格 (Layered Style)
应用场景(Context):
一个需要分解的大系统
系统的显著特征是混合了低层与高层问题
系统的需求本身定义了多个层次上的需求
系统规格说明描述了高层任务,并希望可移植性
高层任务到平台的映射不是直接的
系统需要满足以下非功能性特性:
后期源代码的改动应该不影响到整个系统
系统的各个部分应该可以替换。构件应该可以有不同的实现而不影响其他的构件
提高构件的内聚性
复杂构件的进一步分解
设计和开发系统时,工作界限必须清楚
优点:
- 层的可重用性
- 标准化的支持
- 局部依赖特性
- 可替换性
缺点:
- 层间的依赖性
- 当对低层的修改由于某种原因影响了高层的时候,可能引起底层之上的多个层次
- 效率问题:过多的层次造成从最上层到最下层需要进行逐层的交互等
四、仓库风格 (Repository Style)
以数据为中心(Data-Center)的体系结构风格
数据中心定义了一种共享的数据结构
构件之间通过数据中心分布式协同工作
构件类型:独立构件(Independent Component)、 仲裁者(Mediator)
连接件类型:仓库(Repository)
两种类型:
被动仓库:Database(数据库),由独立构件访问存取仓库来驱动系统运行
主动仓库:Blackboard(黑板),由仓库的数据或状态来触发独立构件做相应的处理,驱动系统运行
优点:
- 便于多构件间共享大量数据,而不必关心数据是何时产生的、由谁提供的,以及通过何种途径来提供
- 便于将新的构件作为知识源添加到系统中来(Blackboard风格)
缺点:
- 共享数据结构的修改变得非常困难
- 需要同步机制和加锁机制来保证共享数据的完整性和一致性,增大了系统设计的复杂度
(五)GRASP
GRASP
General Responsibility Assignment Software Principles
通用职责分配软件原则
核心思想:职责分配(Responsibility Assignment)
9个基本原则:
信息与家(Information expert)
创建者(Creator)
高内聚(High Cohesion)
低耦合(Low Coupling)
控制者(Controller)
多态(Polymorphism)
纯虚构(Pure Fabrication)
间接性(Indirection)
变化预防(Protected Variations)
(六)创建型设计模式(Creational Patterns)
一、工厂方法模式(Factory Method)
为某一个产品族对象的创建,定义统一的Factory Method接口,并由子类具体实现对象创建。
(1)Context(应用场景)
- 当一个类不知道它所需要的对象的类时:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可
- 当一个类希望通过其子类来指定创建对象时:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中
(2)参与者
- 抽象产品类 Product
- 具体产品类 ConcreteProduct
- 抽象工厂类 Creator
- 具体工厂类 ConcreteCreator
(3)优点
- 为一组相关或相似对象(产品族)的创建提供了统一的创建接口(工厂方法)
- 将产品族对象与使用者(Client)之间解耦,使用者无需了解产品族对象创建知识就可获得和使用产品族对象
(4)缺点
需要抽象工厂类和其相应的子类作为工厂方法的定义和实现,如果设计确实需要抽象工厂类和子类存在,则很好;否则的话,需要增加抽象工厂类与其子类
二、抽象工厂模式(Abstract Factory)
解决多个产品族对象的创建工作,专门定义一个用于创建这些对象的接口(基类)。客户只需与这个基接口打交道,不必考虑实体类的类型
(1)Context(应用场景)
- 一个系统不要求依赖产品类实例如何被创建、组合和表达
- 这个系统有多个系列产品,而系统中只消费其中某一系列产品
- 系统要求提供一个产品类的库,所有产品以同样的接口出现,客户端不需要依赖具体实现
(2)参与者
- 抽象产品族(Abstract Product)
- 抽象产品(Product)
- 具体产品(Concrete Product)
- 抽象工厂(Factory)
- 具体工厂(Concrete Factory)
(3)优点
- Concrete Factory中的工厂方法把产品对象创建封装起来,并将具体的产品类与Client分离
- 保证产品族对象创建接口的一致性(统一的产品族对象创建接口)
(4)缺点
Concrete Factory对象的工厂方法数目对应product种类数目,增加新的product种类比较困难,要影响到factory的基类,进而影响到所有的子类
三、原型模式(Prototype)
以一个已有的对象作为原型,通过它来创建新的对象。在增加新的对象的时候,新对象的细节创建工作由自己来负责,从而使新对象的创建过程与Client隔离开来
(1)Context(应用场景)
- 当产品的创建过程要独立于系统时
- 当产品的类型是在运行时被指定的情况下
- 避免创建一个与product层次平行的factory层次时
- 当产品类的实例只能是几种确定的不同实例状态中的一种
(2)参与者
- 原型类(Prototype)
- 具体原型类(ConcretePrototype)
- 用户类(Client) .
四、单例模式(Singleton)
确保1个类只有1个实例化对象,提供一个全局访问点
(1)Context(应用场景)
- 类必须只有一个实例,并且必须可以从已知的访问点对客户端进行访问
- 当唯一的实例应该通过子类进行扩展时,客户机应该能够在不修改代码的情况下使用扩展实例
(2)参与者
单例类(Singleton) .
(3)评价:
- 提供了一种全局化的单一对象实例设计方法
- 在单态设计模式基础上,可扩展设计受限数目的对象实例创建
五、小结
- 工厂方法模式Factory Method:通过一个一致化的factory method完成产品对象的创建
- 抽象工厂模式Abstract Factory:基于多个factory method实现多个product族对象的创建
- 原型模式Prototype:通过product原型来clone创建product对象
- 建造者模式Builder:完成一个包含多个子对象的复合对象的构造
- 单例模式Singleton:Product类的单对象实例创建
- Finder:把对象的获取过程与客户隔离开
(七)结构型设计模式(Structural Patterns)
一、适配器模式(Adapter)
定义一个包装类,用于包装不兼容接口的对象
(1)作用
把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法一起工作的两个类能够在一起工作
(2)Context(应用场景)
- 系统需要复用现有类,而该类的接口不符合系统的需求,可以使用适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
- 多个组件功能类似,但接口不统一且可能会经常切换时,可使用适配器模式,使得客户端可以以统一的接口使用它们
(3)参与者
- 适配器 Adapter
- 适配者 Adaptee
- 客户类 Client
- 目标类 Target
(4)优点
更好的复用性;透明、简单;更好的扩展性;解耦性;符合开放-关闭原则
(5)缺点
过多的使用适配器,会让系统非常零乱,不易整体进行把握
(6)分类
1.类的适配器模式 class adapter
Adapter与Adaptee是继承关系
无法适配Adaptee的子类
可以重载Adaptee的行为
2.对象的适配器模式 object adapter
Adapter与Adaptee是委派关系
可以适配Adaptee的所有子类
二、组合模式(Composite)
将对象组合到树结构中以表示部分-整体层次结构。Composite允许客户端统一地处理单个对象和对象的组合
(1)参与者
Client, Component, Leaf, Composite
(3)Context(应用场景)
- 表示对象的部分-整体层次结构
- 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象
(4)优点
- 定义了包含leaf对象和composite对象的类层次接口——递归结构
- 客户一致地处理复合对象和单个对象
- 易于增加新类型的组件
(5)缺点
很难限制组合中的组件:有时你希望一个组合只能有某些特定的组件。使用Composite时,你不能依赖类型系统施加这些约束,而必须在运行时刻进行检查
三、小结
- Adapter:用于两个不兼容接口之间的转接
- Bridge:用于将接口的抽象与多个可能的实现连接起来
- Facade:用于为复杂的子系统定义一个新的简单易用的接口
- Composite:用于构造对象组合结构
- Decorator:用于为对象增加新的职责(修改对象的接口)
- Proxy:为目标对象提供一个代理者
- Flyweight:针对细粒度对象的一种全局控制手段。
(八)行为型设计模式(Behavioral Patterns)
一、命令模式(Command)
将请求封装为对象,从而允许您用不同的请求、队列或日志请求参数化客户机,并支持可撤消操作
(1)Context(应用场景)
- 需要封装表示对象间的调用交互
- 需要实现对象间交互调用的管理
- 需要支持对象间交互调用的undo/redo
- 记录对象间交互调用以便系统回滚
- 需保证对象间交互的事务完整性
(2)评价
- 对象间调用交互的解耦
- 支持对象间交互的redo/undo – 调用重用
- 组合对象间的多个调用交互–宏调用/批处理
- 易扩展对象间交互–通过继承定义新的Command对象 .
二、迭代器模式(Iterator)
用于支持集合对象的遍历操作,而无需暴露集合对象的内部表示
(1)Context(应用场景)
- 访问集合对象而无需暴露其内部存储结构
- 可提供多种遍历操作方法
- 为不同遍历操作方法提供一个统一的接口形式
(2)参与者
- 迭代器(Iterator)
- 具体迭代器(ConcreteIterator)
- 集合(Aggregate)
- 具体集合(ConcreteAggregate)
(3)评价
- 支持多种集合遍历
- 迭代器简化了集合接口
- 一个集合上可以有多种遍历
三、观察者模式(Observer)
支持对象间1对多的交互调用方式
(1)Context(应用场景)
- 当一个抽象有两个方面,一个依赖于另一个时。将这些方面封装在单独的对象中,可以独立地改变和重用它们
- 当一个对象的更改需要更改其他对象,而您不知道需要更改多少对象时
- 当一个对象应该能够通知其他对象而不需要假设这些对象是谁。换句话说,您不希望这些对象紧密耦合
(2)参与者
- Subject: 目标
- ConcreteSubject: 具体目标
- Observer: 观察者
- ConcreteObserver: 具体观察者
(3)评价
- 抽象目标与观察者之间的耦合
- 支持广播
- 意想不到的更新
四、小结
- Strategy、Iterator、Mediator、State、Command:用一个对象来封装某些特性,比如变化、交互、状态、行为、命令
- Observer:建立起subject和observer之间的松耦合连接
- Mediator:把约束限制集中起来,中心控制
- Command:侧重于命令的总体管理
- Chain of Responsibility:侧重于命令被正确处理
- Interpreter用于复合结构中操作的执行过程