type
status
date
slug
summary
tags
category
icon
password

4.1 软件设计相关概念

4.1.1 软件设计的概念

  • 软件设计的概念:软件设计定义为软件系统或组件的架构、构件、接口和其他特性的定义过程(也就是设计过程)及该过程的结果。
  • 软件设计是什么
    • 软件生命周期中的一个活动(软件的生命周期是计算机软件从开发到维护再到被淘汰的全过程。包含:可行性分析,需求分析,总体设计,详细设计,构建,测试,维护)
    • 进行软件编码的基础(因为软件设计是编码的前一个阶段)
    • 软件需求分析被转化为软件的内部结构(因为软件设计的前一个阶段是需求分析,后一个阶段是编码)
    • 是连接用户需求和软件技术的桥梁(跟上一个差不多)

4.1.2 软件工程中的设计

  • 软件工程设计的模型输入(实际上就是要输入进行需求提炼时建立的模型)
    • 数据模型
    • 功能模型
    • 行为模型
  • 软件工程设计的分类
    • 数据设计(如何根据数据模型设计数据结构)
    • 架构设计(接下来的三个都是跟系统设计有关系的)
    • 接口设计
    • 组件设计

4.1.3 软件设计质量

  • 好的软件设计的三个特点
    • 设计必须实现在需求分析模型中包含的所有明确要求,必须满足客户所期望的所有隐含要求(从客户角度分析)
    • 设计必须对编码人员、测试人员及后续的维护人员是可读可理解的(从后续工作的角度分析)
    • 设计应提供该软件的完整视图,从实现的角度解决数据、功能及行为等各领域方面的问题(从软件本身分析)
  • 设计质量指导原则
    • 设计应该是一种架构(总体设计)
    • 设计应该是模块化的(详细设计)
    • 设计应该包含数据、体系结构、接口和组件各个方面(设计的分类:数据设计,架构设计,接口设计,组件设计)
    • 设计由软件需求分析过程中获得信息驱动,采用可重复使用的方法导出(包含所有需求分析模型中的信息)
    • 设计应该采用正确清楚的表示法(以便后续人员理解)
  • 设计的质量属性(就是从哪些方面来评估设计的质量)
    • 功能性
    • 易用性
    • 可靠性
    • 性能
    • 可支持性:包含扩展性、适应性、可维护性
  • 软件工程的设计活动
    • 软件架构设计(也被称为顶层设计):描述软件的架构,划分不同的组间
    • 软件详细设计:详细描述各组件以便编码实现

4.1.4 设计相关的概念

  • 抽象:是“忽略具体的信息不同事物看成相同事物的过程”
    • 两种抽象方式(ppt上说是抽象机制,感觉不太清楚)
      • 参数化(就是把事物抽象成数据)
      • 规范化(下面详细介绍)
    • 规范化抽象
      • 数据抽象(抽象信息):描述数据对象的冠数据集合(也就是包含的信息,都是名词),下面是一个示例:
        • 包含属性:门的类型、转动方向、开门机关、重量和尺寸
      • 过程抽象(抽象动作):具有明确和有限功能的指令序列(也就是一个操作的过程描述,都是动词),下面是一个示例:
      • 开门(就是一个过程,下面就是对这个过程的细化描述)
        • 一系列过程:走到门前,伸出手并抓住把手,转动把手并拉门,离开打开的门等
    • 抽象有助于定义构成软件的过程(或信息)实体(抽象生成的“相同事物”实际上就是软件过程的实体)
  • 体系结构:软件的整体结构和这种结构为系统提供概念上完整性的方式
    • 体系结构设计可以使用的模型
      • 结构模型
      • 框架模型
      • 动态模型
      • 过程模型
      • 功能模型
  • 设计模式(一个例子就是抽象工厂):在给定上下文环境中一类共同问题的共同解决方案
    • 设计模式的微观结构
      • 实体模式
      • 结构模式
      • 行为模式
  • 模块化:软件被划分为命名和功能相对独立的多个组件(通常称为模块),通过这些组件的集成来满足问题的需求。(模块个数越多,模块成本越小,但是集成成本越高;模块数越少,模块成本越大,但是集成成本越低。所以需要找到最优的模块数。即模块化的基本问题是:如何分解软件系统以达到最佳的模块划分)
    • 模块化的设计标准
      • 模块化的可分解性:可分解为子模块
      • 模块化的组合性:组装可重用组件
      • 模块化的可理解性:可作为独立单元理解
      • 模块化的连续性:需求变化只影响单个模块
      • 模块化的保护:模块内异常只影响自身
  • 信息隐藏:模块应该具有彼此相互隐藏的特性。即模块定义和设计时应当保证模块内的信息(过程和数据)不可以被不需要这些信息的其他模块访问(是实现高内聚低耦合的一个途径)
    • 信息隐藏原则定义和隐藏了模块内的过程细节和模块内的本地数据结构
  • 功能独立:每个模块只负责需求中特定的子功能,并且从程序结构的其他部分看,该模块具有简单的接口
    • 功能独立的优点
      • 易于开发:功能被划分,接口被简化(这个时候实现模块的话就只需要考虑需要完成的需求即可)
      • 易于维护(和测试):次生影响有限,错误传递减少,模块重用(就是一个模块的问题不会传到另一个模块里面)
    • 功能独立性的衡量标准(模块的功能独立性强=高内聚低耦合)
      • 内聚性:模块的功能相对强度
      • 耦合性:模块间的相互依赖程度
  • 精化:逐步求精的过程(统一过程模型中的精化就是这个意思)
    • 抽象与精化的关系
      • 抽象使设计师确定过程和数据,但不局限于底层细节
      • 精化有助于设计者在设计过程中揭示底层细节
  • 重构:不改变组件功能和行为条件下,简化组件设计(或代码)的一种重组技术
    • 重构的方法:检查现有设计的冗余情况未使用的设计元素无效或不必要的算法、较差的构建方式或不恰当的数据结构,或任何其他可被更改从而优化设计的问题

4.1.5 设计技术概要

  • 数据设计:数据设计(有时也被称为数据架构)构建高层抽象(客户/用户的数据视图)的数据模型、信息模型
    • 数据设计的相关概念
      • 数据建模:如关系实体图、数据字典、类图、类关系图
      • 数据结构:计算机存储组织数据的方式
      • 数据库:按照数据结构来组织管理数据的仓库
    • 组件级别数据设计的设计原则
      • 应用于功能和行为系统分析的原则也应适用于数据设计
      • 所有的数据结构及其对应的操作都应该确定
      • 建立数据字典并在数据定义和程序设计中应用
      • 低层次的数据设计应该推迟到设计的后期过程
      • 数据结构的表示应该只对直接使用数据结构中数据的模块可见(信息隐藏)
      • 开发有用的数据结构及其对应操作的程序库
      • 软件设计和编程语言应该支持抽象数据类型的定义与实现
  • 架构设计(软件体系结构设计):程序或计算系统的软件体系结构是指系统的一个或多个结构,它包括软件构件、构件的外部可见属性以及它们之间的相互关系(背前面的那个体系结构的定义也是一样的)
  • 注意:体系结构并非可运行的软件
    • 体系结构设计:设计是体系结构的一个实例。如:客户机服务器体系结构,可对应Java EE或.NET设计框架
    • 体系结构风格:软件体系结构风格就是施加在整个系统设计上的一种变换,目的是为系统的所有构件建立一个结构(这个结构换句话说就是一种软件构建的组织风格)。内容如下:
      • 系统需要执行的函数功能组件集(如数据库、计算模块)
      • 组件之间通信、协同和合作的连接器
      • 组件集成构成系统的约束
      • 设计人员通过分析系统组成部分的已知特性,理解其整体特性的语义模型分析
    • 体系结构风格的主要类型
      • 以数据为中心架构
      • 数据流体系架构
      • 调用和返回体系架构
      • 层次架构
      • 面向对象架构
    • 体系结构的组织和细化。两个基本问题如下:
      • 控制结构:在架构内部如何实现管理控制?是否存在清楚的控制层次?组件如何在系统中传递控制(控制流)
      • 数据传递:组件之间如何进行数据传递?数据流是否连续,或者传递给系统的数据对象是否零散?(数据流)
  • 接口设计(包含界面设计)
    • 环境分析确定了用户接口操作的物理结构和社会结构(接口设计就是用户接口操作的物理结构和社会结构的设计)
    • 高效的用户界面设计的三条准则
      • 以用户为中心
      • 减少用户的记忆负担
      • 保持界面的一致性
  • 组件设计
    • 构件:系统中模块化的、可部署的和可替换的部件,该部件对外提供一组接口
    • 组件:组件(Component)是对数据和方法的简单封装
    • 组件设计的分类
      • 面向对象的组件设计:类与操作的设计
      • 面向过程的组件设计:函数与模块的设计
  • 部署设计
    • 以部署环境创建开始,在整个生命周期阶段中处于逻辑设计和技术需求阶段
    • 部署环境包含整个解决方案的逻辑架构和服务质量(QoS)需求
    • 部署架构设计是一个反复迭代的过程,通常需要多次查看QoS要求和多次检查先前的设计,平衡取舍,最终满足项目的业务目标

4.2 面向对象设计

面向过程的设计没讲捏,那就是不考了

4.2.1 面向对象的设计原则

  • 面向对象设计的特点
    • 面向对象设计强调定义软件对象(也就是定义类),并且使这些软件对象相互协作来满足用户需求
    • 面向对象分析和设计的界限是模糊的(所以在这里还会用到前面的什么用例图啥的,因为边界模糊所以用例图就不好说是需求分析还是设计了),从面向对象分析到面向对象设计是一个逐渐扩充模型的过程。分析的结果通过细化直接生成设计结果,在设计过程中逐步加深对需求的理解,从而进一步完善需求分析的结果
    • 分析和设计(面向对象的需求分析和设计都是迭代的过程)活动是一个反复迭代的过程
    • 面向对象方法学在概念和表示方法上的一致性,保证了各个开发阶段之间的平滑性(也是面向对象需求分析和设计接线模糊的原因)
  • 面向对象设计的四个层次
    • 确定系统的总体结构和风格,构造系统的物理模型,将系统划分成不同的子系统(顶层设计)
    • 中层设计:对每个用例进行设计,规划实现用例功能的关键类确定类之间的关系
    • 进行底层设计:对每个类进行详细设计,设计类的属性和操作优化类之间的关系
    • 补充实现非功能性需求所需要的类(上面的设计都是在实现功能性需求)
  • 面向对象设计的注意点
    • 对接口进行设计
    • 发现变化并且封装它
    • 先考虑聚合然后考虑继承
  • 面向对象设计——强内聚(可以实现功能独立):设计类的原则是一个类的属性和操作全部都是完成某个任务所必须的,其中不包括无用的属性和操作
  • 面向对象设计——弱耦合(可以实现功能独立):在面向对象设计中,耦合主要指不同对象之间相互关联的程度
    • 对象不可能是完全孤立的,当两个对象必须相互联系时,应该通过类的公共接口实现耦合,不应该依赖于类的具体实现细节
    • 强耦合的缺点
      • 使对象不易于理解(因为看一个对象的属性和操作的时候还要涉及其他对象的属性和操作)
      • 增加测试和维护的难度(因为错误可能会从一个对象上扩散到另一个对象上)
    • 耦合方式
      • 交互耦合:如果对象之间的耦合是通过消息连接来实现的,则这种耦合就是交互耦合。在使用交互耦合的时候应当尽量减少参数的个数。
      • 继承耦合:继承耦合是一般化类与特殊化类之间的一种关联形式,设计时应该适当使用这种耦合。在使用继承耦合的时候如果一般化类设计不当可能影响特殊化类;在设计特殊化类的时候也需要去尽量使用一般化类提供的属性和操作,充分发挥继承的优势
  • 面向对象设计——可重用性:软件重用是从设计阶段开始的,所有的设计工作都是为了使系统完成预期的任务,为了提高工作效率减少错误降低成本,所以需要充分考虑软件元素的重用性。
    • 重用性的含义
      • 尽量使用已有的类,包括开发环境提供的类库和已有的相似的类(考虑现有重用)
      • 如果确实需要创建新类,则在设计这些新类时考虑将来的可重用性(考虑将来重用)
  • 面向对象设计——框架:框架是一组可用于不同应用的类的集合(就好像所有类的公共方法一样。想想java的调试框架junit,所有可执行的java代码都是可以使用这个框架提供的接口的)。框架中的类通常是一些抽象类并且相互有联系,可以通过继承的方式使用这些类(所以当框架的类不能满足需求的时候,是选择通过继承或者聚合的方式来实现需要的功能,而不是直接修改框架的类)
  • 面向对象的基本设计原则
    • 开闭原则:组件应该对外延具有开放性,对修改具有封闭性(就是可以扩展但是不能修改。想想java里面如果说一个类有很多子类,这个时候如果去修改了这个类,就会带来严重的后果,所以是不能修改的。但是这个类还能被其他的类继承,所以是可外延扩展的)
    • Liskov替换原则:从基类导出的类(子类)传递给组件时,使用基类的组件应该仍然能够正确完成其功能(也就是java的多态,用Person temp= new Student();这个时候就是将子类Student传递给组件,但是这个组件是使用基类Person的,这个时候java就会通过多态使temp调用的是子类的方法)
    • 依赖倒置原则:依赖于抽象,而非具体实现(正常的面向过程的依赖都是依赖于实现的,但是这个是依赖于抽象,所以是依赖倒置)
    • 接口分离原则:多个客户专用接口比一个通用接口好
    • 发布复用等价性原则:复用的粒度就是发布的粒度(就是将复用的类打包在一起然后发布,这个时候复用的内容跟发布的内容是一样的。所以说复用的粒度等于发布的粒度)
    • 共同封装原则:应该将易变的类放在同一个包里,将变化隔离出来(将所有变化的类放在一起)
    • 共同复用原则:根据内聚性进行分组,只有那些一起被复用的类才应该包含在一个包中

4.2.2 面向对象的架构设计

  • 面向对象设计的活动(这个是这几节的总论)
    • 系统架构设计(架构设计)
    • 用例设计(组件设计,实际上是细化用例,将用例用多个类来表示)
    • 类设计(数据设计,是根据细化用例生成的类,对其中的每一个类都进行属性和操作的设计)
    • 数据库设计(数据设计)
    • 用户界面设计(接口设计)
  • 面向对象设计的活动——架构设计(系统设计):架构设计的目的是要勾画出系统的总体结构,这项工作由经验丰富的架构设计师主持完成
    • 架构设计的输入:用例模型、分析模型(实际上就是面向对象的需求分析中产生的模型)
    • 架构设计的输出:物理结构、子系统及其接口、概要的设计类
  • 架构设计的步骤(实际上就是按照上面提到的架构设计的输出来一步步做的)
    • 构造系统的物理模型(对应架构设计输出中的物理结构)
      • 首先用UML的配置图(部署图)描述系统的物理架构(画出总体框架)
      • 需求分析阶段捕获的系统功能分配到这些物理节点上。(补充必要细节)
      • 配置图上可以显示计算节点的拓扑结构、硬件设备配置、通信路径、各个节点上运行的系统软件配置、应用软件配置。(补充不必要的细节)
    • 一个图书馆信息管理系统的物理模型如后图所示:
      • notion image
    • 设计子系统(对应架构设计输出中的子系统。而每一个子系统实际上也是一个设计类,然后对外暴露了一组接口,所以也对应了架构设计输出中的接口以及概要的设计类)
      • 子系统概述
        • 对于一个复杂的软件系统来说,将其分解成若干个子系统,子系统内还可以继续划分子系统或包(有点像数据流图中的层次数据流图,一步步将数据加工划分),这种自顶向下、逐步细化的组织结构非常符合人类分析问题的思路。
        • 每个子系统与其它子系统之间应该定义接口在接口上说明交互信息,注意这时还不要描述子系统的内部实现(接口是对外暴露的,所以不要描述内部信息,是为了做到强内聚弱耦合)。
        • 可用UML组件图表示。
      • 设计子系统的步骤
        • 明确划分各个子系统的方式
          • 按照功能划分,将相似的功能组织在一个子系统中
          • 按照系统的物理布局划分,将在同一个物理区域内的软件组织为一个子系统;
          • 按照软件层次划分子系统,软件层次通常可划分为用户界面层、专用软件层、通用软件层、中间层和数据层,见后图:
            notion image
        • 定义子系统之间的关系。子系统之间有如下几种关系
          • “请求-服务”关系,“请求”子系统调用“服务”子系统,“服务”子系统完成一些服务,并且将结果返回给“请求”子系统。
          • 平等关系,每个子系统都可以调用其它子系统。
          • 如果子系统的内容相互有关联,就应该定义它们之间的依赖关系。在设计时,相关的子系统之间应该定义接口,依赖关系应该指向接口而不要指向子系统的内容
          • 注意:如果两个子系统之间的关系过于密切,则说明一个子系统的变化会导致另一个子系统变化,这种子系统理解和维护都会比较困难。解决子系统之间关系过于密切的办法基本上有两个:
            • 重新划分子系统,这种方法比较简单,将子系统的粒度减少,或者重新规划子系统的内容,将相互依赖的元素划归到同一个子系统之中
            • 定义子系统的接口,将依赖关系定义到接口上
        • 定义子系统的接口:每个子系统的接口上定义了若干操作体现了子系统的功能,而功能的具体实现方法应该是隐藏的(信息隐藏,为了做到弱耦合),其他子系统只能通过接口间接地享受这个子系统提供的服务,不能直接操作它
    • 非功能需求设计(面向对象设计的四个层次中就有补充实现非功能需求所需的类,所以这个步骤在设计阶段就应该体现出来):分析阶段(也就是面向对象的需求分析阶段)定义了整个系统的非功能需求,在设计阶段要研究这些需求设计出可行的方案。
      • 非功能性需求包括:系统的安全性、错误监测和故障恢复、可移植性和通用性等等
    • 具有共性的非功能需求一般设计在中间层和通用应用层(这样不同的应用都可以使用到非功能性需求的部分),目的是充分利用已有构件,减少重新开发的工作量

4.2.3 面向对象的用例设计与类设计

类图对应着面向对象模型工具中的数据模型,也就是画类图和类关系图了
这里提到的用例设计并不是需求分析中去找用例,而是细化用例。实际上细化一个用例的最终目的就是生成这个用例的类图,而类设计只是细化用例(用例设计)中的一步,因为类设计比较重要所以单拎出来讲。所以这里是将用例设计和类设计放在一起

用例设计(细化用例)

用例设计的目的是生成类图(在用例设计中就使用到了类设计,在这个类图中就能看见所有的类,类的属性,类的操作以及类之间的关系)
  • 细化用例的步骤
    • 根据分析阶段产生的高层类图和交互图,由用例设计师研究已有的类,将它们分配到相应的用例中。(分配)
    • 检查每个用例功能依靠当前的类能否实现,同时检查每个用例的特殊需求是否有合适的类来实现。(检查)
    • 细化每个用例的类图描述实现用例的及其类之间的相互关系(细化)
  • 类:类是包含信息(也就是类的属性)和影响信息行为(对类的属性进行操作就会影响信息的行为,也就是类的操作)的逻辑元素。(也就是java中的类了)
    • 类的符号:由三个格子的长方形组成,有时下面两个格子可以省略(但是还是都画出来吧,就算是空的也摆一个空的长方形)。最顶部的格子包含类的名字,类的命名应尽量用应用领域中的术语,有明确的含义中间的格子说明类的属性(属性一般都是要带上类型的)。最下面的格子类的操作行为(操作一般都是要带上接收的参数类型还有返回值类型的)。如下图:
      • notion image
  • 类间关系
    • 依赖关系:依赖描述了类与类之间的一种使用与被使用的关系( use-a ),是一种弱关联关系(所以依赖关系是虚线,但是关联关系是实线)
      • 依赖关系的符号及其含义:用一个带虚线的箭头表示,由使用方指向被使用方(从英文上理解,这里是client use supplier,所以是从client指向supplier)
        notion image
        注意:依赖多为单向关系;若需建立双向联系,设为关联关系比较方便
        对上图的理解:课表依赖于课程。课程的变化有可能导致课表的变化;但课表的变化不会影响课程
        依赖线上写的是使用到supplier的函数的函数名,比如上图中就应该写add以及remove
    • 关联关系:关联关系是一种拥有(has-a)的关系,它使一个类知道另外一个类的属性和方法;通常含有“知道”、 “可以调用”的含义。在java代码常通过成员变量实现
      • 关联关系的符号及其含义:双向关联关系用带双箭头的实线或者无箭头的实线表示。单向关联用一个带箭头的实线表示,箭头指向被关联的对象,即导航性(也可以从英文理解,A has B所以箭头是从A指向B的,但是双向关联就是双向has,所以这个时候就是双箭头了)
        notion image
        注意:如果有关联关系,A的属性一栏就不用再列出对象B了(也就是A属性里面的B通过关联关系建立)
    • 聚合关系:聚合是特殊形式的关联,聚合表示对象之间的整体与部分(是物理上的整体与部分)的关系(这个时候部分离开了整体还是能够存在的,如车和车轮)。在java代码中常通过成员变量实现
      • 聚合关系的符号及其含义:聚合关系用空心菱形加实线箭头表示,空心菱形在整体一方箭头指向部分一方(聚合关系是特殊形式的关联,所以是在关联关系的基础上加上了一个空心菱形表示整体。箭头指向部分一方也可以使用关联关系的has来进行理解)
      • 如下图:
        • notion image
    • 组合关系:组合是特殊形式的聚合,在表示对象之间的整体与部分的前提下(聚合关系的性质),组合关系表示更为强烈的所有权关系以及相同的生存周期(这个时候部分离开了整体就没办法存在,比如人和手。这个时候手离开了人就没办法存在)。在java代码中常通过成员变量实现
      • 组合关系的符号及其含义:组合关系用实心菱形加实线箭头表示,实心菱形在整体一方箭头指向部分一方(组合关系是特殊形式的聚合关系,所以是在聚合关系的基础上把空心菱形变成了实心菱形)
      • 如下图:
        • notion image
    • 泛化关系:泛化定义了一般元素特殊元素之间的分类关系;也就是通常所说的类的继承关系(跟用例图中的泛化关系是一样的,英文也是is a kind of)
      • 泛化关系的符号及其含义:泛化表示为一头为空心三角形的实线,从子类指向父类(与用例图中的泛化关系是一样的,也是带三角形的实线箭头。箭头指向一样可以使用英文理解,就是子类is a kind of 父类,所以是从子类指向父类)
      • 如下图:
        • notion image
          注意:类名写成斜体字是表示该类是抽象类
  • 类间关系的辨析
    • 关联关系与依赖关系
      • 当类A的实例与类B的实例存在1对1或1对多的关系时,设为关联关系比较方便。例如:
        • B从属于类A,A需要知道B的信息(这个时候就构成了A has B了),同时B也需要知道A的一些信息(但双方对信息的需求是有差异的)。
        • 客户和订单,每个订单对应某个特定客户,每个客户对应许多订单。(这个时候客户has订单)
        • 再如公司和员工,每个员工对应某个特定的公司,每个公司对应许多员工。(这个时候公司has员工)
      • 反之,若两个类的实例的关系是多对多,或者不存在固定的隶属关系(就是不是has的关系),设为依赖关系比较方便。例如:
        • 充电器和手机(手机use充电器,而不是手机has充电器)
        • 自行车和打气筒之间(自行车use打气筒,而不是自行车has打气筒)
        • 共享单车和使用者(使用者use共享单车,而不是使用者has共享单车)
      • 对于短暂的关系(即一个类不需要同另一个类维持长时间的连接,但确实偶尔会用到另一个类),用依赖关系
      • 另外,也可从类A和类B需相互了解的信息多少来判断:若类A只需用到类B的少量信息或功能无需知道类B的全部情况,设为依赖关系比较方便。(也就是只使用部分就设为关联关系)
      • 依赖的耦合程度弱于关联(所以说依赖是一种弱关联关系)。如果某些功能既可以用依赖实现,也可以用关联实现,则倾向于用依赖,会减少耦合,有时能减轻程序的维护量。
      • 注意:类之间耦合的强弱程度,仅是软件设计时考量的诸多因素之一。虽然提倡“弱耦合,强内聚”,但也不必任何时候都坚持先用依赖关系(有的时候显然使用关联关系会比使用依赖关系好处理很多,这个时候就不会因为为了做到强内聚弱耦合而去使用依赖关系)
    • 聚合关系与组合关系
      • 聚合中,代表部分事物的对象可以属于多个聚合对象,即为多个聚合对象共享,而且可以随时改变它所从属的聚合对象。在组合中,代表部分事物的对象只属于一个组合对象脱离了整体,该部分也没有存在的意义
      • 聚合中,即使销毁了代表总体的对象,代表部分的对象不一定会被销毁;部分的对象与总体的对象的生存周期可以不同。在组合中,一旦销毁了代表总体的对象也就销毁了代表部分的对象部分与总体的生存周期是相等的
    • 示例如下:
      • 鸭群与鸭子具有聚合关系。汽车由引擎、轮胎以及其它零件组成,也是聚合关系。(因为鸭群没了,但是鸭子还在;车子没了,轮胎还在,所以是聚合)
      • 一个人由头、四肢等各种器官组成,因为人与这些器官具有相同的生命周期,是组合关系。(但游戏中不一定成立😀。如果人死了,这些器官也挂了,所以是组合)
  • 几种类的概念
    • 边界类:是系统内部与系统外部的业务主角(也就是数据流图中的外部实体,或者是用例图中的参与者)之间进行交互建模的类(也就是提供用户和系统交互功能的类)。下面是一些常见的边界类:
      • 用户界面类:帮助与系统用户进行通信的类(用户和系统之间交互)
      • 系统接口类:帮助与其他系统进行通信的类(其他系统和系统之间交互)
      • 设备接口类:为用来监测外部事件的设备(如传感器)提供接口的类(其他设备和系统之间交互)
    • 所以说白了边界类就是提供系统使用者和系统交互功能的类
      • 边界类的符号(常用右边的形式,也就是类图中的形式):
        • notion image
    • 控制类:控制类用于对一个或几个用例所特有的控制行为进行建模,它描述用例业务逻辑的实现,控制类的设计与用例实现有着很大的关系。(控制类就是操作实体类的类)
      • 控制类的符号(常用右边的形式,也就是类图中的形式):
        • notion image
    • 实体类:实体对象的抽象,通常来自现实世界,用来描述具体的实体,通常映射到数据库表格与文件中
      • 实体类的符号(常用右边的形式,也就是类图中的形式)
        • notion image
  • 为了便于理解边界类、控制类和实体类,下面给出一个分析类图的例子
    • notion image
      其中,边界类:输入成绩窗口、统计成绩窗口、教师面板;控制类:统计成绩;实体类:课程
  • 如何对类进行分类?(注意:类图是对一个用例构建的)
    • 如何找边界类?
      • 参与者与用例之间应当建立边界类
      • 用例与用例之间如果有交互,应当为其建立边界类
      • 如果用例与系统边界之外的非人对象有交互(比如其他的系统或者设备等),应当为其建立边界类
      • 在相关联的业务对象有明显的独立性要求,即它们可能在各自的领域内发展和变化,但又希望互不影响时,也应当为它们建立边界类
    • 如何寻找控制类?
      • 控制类来源于对用例场景中动词的分析和定义
      • 控制类主要起到协调对象的作用(说明白一点就是链接边界类和实体类或者实体类和实体类),例如从边界类通过控制类访问实体类,或者实体类通过控制类访问另一个实体类。
      • 如果用例场景中的行为在执行步骤、执行要求或者执行结果上具有类似的特征应当合并或抽取父类
    • 如何寻找实体类?
      • 实体类用于对必须存储的信息和相关行为进行建模
      • 实体类源于业务模型中的业务实体(就是来自真实世界),但出于对系统结构的优化,可以在后续的过程中被分拆、合并
  • 细化用例的步骤(again)
    • 通过扫描用例中所有的交互图识别参与用例解决方案的类。在设计阶段完善类、属性和方法。例如,每个用例至少应该有一个控制类,它通常没有属性而只有方法,它本身不完成什么具体的功能,只是起协调和控制作用(包含了最上面提到的细化用例步骤中的分配和检查部分。分配就是这里说到的识别参与用例的类;检查实际上就对应了后面的例子,也就是需要检查每一个用例有没有控制类
    • 添加属性的类型、方法的参数类型和方法的返回类型(描述每一个类,实际上这一步也是在设计类了)
    • 添加类之间的关系,包括关联、依赖、泛化、聚合、组合等(描述类之间的相互关系)
    • (以上两条就对应了最上面提到的细化用例步骤中的细化步骤,就是描述类和类之间的相互关系)
  • 经过用例设计(细化用例)以及类设计,就可以得到类似下图的类图
    • notion image

类设计

类设计的目的就是在用例设计(细化用例)的过程中,去设计每一个类的属性、操作以及类之间的相互关系,所以类设计是用例设计中的一环
  • 类设计的步骤
    • 定义类的属性:用所选择的编程语言定义每个类的属性。类的属性反映类的特性,通常属性是被封装在类的内部,不允许外部对象访问(需要做好信息隐藏)。定义类属性的注意点如下:
      • 尽量减小类的粒度(也就是在类里面少定义一些属性),这样有利于实现和复用
      • 如果一个类的属性太多则需要考虑是否需要分离出一个类
      • 尽量使用编程语言所提供的类型,这样可以提高可维护性和可理解性
      • 尽量使用简单的数据结构,这样可以提高可维护性和可理解性
    • 定义类的操作:由构件工程师为每个类的方法设计必须实现的操作,并用自然语言或伪代码描述操作的实现算法。定义类的操作的注意点如下:
      • 检查类应当实现的功能性需求是否实现
      • 检查类应当实现的非功能性需求是否实现
      • 设计应当在类接口中定义的操作
      • 应当考虑一些特殊情况,如中断等
    • 定义类之间的关系(这里好像是指针对了关联关系):有如下两种(qyj讲的稀烂,这里实际上还需要去考虑类之间的依赖、泛化、关联、聚合、组合关系)
      • 设置基数:一个类的实例与另一个类的实例之间的数量联系。在成绩管理管理系统中,“课程”类和“教师”类关联,如果需求说明中有“一位教师可任课最多3门”,那么它们之间的基数为1:0..3
      • 使用关联类:可以放置与关联相关的属性。(也就是独立出来一个类,来存储两个类之间关联的信息,如教师和课程之间的“教学班编号”,教学班编号既与老师信息有关,也与课程信息有关,所以这个时候老师和课程这两个类就通过这个“教学班编号”类关联在一起了)
    • 如下图:
      • notion image
        注意:0...3代表的是一个老师可能有0、1、2、3门课(qyj讲的什么东西,用一堆专业术语讲得巨抽象)

4.2.4 UML顺序图

顺序图对应着面向对象模型工具中的行为模型
  • 什么是顺序图
    • 顺序图是强调消息时间顺序的交互图(交互实际上就是一种行为)。
    • 顺序图描述了对象之间传送消息的时间顺序,用来表示用例中的行为顺序(所以是面向对象模型工具中的行为模型工具之一)。
    • 顺序图将交互关系表示为一个二维图。即在图形上,顺序图是一张表,其中显示的对象沿横轴排列,从左到右分布在图的顶部;而消息则沿纵轴按时间顺序排序
  • 下面给出一个顺序图的示例:
    • notion image
      关于顺序图的知识点后续会详细讲解
  • 什么时候会用到顺序图:当一个用例涉及到多个类的时候
  • 顺序图的组成
    • 对象(包括参与者和用例中的类对象)
    • 生命线
    • 消息
    • 激活期
  • 如下图:
    • notion image
下面逐个详细介绍
  • 对象(包括参与者和类中的对象):
    • 参与者和对象按照从左到右的顺序排列
    • 一般最多两个参与者,他们分列两端启动这个用例的参与者往往排在最左边接收消息的参与者则排在最右端
    • 对象从左到右按照重要性排列或按照消息先后顺序排列。(通常是按照消息的先后顺序排列的,这样就使得顺序图对象的从左到右的排列顺序为:启动的参与者、边界类、控制类、实体类、接收消息的参与者)
    • 对象命名的三种方式(实际上都是直接拿类名当做对象的名字的):如下图
      • notion image
  • 生命线
    • 每个对象都有自己的生命线,用来表示在该用例中一个对象在一段时间内的存在(也就是表示对象的生命周期)
    • 生命线使用垂直的虚线表示
    • 如果对象生命期结束,则用注销符号表示(在生命线上打个×)
    • 对象默认的位置在图顶部,表示对象在交互之前已经存在
    • 如果是在交互过程中由另外的对象所创建,则位于图的中间某处
  • 如下图:
    • notion image
  • 激活期:激活表示该对象被占用以完成某个任务,去激活指的则是对象处于空闲状态、在等待消息。
    • 当一条消息被传递给对象的时候,它会触发该对象的某个行为,这时就说该对象被激活了。
    • 在UML中,激活用一个在生命线上的细长矩形框表示
    • 矩形本身被称为对象的激活期或控制期,对象就是在激活期顶端被激活的。
    • 激活期说明对象正在执行某个动作。当动作完成后,伴随着一个消息箭头离开对象的生命线,此时对象的一个激活期也宣告结束
  • 总结:消息的传入大概率会使得对象被激活,消息的传出大概率会使得对象被去激活。消息都需要传入或传出在激活期
  • 消息
    • 面向对象方法中,消息是对象间交互信息的主要方式(交互耦合就是通过信息传递进行耦合的)
    • 结构化程序设计中(也就是面向过程方法),模块间传递信息的方式主要是过程(或函数)调用
    • 在顺序图中,消息是由从一个对象的生命线指向另一个对象的生命线的直线
    • 顺序图中尽力保持消息的顺序是从左到右排列的在各对象之间消息的次序由它们在垂直轴上的相对位置决定(因为垂直轴是表示时间的)
    • 消息的命名:消息命名多用动宾结构(跟数据流图中的数据加工,用例图中的用例命名规范都是一样的)
    • 消息的分类
    • 消息类型的总览图如下:
      • notion image
      • 简单消息(Simple Message):不多做介绍,就是单纯传递一个消息,并且
      • 同步消息(Synchronous Message):同步消息最常见的情况是调用,即消息发送者对象在它的一个操作执行时调用接收者对象的一个操作,此时消息名称通常就是被调用的操作名称。当消息被处理完后,可以回送一个简单消息或者是隐含的返回
      • 如下图:
        • notion image
          可以发现同步消息的符号是实线带实心三角形的箭头,箭头的方向就是消息传送的方向
      • 异步消息(Asynchronous Message):异步消息表示发送消息的对象不用等待回应的返回消息,即可开始另一个活动。
      • 异步消息在某种程度上规定了发送方和接收方的责任,即发送方只负责将消息发送到接收方,至于接收方如何响应,发送方则不需要知道。接收方在接收到消息后它既可以对消息进行处理,也可以什么都不做。
        • 如下图:
          notion image
          可以发现异步消息的符号是一个实现半角箭头,箭头的方向就是消息传送的方向
      • 反身消息(Message to Self):一个对象也可以将一个消息发送给它自己,这就是反身消息
        • 如果一条消息只能作为反身消息,那么说明该操作只能由对象自身的行为触发。
        • 这表明该操作可以被设置为private属性,只有属于同一个类的对象才能够调用它。
        • 在这种情况下,应该对顺序图进行彻底的检查,以确定该操作不需要被其他对象直接调用
      • 返回消息(Return Message):本质上就是一个方向反过来的简单消息
        • 返回消息是顺序图的一个可选择部分(返回消息一般都是跟同步消息配对使用,但是同步消息的返回消息是可以隐藏的),它表示控制流从过程调用的返回
        • 返回消息一般可以缺省,隐含表示每一个调用都有一个配对的调用返回。(就是同步消息的返回是可以隐含返回的)
        • 是否使用返回消息依赖于建模的具体/抽象程度。如果需要较好的具体化,返回消息是有用的(比如说用户需要看到一些信息的时候,或者说主调对象需要根据返回消息进行一些操作的时候,这些情况就需要用到返回消息了);否则,主动消息就足够了
      • 注意:返回消息的符号是虚线箭头
  • 绘制顺序图的注意事项
    • 对象的创建与撤销
      • 对象默认在顺序图的顶部,表示对象在交互之前已经存在
      • 如果对象在交互过程中被创建,就需要将对象放在顺序图的中间
      • 如果对对象进行了撤销(删除)操作,那么就需要在撤销操作结束之后在对象的生命线上打上撤销标志(就是在生命线上打上一个×,表示对象的生命周期结束)
    • 对象创建与撤销的方式
      • 对象创建方式,如下图(还是选择右边的画法,将消息打在激活期上,这样就能统一起来了):
        • notion image
      • 对象的撤销方式,如下图(发送destroy表示要撤销对象,然后在对象的撤销操作执行结束之后需要在声明线上打上撤销符号,表示对象的生命周期结束):
        • notion image
    • 关于顺序图消息的获取
      • 首先需要抓住题干中的动词,基本上就是每一个动词对应一个消息
      • 然后再根据实际的需要补充一些消息(比如:显示提示信息、数据库的相关操作等)
    • 何时使用顺序图:当需要强调按时间展开信息的传送时,就需要使用顺序图(注意,一个单独的顺序图只能展现一个控制流,所以通常情况下由于控制流的复杂性,需要其他的图来辅助建模,如使用其他的图来表示例外情况等)
  • 顺序图和用例之间的关系
  • 如下图:
    • notion image
      根据自己的理解,顺序图是描述用例的动态过程,与类图一样,也是细化用例(用例设计)生成的一个模型。从上图中可以发现,一个用例细化之后就对应了一个类图(静态过程)和一个顺序图(动态过程),并且类图和顺序图之间是有一定联系的
  • 顺序图建模的参考策略(就是说明绘制一个顺序图需要遵守的原则,bydqyj讲这么高级干嘛)
    • 设置交互的语境(就是选定一个用例)
    • 对象从左到右
    • 设置每个对象的生命线
    • 消息自上而下(要按照时间顺序)
    • 设置对象的激活期
    • 时间和空间约束(消息中可以附上时间空间的约束)
    • 前置和后置条件(消息中可以附上前置后置的条件)
  • 绘制顺序图的步骤
    • 确定交互的范围
    • 识别参与交互的对象和活动者
    • 设置对象生命线的开始和结束
    • 设置消息
    • 细化消息

4.2.5 其他UML建模视图

  • 协作图
    • 所谓协作,是指在一定的语境中一组对象以及用以实现某些行为的这些对象间的相互作用。它描述了这样一组对象为实现某种目的而组成相互合作的“对象社会”。
    • 协作图就是表现对象协作关系的图(就是对象之间是如何交互的,这个在顺序图中也有体现),它表示了协作中作为各种类元角色的对象所处的位置,以及关联角色。
    • 类元角色和关联角色描述了对象的配置和当一个协作的实例执行时可能出现的连接
    • 通过描绘对象间消息的传递情况来反映具体的使用语境的逻辑表达。
    • 显示对象及其交互关系的控件组织结构。协作图显示了在交互过程中各个对象之间的交互关系以及对象彼此之间的连接。与序列图(顺序图)不同,协作图显示的是对象之间的关系,并不是侧重交互的顺序,它没有将时间作为一个单独的维度,而是使用序列号来确定消息及并发线程的顺序。(所以实际上协作图就是顺序图的一维版本,就是把时间那一维压掉了。但是注意协作图上仍然有时间顺序,是通过消息标号来表示的)
    • 协作图的另外一个作用是表现一个类操作的实现。协作图可以说明类操作中使用到的参数、局部变量以及返回值等。当使用协作图表现一个系统行为时,消息编号对应了程序中的嵌套调用结构和信号传递过程
  • 总结:协作图实际上就是描述对象协作关系的图。绘制的时候就是将顺序图中的时间轴抹去,然后将消息集中在对象符号上即可
    • 协作图的符号
      • 对象:参与者或者类对象(跟顺序图中的参与者和对象是一样的,可以直接把顺序图的拿过来用)
      • 协作关系:连接对象的横线,其上可标注带箭头的消息(这个是相较于顺序图多出来的部分,这个代表的不是消息,而是说明被链接的两个对象之间是有交互的,并且协作关系是不带箭头的,因为协作的双方是平等的。消息是放在协作关系线上的)
      • 消息:协作动作或者消息,有方向(也就是顺序图中消息传递的方向)。消息应该按照协作顺序(也就是时间顺序,就是顺序图中的顺序)编号
      协作图的示例如下:
      notion image
  • 活动图(有点像流程图)
    • 什么是活动图:活动图是一种用于描述系统行为的模型视图,它可用来描述动作和动作导致对象状态改变的结果,而不用考虑引发状态改变的事件
    • 活动图的作用
      • 描述一个操作(也就是一个用例)执行过程中所完成的动作,即活动图对用例描述尤其有用
      • 显示如何执行一组相关的动作,以及这些动作如何影响它们周围的对象,即活动图对理解业务处理过程十分有用
      • 描述复杂过程的算法(描述算法感觉不如流程图)
    • UML活动图的元素/符号
      • notion image
        里面常用的有:状态(动作状态)、判定、泳道、初始状态、最终状态、转换(分叉和连接)。注意:其中转换是用来表示并发的,而判定是用来表示分支的
    • 动作状态与活动状态的区别
      • 动作状态(Action State)是原子性的动作或操作的执行状态,它不能被外部事件的转换中断。
      • 动作状态的原子性决定了动作状态要么不执行,要么就完全执行,不能中断。
      • 活动状态用于描述一个具有入口和退出动作的活动。入口动作在进入该状态时执行,退出动作则再离开状态时执行。
      • UML活动状态还可以有内部状态转换(也就是活动状态还是可以细分的),用于描述活动状态的内部过程。
      • 动作状态是一种特殊的活动状态。可以把动作状态理解为一种原子的活动状态,即它只有一个入口动作,并且它活动时不会被转换所中断。
      • 活动状态和动作状态的表示图标相同,都是平滑的圆角矩形。两者不同的是活动状态可以在图标中给出入口动作和出口动作等信息
      • 总结:动作状态是原子性的,不可再分的,只能有一个入口动作;活动状态不是原子性的,是可以再分的,可以有入口动作和出口动作,内部也可以有状态转换。动作状态是一种原子性的活动状态
    • 组合活动
      • 组合活动是一种内嵌活动图的状态。一个组合活动在表面上看是一个状态,但其本质却是一组子活动的概括。一个组合活动可以分解为多个活动或者动作的组合。每个组合活动都有自己的名字和相应的子活动图。一旦进入组合活动,嵌套在其中的子活动图就开始执行,直到到达子活动图的最后一个状态,组合活动结束。
      • 如果一些活动状态比较复杂,就会用到组合活动。
      • 比如,我们去购物,当选购完商品后就需要付款。虽然付款只是一个活动状态,但是付款却可以包括不同的情况。对于会员来说,一般是打折后付款,而一般的顾客就要全额付款了。这样,在付款这个活动状态中,就又内嵌了两个活动,所以付款活动状态就是一个组合活动
      • 总结:组合活动就有点像数据流图中的非底层数据加工,每一个组合活动(对应数据流图中的数据加工)都对应了一张子活动图(在数据流图中就对应了一个子数据流图)。组合活动是由多个活动(非原子性)或者动作(原子性)组成的,所以组合活动还能再对应一张子活动图,进入组合活动的时候就进入了子活动图;退出子活动图就代表着退出组合活动
    • 并发:并发(concurrency)指的是在同一时间间隔内,有两个或者两个以上的活动执行(就是同时有多个活动进行)
      • 并发的表示:分叉用来表示将一个控制流分成两个或者多个并发运行的分支,汇合(或连接)用来表示并行分支在此得到同步(所以需要用到UML活动中的转换符号)。如下图:
        • notion image
    • 条件分支:条件分支是转换的一部分(虽然是转换,但是使用的符号是不一样的。条件分支有专门的判定符号)。
      • 它将转换路径分成多个部分,每一部分都有单独的进入条件和不同的结果。
      • 当控制流遇到分支时,会根据进入条件(布尔值)的真假来判定动作的流向。
      • 分支的每个路径的进入条件应该是互斥的,这样可以保证只有一条路径的转换被激发。
      • 在活动图中,离开一个活动状态的分支通常是完成转换,它们是在状态内活动完成时隐含触发
    • 合并(与条件分支组合使用。但是实际情况下用的应该比较少):合并指的是两个或者多个控制路径在此汇合的情况
      • 合并是一种便利的表示法,省略它不会丢失信息(所以分支可以隐含表示分支结束,并不需要合并来显示表示)
      • 合并和分支常常成对的使用合并表示从对应分支开始的条件行为的结束(也就是所有分支汇合的地方)
    • 下图是一个使用条件分支和合并的示例:
      • notion image
        注意条件分支和合并是使用UML符号中的判定符号;而并发是使用UML中的转换符号(包括分叉和连接)
    • 泳道(重点捏):为了对活动的职责进行组织(也就是明确哪些活动是由哪些对象执行的)而在活动图中将活动状态分为不同的组,称为泳道。
      • 每个泳道代表特定含义的状态职责的部分。(讲的什么b话)
      • 每个活动只能明确的属于一个泳道泳道明确的表示了哪些活动是由哪些对象进行的
      • 每个泳道都有一个与其他泳道不同的名称。(通常是对象名)
      • 每个泳道可能由一个或者多个类实施(所以泳道跟类对象还是有一定区别的,但是通常情况下就是把类名当成泳道名,也就是认为类和泳道没有区别),类所执行的动作或拥有的状态按照发生的事件顺序自上而下的排列在泳道内(所以带泳道的活动图也是有时间纵轴的)
    • 下面是一个泳道示例:
      • notion image
        注意:这里的系统实际上就代表了多个类对象
    • 控制流和对象流(画活动图的时候不需要考虑)
      • 活动图中交互的简单元素是活动和对象,控制流(control flow)就是对活动和对象之间的关系的描述。(即控制流是描述关系的)
      • 控制流表示动作与其参与者和后继动作之间以及动作和其输入和输出对象之间的关系。
      • 对象流就是一种特殊的控制流。(对象流就是在控制流的基础上在活动图中加上了对象)
      • 用活动图描述某个对象时,可以把涉及到的对象放置在活动图中,并用一个依赖将其连接到进行创建、修改和撤销的动作状态或者活动状态上,对象的这种使用方法就构成了对象流
    • 对象流的示例:
      • notion image
        这里就是把订单、结账信息、付款信息等对象都放到活动图中了,所以是一个对象流(左侧一列活动是在用户的泳道中的;右侧一列活动是在系统的泳道中的。由此也可以发现对象实际上就是泳道之间沟通的桥梁)
  • 流程图(不考)
  • 就在这里摆一张图:
    • notion image
  • 状态图(也不考吧,这里就了解一下):又称状态转换图,(State Transform Diagram,STD)是描述系统行为的模型。常用于面向过程设计。(所以前面介绍的模型工具中应该是错误的,面向对象的分析模型应该是有协作图而不是状态图,状态图应该是算面向过程设计的分析模型工具的)
    • 状态模型是一种描述系统对内部或者外部事件响应的行为模型。(反应的是系统行为,所以状态图应该算是面向过程的分析模型工具中的行为模型)
    • 它描述系统状态和事件,以及事件引发系统在状态间的转换。(描述系统,所以是面向过程分析模型中行为模型的描述工具)
  • 下面是一个状态图的示例:
    • notion image
    • 状态图的符号(可以参照上图来理解,byd有点像自动机,也就是状态转换图):
      • 其中的圆角方框或者椭圆形表示状态
      • 箭头表示状态转换,即从源状态指向目的状态
      • 箭头上的文字触发状态转换的事件(对应自动机上的状态转移条件),如果有多个事件触发同样的转换(同样的转换就是指源转态和目的状态是一样的),可以并列写在一起
  • 组件图:系统的子系统结构(所以组件图应该是算面向对象分析模型的描述工具)可用UML组件图表示。组件图在宏观层面上显示了构成系统某一个特定方面的实现结构(应该也不考)
    • 组件图通常包含三种元素,即组件、接口和关系。(关系就是指子系统之间的关系,如请求服务关系,平等关系,依赖关系。组件、接口、关系对应上面提到的设计子系统步骤中的划分子系统、设计子系统接口、设计子系统关系)
    • 组件图还可以使用包来进行组织,使用注解与约束来进行解释和限定。
    • 组件
      • 组件是系统设计的一个模块化部分,它隐藏了内部的实现,对外提供了一组接口。(做到了面向对象设计中的模块化,也做了信息隐藏,能实现强内聚弱耦合)
      • 并且由于它对接口的实现过程与外部元素独立,所以组件具有可替换性
    • 组件的图形表示如下:
      • notion image
        上面多出来的棒棒就是对外暴露的接口,上面写的字就是组件名
    • 接口:接口是组件对外提供的服务。对于一个组件而言,它可有两类接口,提供接口与需求接口(提供接口就生产出来数据给其他的子系统使用;需求接口就是需要接收其他子系统提供的数据才能执行相应的操作)
    • 端口:端口是一个被封装的组件的对外窗口(接口应该是有端口组成的)
      • 端口是有标识的
      • 别的组件可以通过一个特定端口(就是接口中的某一个实现特定功能的端口)与另一个组件通信
    • 组件之间的嵌套表示(实际上就是一个组件内部的结构也通过组件来表示)
Paper2PX4
Loading...
Noah
Noah
永远年轻,永远热泪盈眶
公告
❗❗复习笔记问题❗❗
由于兼容性问题
导入md文件可能导致了一些格式错误
🌹如发现格式错误,请联系我~🌹
🌹如博客内容有误也欢迎指出~🌹