当前课程知识点:软件理论与工程 > 第3章 软件设计 > 3.6 构件级设计 > 3.6 构件级设计
大家好
今天我们来一起分享一下
软件设计当中的
构件级设计
完整的软件构件
是在体系结构设计过程中定义的
但是
没有在接近代码的抽象集上
表示内部数据结构
和每个构件的处理细节
构件级设计
定义了数据结构
算法
接口特征
和分给每个软件构件的通信机制
体系结构设计第一次迭代完成之后
就应该开始构件级的设计了
在这个阶段
全部数据和软件的程序结构
都已经建立起来
目的是把设计模型
转化为运行软件
但是
现有设计模型的抽象层次
还相对较高
而可运行程序的抽象层次
相对较低
那么这中间是有很大的一个差距
这种转化具有挑战性
因为可能会在软件过程后期
引入难以发现
和改正的微小错误
一位帮我们理解软件设计的主要贡献者
Edsgar Dijkstra在其著作中写道
软件似乎不同于很多其他的产品
对那些产品而言
一条规则是
更高的质量
意味着更高的价格
那些想要真正可靠软件的人发现
他们必须找到某种方法
来避免开始时的大多数的错误
结果程序设计过程的成本会更低
高效率的程序员
不应该把他们的时间浪费在调试上
应该在开始
就不引入错误
当设计模型被转化为源代码时
必须要遵循一系列的设计原则
才能够保证
不仅能够完成转化任务
而且
不在开始的时候
就引入错误
那么 什么是构件呢
通常来讲
构件是计算机软件中的
一个模块化的构造块
对象管理小组
OMG统一建模语言规范
是这样定义构件的
系统中
模块化的
可部署的
可替换的部件
该部件封装了实现
并暴露了一组接口
在面向对象的观点当中
构件是协作类的集合
为了说明设计细化的过程
我们来看一个例子
我们用这个例子来说明
面向对象的构件的设计
我们考虑一个高级的影印中心
构造软件
这个软件的目的是
收集前台的客户需求
对印刷业务进行定价
然后把印刷业务交给自动生产设备
那么 在这个任务的需求分析过程当中
得到了一个名为
PrintJob的分析类
分析过程中定义了
PrintJob这个
分析类的属性
操作
我们在图中给出了注释
那么 在体系结构设计中
PrintJob被定义为
软件体系结构的一个构件
用简化的UML符号表示的该构件
显示在图的右上方
它有两个接口
ComputerJob和initiateJob
其中ComputerJob接口
具有对任务进行定价的功能
而另一个接口initiatejob
能够把任务传给生产设备
这两个接口我们在图下方的左边给出了
然后我们的构件级设计就要由此开始了
我们必须对PrintJob这个构件的细节
进行细化
以提供指导实现的充分信息
并通过不断补充作为构件
PrintJob类的全部属性
和操作
来逐步细化我们最初的
分析类PrintJob
细化后的PrintJob包括
很多的属性信息
和构件实现所需要的更广泛的
操作的描述
ComputerJob和initiateJob接口
隐藏着与其它构件通信和协作
这个部分我们在图中并没有表示出来
例如
ComputePageCost这个操作
它是
ComputerJob接口的一个组成部分
这个ComputePageCost这个操作
可能与包含任务定价信息的
PricingTable构件进行协作
checkpriority这个操作呢
是initiate接口的一个组成部分
它可能与JobQueue构件进行协作
用来判断
当前等待生产的任务类型和优先级
对于体系结构设计组成部分的每个构件
我们都要实施细化
细化一旦完成
要对每个属性
每个操作
和每个接口
进行更进一步的细化
对适合每个属性的数据结构
必须予以详细的说明
另外
还要说明实现与操作相关的
处理逻辑的算法细节
那么 最后
就是实现结构所需要的机制的设计
对于面向对象的软件
还会包含对实现系统内部对象间的
消息通信机制的一些相关的描述
那么我们再看一下传统的构件
在传统的观点下
一个构件就是程序的一个功能的要素
程序由处理逻辑
以及实现处理逻辑所需的内部数据结构
以及能够保证构件被调用和
实现的数据传递的接口构成
与面向对象构件类似
传统的软件构件
也来自于分析模型
但是 不同的是
在这种情况下
是以分析模型争斗的构件细化
作为导出构件的一个基础
构件层次结构上的每个构件
都被映射为某一个层次上的模块
一般来讲
控制构件位于层次结构
也就是我们的体系结构的顶层附近
而问题域的构件
则倾向于位于层次结构的底层
为了有效的模块化
在构件细化的过程中
采用了功能独立性的设计概念
那我们再回过头来看一下 我们刚才的
影印中心的例子
我们在传统的构件的角度下
导出了
如图所示的层次体系结构
其中
绿色的构件的功能
相当于图中的面向对象构件中
PrintJob类
定义的一些操作
然而 在这种情况下
每个操作都被表示为如图所示的
能够被调用的单独的模块
其它模块
用来控制处理过程
也就是我们前面提到的
控制构件
在构件级设计中
每个构件都要被细化
需要明确的定义模块的接口
也就是
每个经过接口的数据
或控制对象
都需要明确的加以说明
同时 我们还需要定义模块内部
使用的数据结构
并使用逐步求精的方法
设计完成模块中相关功能的一些算法
为了说明这个过程
我们考虑ComputePageCost模块
这个模块的目的在于
根据用户提供的规格说明
来计算每页的印刷成本
为了实现这个功能
我们需要
文档的页数
文档的印刷份数
单面或者是双面印刷
颜色
纸张大小
等一些参数
这些数据通过该模块的接口
传递给ComputePageCost模块
然后
ComputePageCost模块呢
根据任务量和复杂度
使用传递进来的这些数据
来决定一页的成本
这是一个通过接口
将所有数据传递给模块的功能
图中是UML符号描述的构件级设计
其中ComputePageCost模块
通过调用getJobData
模块
允许所有的相关数据
都传递给该构件
和数据库接口
accessCostsDB
这个接口能够使
该模块访问
存放所有印刷成本的数据库
来访问相关的数据
接着
对ComputePageCost的模块
进一步细化
给出算法
和接口的细节描述
其中
算法的细节
可以由图中显示的伪代码
或者是UML的活动图来表示
我们的接口被表示为一组输入
和输出的数据对象
或者是数据项的集合
设计细化的过程
会一直进行下去
直到能够提供指导构件的足够细节
都已经提供出来为止
那么我们下面看一下内聚
在传统观点里面
内聚是指的构件的专一性
在面向对象的观点里面
内聚性意味着构件或类
指封装那些相互关联的 密切的
以及与构件或者类自身有密切关系的
属性和操作
那么 无论是面向对象的还是传统观点的
我们的内聚性
可以进行一些分类
可以包括功能内聚
分层内聚
还有通信内聚
那么 功能内聚主要是
通过操作来体现
当一个模块完成一组
且只有一组操作并返回结果时
我们就称此模块是功能内聚的
分层内聚
由包
构件和类来实现
高层能够访问低层的服务
但是低层不能访问高层的服务
通信内聚
访问相同数据的所有操作
被定义在一个类中
一般来说
这些类只着眼于数据的查询
数据的访问
和数据的存储
看完了内聚 我们再看一下
在传统观点和面向对象观点当中的耦合
传统观点的耦合指的是
一个组件连接到其它组件
和外部世界的程度
在面向对象的观点里面
耦合指的是
类之间彼此联系的程度的一种特性的度量
耦合 可以有内容耦合
控制耦合
还有外部耦合
通信和协作
是面向对象系统当中的基本元素
然而
这个重要特征存在着一个负面的影响
随着通信和协作数量的增长
也就是说
随着类之间的联系程度越来越强
系统的复杂性也会随之增长
同时 随着系统复杂度的增长
软件实现
测试
和维护的困难
也会随之增大
耦合是类之间
彼此联系程度的一种定性的度量
随着类之间的相互依赖越来越多
类之间的耦合程度也就会增加
在构件级设计中
一个重要的目标就是
尽可能的保持
低耦合
我们的内容耦合是指的
发生在当一个构件
暗中修改了其它构件的内部数据时
我们称之为内容耦合
这就违反了基本的设计概念当中的
信息隐蔽的原则
控制耦合
发生在当操作a
调用了操作b
并且向b传递了一个控制标记时
接着 这个控制标记
会指引b中的逻辑流程
这种耦合形式的主要问题会在于
b中的一个不相关的变更
往往能够导致
a所传递的控制标记的意义
也必须发生变更
如果你忽略了这个问题
就会引起错误
外部耦合
发生在当一个构件和基础设施构件
比如操作系统
数据库容量
无线通信功能等等
进行通信协作时
尽管这种类型的耦合是必要的
但是 在一个系统中
应该尽量将这种耦合限制在少量的构件
或者是类的范围内
软件必须要进行
内部和外部的通信
因此耦合是不可避免的
然而 我们要把不可避免的耦合
尽量降低
并且我们要充分理解
该耦合产生的后果
实施构件级设计
构件级设计
我们要经过一系列的步骤
进行
第一
我们要标识出
所有与问题域相对应的设计类
我们使用需求模型和架构模型
每个分析类和体系结构构件都要细化
第二
我们要确定
所有与基础设施域相对应的设计类
在需求模型中
并没有描述这些与基础设施相对应的设计类
并且在体系结构设计中
也经常会忽略这些类
但是此时
我们必须要对它们进行描述
这种类型的类
和构件包括
图形用户界面
GUI构件
操作系统构件
以及对对象
和数据
进行管理的构件
第三
我们要细化所有
不需要作为复用的构件的设计类
我们要详细的描述实现类
细化所需要的所有接口
属性和操作
在实现这个任务时
必须考虑采用设计的启发式原则
如构件的内聚和耦合
其中在类和构件协作时
我们要说明消息的细节
我们可以使用协作图
来说明
类和构件协作时的消息的细节
需求模型中
用协作图显示分析类之间的相互协作
在构件级设计过程中
某些情况有必要通过对系统中的对象间
传递消息的结构进行说明
来表现协作的细节
尽管这是一个可选的设计活动
但是
它可以作为接口规格说明的一个前提
这些接口显示了系统构件之间的通信
和协作的方式
我们还要为每个构件确定适当的接口
在构件级设计中
一个UML的接口是
一组外部可见的
公共的操作
接口不包括内部结构
没有属性
也没有关联
更正式的来讲
接口就是某个抽象类的等价物
该抽象类提供了设计类之间的可控连接
还有我们要细化属性
并且定义实现属性所需要的数据类型
和数据结构
描述属性的数据类型和数据结构
一般都需要在实现时
所采用的程序设计语言中来进行定义
另外 我们详细描述每个操作的处理流
详细描述每个操作中的处理流可能需要
有基于程序设计语言的伪代码
或者是UML的活动图来完成
每个软件构件
都要应用逐步求精的概念
通过很多次的迭代来进行细化
然后我们还要说明持久的数据源
比如数据库和文件
并确定管理数据源所需要的类
再有开发并且细化类
或构件的行为表示
UML的状态图
被用作需求模型的一部分
表示系统外部可观察到的行为
和更多的分析类个体的局部行为
在构件级设计过程中
有些时候
对设计类的行为进行建模也是必要的
我们也可以使用UML的状态图
对设计类的行为进行建模
对象
的动态行为受到外部事件
和对象的当前状态的影响
为了理解对象的状态行为
设计者必须检查
设计类生命周期中
所有相关的用例
这些用例提供的信息
可以辅助设计者描述
影响对象的事件
以及随着时间的流逝
和事件发生对象所处的状态
另外 我们要细化部署图
以提供一些额外的细节
部署图作为体系结构设计的一部分
采用描述符形式来表示
在这种表示形式中
主要的系统功能
通常是子系统
或者是构件
都表示 在容纳这些功能的计算环境当中
那么这些构件和计算环境的细节
我们就可以通过细化部署图来获得
最后
考虑每个构件设计时
我们要时刻的考虑其它
可选的更优的方案
经过这样一系列的步骤
我们才能够完成构件级设计的相关工作
那么我们再通过影印系统的例子
我们来看一下协作图
来显示类
对象之间协作之间的消息传递的细节
比如图中我们有
ProductionJob
WorkOrder和JobQueue
这三个对象相互协作
为生产线准备印刷作业
图中的箭头表示对象间传递的消息
在需求建模时
消息说明如图所示
然而 随着设计的进行
我们消息要通过扩展
语法来进行细化
在前面的面向对象的构件
那个
PPT上
我们的initiateJob接口
由于没有展现出足够的内聚性而存在争议
实际上
在上一个面向对象的构件的图中
initiateJob接口
完成了三个不同的子功能
一
建立工作单
二
检查任务的优先级
三
将任务传递给生产线
为了实现接口的内聚性
我们的接口设计应该进行重构
重新检查设计类
并定一个新类WorkOrder
这个类的作用是
处理与装配工作单相关的所有活动
操作buildWorkOrder()
成为这个类的一个部分
类似的
我们可能需要定义包括操作
checkPriority()
在内的JobQueue这样的类
我们的ProductionJob类
包括给生产线传递生产任务的
所有相关的信息
那我们的initiateJob接口
就会采用图中所示的形式
现在我们的initiateJob接口
就是内聚的
集中在一个功能上
与ProductionJob
WorkOrder和JobQueue
相关的接口
几乎都是内聚的
实现专一的一个功能
经过重构的接口
如图所示
下面我们再看一下
我们要描述每个操作的处理流
每个操作的处理流
这个可能需要有
基于程序设计语言的伪代码
或者由UML图来完成
每个软件构件都要应用逐步求精的概念
通过
很多次的迭代
来添加相关的细节
来进行细化
图中的活动图
展现了我们刚才的操作处理流的细节
我们为了表现对象的行为的时候
可以使用状态图
状态图描述从
一种状态到另一种状态的转化
它可以表示为
event-name
括号里面放上
parameter-list
然后我们要加上我们的
guard-condition条件
和我们的action expression
我们的动作表示
我们开发并且细化类
或者是构件的行为的时候
可以使用我们的状态图
其中
event-name表示事件
parameter-list包含了
与事件相关的
所有的数据参数
我们的guard-condition
采用对象约束语言
OCL来书写
描述了事件发生前
必须满足的条件
我们的action expression定义了
状态转换时
会发生的动作
针对状态的进入和离开两种情形
每个状态
都可以定一个entry
和exit两个动作
在大多数的情况下
这些动作与正在建模的类
的相关的操作是相对应的
另外 我们可以使用do
提示符提供一种机制
用来显示伴随此种状态的相关活动
而我们的include提示符则提供了
通过在状态定义中
嵌入更多的状态图的细节的方式
来进行细化的一种手段
下面我们看一下基于构件的软件工程的流程
我们刚才解释了
构件级的一些设计
如果我们可以设计好很多
优秀可复用的构件的话
我们基于构件的软件工程
就可以重用这些构件
基于构件的软件工程是一种强调
使用可复用的软件构件
来设计与构造计算机系统的过程
领域工程的目的是识别 构造
分类
和传播一组软件 构件
这些构件在某一特定的领域中
可以适用于现在
和未来的软件
基于构件的软件工程的流程
如这张图所示
这张图的上半部分是
领域工程的工作
在领域工程中
首先要进行领域分析
领域分析
可以产生一些领域模型
同时
领域分析当中
我们会产生软件架构开发
软件架构开发会用到
刚才分析出来的领域模型
软件架构开发的产出物
可以是一些复用的产品
也可以产生一些结构模型
结构模型
支撑复用产品的开发
然后在领域工程最终可以产生知识库中
可复用的产品和构件
在这张图的下半部分是软件工程的工作
那么我们软件工程是起始于用户的需求
进行系统分析
由于我们是基于构件的软件开发
我们在系统分析的时候
可以去参考领域模型
进行系统分析
系统分析会产生一些系统原型
会产生一些规范与设计
在规范与设计的阶段
我们同样可以参考领域工程
输出的结构模型
规范与设计的部分
可以产生一些构件
和分析设计模型
在软件工程领域产生构件的时候
我们可以复用在领域工程里面的
产品库中的可复用的产品和构件
通过以下软件工程中的
构件
最终我们可以生成
我们的目标的应用软件
这张图显示了我们
基于构件的软件工程开发的
整体的流程
那么基于构件的软件工程
我们是有一些可用的构件库
然后我们的构件应该具有一致的结构
应该具有软件的构件标准
例如 我们可以参考OMG的CORBA标准
和Microsoft COM标准
或者是Sun公司的JavaBeans标准
参照了一定的标准
我们才能支持它的复用
好
以上是我们关于
构件级设计分享的所有相关内容
这一部分我们分析了
构件级设计
还分析了基于构件的软件工程
今天的内容就到这里
谢谢大家
-课程概述
-1.1 软件的本质
-1.2 软件工程
--1.2 软件工程
-1.3 软件过程结构
-1.4 过程模型
--1.4 过程模型
-1.5 敏捷开发方法
-第1章 习题
--第1章 习题
-2.1 需求工程过程
-2.2 需求获取
--2.2 需求获取
-2.3 需求分析
--2.3 需求分析
-2.4 过程建模
--2.4 过程建模
-2.5 面向对象建模
-第2章 习题
--第2章 习题
-3.1 设计概述
--3.1 设计概述
-3.2 设计的概念
-3.3 设计模型元素
-3.4 体系结构概述
-3.5 体系结构风格
-3.6 构件级设计
-3.7 UI设计
--3.7 UI设计
-3.8 基于模式的设计
-第3章 习题
--第3章 习题
-4.1 UML概述
-4.2 UML 及UML中的事物
-4.3 UML关系和图
-4.4 UML 图细节(上)
-4.4 UML 图细节(下)
-第4章 习题
--第4章 习题
-5.1 软件测试策略
-5.2 测试传统的应用系统
-5.3 测试面向对象的应用系统
-5.4 测试web应用系统
-5.5 测试移动应用系统
-第5章 习题
--第5章 习题
-6.1 软件项目估算
-6.2 软件过程管理
-6.3 软件配置管理
-6.4 项目版本控制及调试
-第6章 习题
--第6章 习题