EagleBear2002 的博客

这里必须根绝一切犹豫,这里任何怯懦都无济于事

软件工程与计算II-12-详细设计

本文主要内容来自 SpriCoder的博客,更换了更清晰的图片并对原文的疏漏做了补充和修正。

详细设计基础

  1. 详细设计的出发点:软件详细设计是在软件体系结构设计之后进行,以需求开发的结果(需求规格说明和需求分析模型)和软件体系结构的结果(软件体系结构设计方案与原型)为出发点。

什么是详细设计

  1. 中层设计是对特定的模块的,以及针对特定模块的对象/类的低级设计

  1. 高层设计反映的是系统高层抽象的构件层次,描述系统的高层结构、关注点和设计决策。
  2. 中层设计反映的是组成模块的内部结构,例如数据定义、函数定义、类定义、类结构等。
  3. 低层设计则是深入莫夸或者类的内部,关注具体的数据结构、算法、类型、语法和控制逻辑等。
  1. 软件架构定义了模块的规范(对外抽象出来的接口):就是模块之间交互需要知道的信息
  2. 细节设计通过细节设计机制实现模块
    1. 中级:(子调制)-> OO->类指定
    2. 低级:DS. + ALG. ->实现类
  3. 细节设计要求设计者考虑模块的美观,功能和许多其他方面
    1. 详细设计中的质量要求:修改,维护,性能……

详细设计的输入

从需求、体系结构设计到详细设计

  1. 具体的模块的设计是详细设计
  2. 是对体系结构设计的更加精确的描述

详细设计是从哪里开始的

  1. 详细设计的目的是实现所有功能性需求和非功能性需求。

详细设计的上下文

  1. 模块的规格:导出/导入接口
  2. 职责分配:
    1. 有些职责来自 RE(SRS):典型的用例,领域模型,序列图,状态图
    2. 其他一些来自实施决策
  3. 在详细设计文档中需要明确定义:
    1. 模块结构及其接口(如果有更细的模块分解)
    2. 类结构、类协作、类接口(面向对象分析方法)
    3. 控制结构与函数接口(结构化分析方法)
    4. 重要的数据结构和算法逻辑(如果必要的话)

软件体系结构:构件之间的接口

详细设计的输出

结构化详细设计

结构化设计的思想

  1. 分解是降低复杂度的一种方法
  2. 按算法的分解:自然的分解想法
  3. 从数据流图向结构图的转换

降低复杂度的方法

  1. 分解:同一层次
  2. 抽象:从低层次抽象出高层次

如何描述一个系统?

  1. 一系列相互关联的过程
  2. 将输入转化为输出
  3. DFD:数据流图
    1. 数据流(箭头)
    2. 过程(圆圈)
    3. 数据存储(平行线)
    4. 外部实体(矩形)

按算法分解

  1. 分而治之

结构化设计

  1. 结构化设计的重心:从数据流图到结构图
  2. 上述转化过程:
    1. 寻找到输入的最高抽象点和输出的最高抽象点
    2. 根据输入、输出的最高抽象点,对模块进行划分
    3. 然后在一次对每个模块寻找最高抽象点,再进行模块分解,从而逐步求精得到树状的结构图
  3. 详细参考课本(201 页)

数据流图

结构图

最高抽象点

转换后的结构图

案例

面向对象详细设计

面向对象设计的思想:职责重要

职责

  1. 职责是执行任务(操作职责)或维护某些数据(数据职责)的义务。
    1. 行为职责通常由行为来履行。
    2. 数据职责通常由属性来完成。
    3. 可能会涉及到类之间的协作

职责驱动的分解

  1. 职责可以在不同的抽象层次上陈述。
  2. 职责可以分解。
  3. 可以将高级职责分配给高级组件。
  4. 职责分解可以作为分解组件的基础:职责既反映了操作义务,也反映了数据义务,因此职责驱动的分解可能与功能分解不同。

职责启发法

  1. 很好地分配职责有助于实现高凝聚力和低耦合。(高内聚)
  2. 确保模块职责不重叠。
  3. 仅当操作和数据有助于完成模块的职责时,才将其放置在模块中。

委托

  1. 委托是一种策略,其中一个模块(委托人)将职责交给另一个模块(委托人)。
  2. 代理帮你完成联系和收集的情况

面向对象设计的思想:协作重要

什么是协作

  1. 程序中的对象必须协作;否则,程序将仅由一个可以执行所有操作的大对象组成。-丽贝卡·维尔夫斯·布洛克等,《设计面向对象的软件》,Prentice Hall,1990 年:内聚性好一定意味着比较零散(类比较多)
  2. 同等重要的(作为继承)是相互负责地协作的对象社会的发明。这些社会形成了我所谓的系统机制,并代表了战略性架构决策,因为它们超越了各个类。 -[The C ++ Journal,Vol.2,No.1 1992 年,"与 Grady Booch 的访谈"]:每个对象都是相对自治的个体。
  3. 一个应用程序可以分解为许多不同的行为。
  4. 每个此类行为都是通过应用程序对象之间的独特协作来实现的:对象和对象之间的实践
  5. 每次协作,无论大小,都保证实现应用程序的行为
  6. 将面向对象的应用程序想象成通过关系连接的对象网络。
  7. 协作是通过网络追求特定行为的消息模式
  8. 协作分布在对象网络中,因此在任何地方都不存在

协同设计的需求

  1. 毕竟,我们正在尝试实现的是应用程序操作。
  2. 如果实现它们的协作设计不当,则应用程序将不准确或脆弱

面向对象详细设计的过程

  1. 面向对象:对象内部是容易理解的,之间的调用的理解是困难。
  2. 结构化:模块内部是困难的,之间的调用是容易的
  3. 概念类图的类和设计类图的类是不同的:
    1. 因为设计类图中有的类是辅助类。
  4. 设计模型重构
    1. 根据模块化的思想进行重构,目标是高内聚、低耦合
    2. 根据信息隐藏的思想筹够,目标是隐藏职责与变更

通过职责建立静态设计模型

抽象对象的职责

  1. 类表达了对对象族的本质特征的抽象,提供了构建一个对象的所需要的蓝图
  2. 职责分类
    1. 属性职责:对象的状态
    2. 行为职责:对象的行为

  1. + 是 public,- 是 private

抽象类之间的关系

  1. 整体存在则部分存在,部分存在则整体存在
  2. 上图需要好好背诵和记忆:重点掌握类图的画法

GRASP 原则

  1. 一般职责分配软件模式
  2. 不是“设计模式”,而是对象设计的基本原理
  3. 专注于对象设计的最重要方面之一:为类分配职责
  4. 强调适用性:并不是一个普适的
  5. 常见的一些特点:
    1. 低耦合:分配一个职责要保证低耦合度
    2. 高耦合:分配一个职责的时候要保持类的高聚合度
    3. 信息专家:将一个职责分配给专家-履行职责所必须的信息的类
    4. 创建者:创建规则在后面
    5. 控制者:控制规则在后面(避免大多数信息由一个类发出、组件相对较小、行为职责和数据绑定、职责单一)
拇指原则
  1. 当存在替代设计选择时,请仔细研究替代方案的凝聚力和耦合含义,并可能对替代方案的未来发展压力。
  2. 选择具有良好内聚性,耦合性和稳定性的替代方案。
信息专家
  1. 问题:在面向对象设计中分配职责的最基本原则是什么?
  2. 解决方案:将具有完成任务所必需的信息的班级分配给班级。
  3. 维护信息封装
  4. 促进低耦合
  5. 促进高内聚类
信息专家的例子
  1. 谁负责了解典型的销售点应用程序中的销售总额?(求总价) Sale

  1. 计算总计需要所有 SalesLineItem 实例及其小计。而这是只有销售(Sale)道的
  2. 这就是为什么 Sale 是信息专家。
  3. 因此(通过全部的情况进行开展的)

  1. 但是每个订单项都需要小计(数量乘以价格)。
  2. 根据专家的说法,SalesLineItem 是专家,知道数量并且与知道价格的产品规格相关联。

  1. 因此,职责分配给 3 个类别。

Eg.Case Study: 智能热水器
  1. 智能控制水温
    1. 周末水温高
    2. 夜晚水温低
    3. 生病等特殊情况水温高
    4. 度假水温低
  2. 概念模型
      1. 热水器控制器
        1. 模式
        2. 低温
        3. 高温
        4. 周末
      2. 时钟
    1. 接口:
    2. WaterHeaterController 和 Clock 怎么交互?
      1. 轮询
      2. 通知
  3. 怎么知道当前时间是该升温还是降温?
    1. Controller 自己保存特殊时间并计算(比较当前时间和特殊时间):Bad:多个职责。
    2. 由 SpecialTime 类保存特殊时间;Controller 调用 getSpecialTime()到特殊时间,再计算
      1. Bad:数据职责与行为职责的分离
      2. SpecialTime 是信息专家,对外给接口
    3. 由 SpecialTime 类保存特殊时间,并提供 isSpecialTime();Controller 调用方法
      1. Good:单一职责
    4. 谁有信息谁是专家,数据和功能不要分开
    5. 为什么同样是 get 方法
      1. 一个是合理的:商品那个,那个是因为商品和单价是分开的,所以是合理的
      2. 一个是不合理的:现在这个,因为只有一个数据就可以完成计算
      3. 一个是简单的 get 方法,不完全数据和行为
      4. 另一个是只需要这一个数据就可以了,并且行为封装在一起是合理的
      5. 类之间的关系的影响

添加辅助类

  1. 接口类
  2. 记录类(数据类)
  3. 启动类:从各种地方的初始化,进行转发和分派
  4. 控制器类
  5. 实现数据类型的类
  6. 容器类

添加辅助类后的设计模型

通过协作创建动态设计模型

抽象对象之间协作

  1. 从小到大,将对象的小职责聚合形成大职责;
  2. 从大到小,将大职责分配给各个小对象。
  3. 这两种方法,一般是同时运用的,共同来完成对协作的抽象。
  4. 顺序图
    • 可以用顺序图表示对象之间的协作。顺序图是交互图的一种,它表达了对象之间如何通过消息的传递来完成比较大的职责。
    • 包含两部分:对象本身和对象之间的信息流
  5. 信息分为:图示见课本 206 页
    1. 同步消息
    2. 异步消息
    3. 同步消息返回

  1. 对象结束之后可以在底下画一个 X 表示结束
  2. 状态图
    1. 除了顺序图,我们还可以通过状态图来表达软件的动态模型。UML 状态图(State Diagram)
    2. 主要用于描述一个复杂对象在其生存期间的动态行为,表现为一个对象所经历的状态序列, 引起状态转移的事件(Event),以及因状态转移而伴随的动作(Action)。一般可以用状态机对一个对象的生命周期建模,UML 状态图用于显示状态机(State Machine Diagram),重点在于描述 UML 状态图的控制流。而协作是:用复杂对象的状态图中的 Event 体现出对象之间消息的传递;用 Action 体现消息引发的对象状态的改变(行为)。

明确对象的创建

创建者模式
  1. 问题:谁负责创建某个类的新实例?
  2. 解决方案:根据潜在的创建者类与要实例化的类之间的关系,确定哪个类应创建类的实例。
  3. 问题:谁负责创建对象?
  4. 回答:如果有以下情况,则由创建者分配 B 类创建 A 类实例的职责:
    1. B 聚集了 A 对象
    2. B 包含了 A 对象
    3. B 记录了 A 的实例
    4. B 要经常使用 A 对象
    5. 当 A 的实例被创建,B 具有传递给 A 的初始化数据(也就是 B 是创建 A 的实例这项任务的信息专家)
    6. 在有选择的地方,更喜欢 B 聚合或包含 A 对象

  • 第一个(组合关系)
  • 第二个(单向被关联):比如访问数据库,你要访问的时候,我就给一个访问对象来使用,不用的时候归还就行。
  • 第三个(持有必要数据):根据业务的情况决定什么时候被创建,有时候 B 可以创建但是不知道什么时机来创建,如果 C 知道,那么我们可能让 C 创建对象,然后 B 进行初始化
  • 第四个(聚合关系):关系比较多,要看时机等什么时候合适
创建例子
  1. 谁负责创建 SalesLineItem 对象? 销售:往往是一旦有 sale 就会创建
  2. 找到聚合或者包含了 SalesLineItem 的物体类

  1. 创建者模式建议是 Sale
  2. 合作图是

创作者摘要
  1. 通过创建负责创建需要引用的对象的类的实例来促进低耦合
  2. 通过自己创建对象,它们避免依赖于另一个类为它们创建对象.
谁创建 Square / Piece / Player?

  1. Piece 的创建(那个关联性最强,就是用哪一个来创建)
    • Player?对
    • Board?
  2. Squares 的创建:Board 创建
  3. Player 的创建:用 Game 创建(没有大问题)

控制器

  1. 问题:如何分配处理系统事件的职责?
  2. 解决方案:如果程序从其图形界面以外的其他来源接收事件,请添加事件类以将事件源与实际处理事件的对象分离。
控制方式
  1. 将处理系统事件消息的职责分配给代表以下选项之一的类:
    1. 整个组织的业务(立面控制器)。
    2. 整个系统(外观控制器)。
    3. 在问题域中真实操作解决问题的人(角色控制器)。
    4. 自动化解决用例的模块(用例控制器)。
控制者
  1. 购买项目用例中的系统事件
    1. 输入部分
    2. 结束售卖
    3. 结账
  2. 谁负责输入
  3. 控制者有四种处理对象
    1. 整个系统 Post
    2. 整个业务商店
    3. 在现实生活中活跃在任务中的
    4. 在系统中机器处理这个部分

  1. 按了按钮就会直接进行响应

  1. 用 POST 方法申请 Item
  2. 谁设定 1:enterItem 接口?和需求有关
  3. 界面变更和逻辑变更的频率时不同的,需要分开,Controller 存在的必要性
控制器总结
  1. Controller 本身不是面向对象的,它包含很多复杂的逻辑
  2. 使用控制器对象可使外部事件源和内部事件处理程序彼此独立于他们的类型和行为
  3. 控制器对象可能变得高耦合和职责上低内聚
什么是棋盘游戏的控制者?

选择合适的控制风格(重要)

  1. 集中式控制风格
  2. 委托式控制风格
  3. 分散式控制风格

  1. 系统行为的逻辑在对象(组件)网络之间分布的方式。
  2. 分散的:系统行为的逻辑通过对象网络“广泛传播
  3. 集中式:一个额外的控制器记录系统行为的所有逻辑。
控件控制情况
  1. 做出决定并指导他人行动的对象是控制器。
  2. 他们总是与他人合作有两个原因:
    1. 收集信息以便做出决定
    2. 并呼吁其他人采取行动。
  3. 他们的重点通常是决策而不是执行后续操作:他们的最终职责通常会转移给对控制器负责的较大任务有更多特定职责的其他人
控制器协作情况
  1. 控件样式是一种将所有系统行为分布在对象(组件)网络之间的方式。
    1. 集中式:几个控制器记录所有系统行为的逻辑
    2. 委托式:通过对象网络分配决策,由几个控制器进行主要决策
    3. 分散式:所有系统行为都通过对象网络广泛传播
集中式控制风格
  1. 容易找到做出决定的地方
  2. 易于查看如何制定决策以及如何更改决策流程
  3. 控制器可能会变得的庞大,复杂且难以理解,维护,测试等。
  4. 控制器可以将其他组件视为数据存储库
    1. 增加耦合
    2. 破坏信息隐藏

  • 都是他在调用别人

  • 部分去中心化的中心模式(如上)

  • 上图例子:通过一些部分特别的方式读取输入

  • 上图例子:依赖状态来降低指向性
  • 控制器只负责状态转移,不管具体的状态处理

  • 更加分散的设计:进行分发,只负责协调
控制的启发 1
  1. 避免大多数消息都来自单个组件的交互设计。
  2. 保持组件较小。
  3. 确保并非仅将全部职责分配给几个组件。
  4. 确保操作职责与数据职责一致。
委托式控制风格
  1. 作出决策的对象不只有一个,职责的分解决定了控制对象的层次。
分散式控制风格
  1. 其特点是拥有许多组件,几乎没有数据,职责也很少。
  2. 很难理解控制流。
  3. 组件无法独自完成很多工作,从而增加了耦合。
  4. 隐藏信息是很难的。
  5. 内聚性通常很差。
  6. 很少有模块化原则可以满足。
  7. 完全靠对象自治的方式来实现自己的职责。
控制启发二
  1. 避免要求每个组件发送许多消息的交互。

为类间协作开发集成测试用例

详细设计的集成测试

  1. 类间协作的集成测试
    1. 重点针对复杂逻辑(交互比较多)
    2. 自顶向下或者自底向上的集成
  2. Mock Object
    1. 不是 stub

  1. 测试用例

类间协作的集成测试

详细设计文档描述和评审

  1. 所有模块都应该尽量详细

详细设计验证

  1. 评审:应该很好的展开
  2. 度量
    1. 模块化度量
  3. 测试
    1. 协作测试

  1. 设计的信息程度对后继开发人员是否足够?就是给不同人应该差不太多。

第三阶段

  1. 制品合理性