3 统一建模语言(UML)(上)
截止现在,UML的最新版本为2017年12月发布的2.5.1,我们可以通过其官网(https://www.omg.org/spec/UML/)查看有关该版本的规范文本(简称UML规范),在规范文本中提到:
The objective of UML is to provide system architects, software engineers, and software developers with tools for analysis, design, and implementation of software-based systems as well as for modeling business and similar processes. The initial versions of UML (UML 1) originated with three leading object-oriented methods (Booch, OMT, and OOSE), and incorporated a number of best practices from modeling language design, object-oriented programming, and architectural description languages. Relative to UML 1, this revision of UML has been enhanced with significantly more precise definitions of its abstract syntax rules and semantics, a more modular language structure, and a greatly improved capability for modeling large-scale systems. One of the primary goals of UML is to advance the state of the industry by enabling object visual modeling tool interoperability. However, to enable meaningful exchange of model information between tools, agreement on semantics and syntax is required. UML meets the following requirements:
|
UML的目标是为系统架构师、软件工程师和软件开发人员提供一种工具,用于分析、设计和实现基于软件的系统以及用于对业务及类似的流程进行建模。 UML的初始版本(UML 1)起源于三种领先的面向对象方法(Booch、OMT和OOSE),并融合了建模语言设计、面向对象编程和架构描述语言的许多最佳实践。相较于UML 1,这个版本(UML2)主要从以下几个方面得到了增强:对抽象语法规则和语义的定义更加精确;语言结构更加模块化;对大规模系统的建模能力也大大提高。
|
如上所述,在官网提供的规范文本中,对UML的语法、语义及结构给出了严格的定义。接下来,我们将以UML2为基础,对UML的组成结构、概念模型以及主要的建模元素给出说明。
3.1 UML组成结构
与UML1.x不同,为了更清楚地表达UML的结构,从UML2开始,整个UML规范被划分成基础结构(Infrastructure)和上层结构(Superstructure)两个相对独立的部分。基础结构是UML的元模型,它定义了构造 UML模型的各种基本元素;而上层结构则定义了面向建模用户的各种UML模型的抽象语法(Abstract Syntax)、语义(Semantics)和表示(Notation),同时给出示例(Examples)。然而,从UML2.5开始,为了消除冗余并简化UML规范,基础结构部分不再作为UML规范的一部分,而是在UML规范相应的部分对UML元类给出完整定义。
3.1.1 语法结构
UML的抽象语法通过UML元模型来定义,而这个元模型本身也是用UML来定义的。在UML规范中,主要采用UML类图来描述各元素的抽象语法,采用约束机制和自然语言(文本)来描述模型语义。这样表述起来似乎有些难以理解,我们不妨举个例子来说明。
图3-1、2分别给出了规范中关于UML最基础的元模型(元素、关系以及注释)抽象语法和语义的类图表示和自然语言描述。
图3-1 UML规范中针对元模型抽象语法的类图表示
图3-2 UML规范中针对元模型语义的自然语言表述
3.1.2 语义结构
通常,UML模型可划分为结构语义和行为语义两类语义域。
- 结构语义(Structural Semantics),也称静态语义,它定义了在建模域中关于个体的UML结构化模型元素的含义,这个含义可能只在某个特定的时间点是正确的。
- 行为语义(Behavioral Semantics),也称动态语义,它定义了在建模域中关于个体如何随着时间变化而做出不同行为的UML行为模型元素。
图3-3来自UML2.5规范,列出了UML语义域分层的详细分解结构。UML2.5规范文本的主体内容就是按照该结构组织的。
图3-3 UML规范中给出的UML语义域结构
- UML结构模型建立在一个通用的基础结构(Common Structure)之上,包括类型(type)、命名空间(namespace)、关系(relationship)和依赖(dependency)等概念。
- Common Structure 具体的建模元素包括不同的分类器(Classifiers)以及简单的值类型(Values)和包(Packages)等。其中分类器包括数据类型(data types)、类(classes)、信号(signals)、接口(interfaces)和构件(components)等。
- 构建在基础结构之上的 UML 基本行为语义为行为的执行提供了一个基本框架,通用行为(Common Behavior)语义还定义了结构化对象之间通过相关行为产生的通信。
- 动作(Actions)是UML中的基本行为单元,用于定义细粒度的行为;在此基础上形成高层次的行为机制,包括状态机(State Machines)、活动(Activities)和交互(Interactions)等。
- 此外,还提供了一些既结构化又包含行为的辅助建模结构,包括用例(UseCases)、部署(Deployments)和信息流(Information Flows)。
3.2 概念模型
UML2规范详细阐述了各类模型元素的语法结构,可谓面面俱到。然而对于普通建模用户来说,往往只关心与业务相关的那些要素,即对目标系统建模过程中需要用到哪些建模元素、涉及哪些基本概念等,这些核心概念形成了UML的概念模型。对于开发人员,可先通过概念模型掌握UML建模的基本思想,从而能够读懂并建立一些基本模型,当有了丰富的UML建模经验后,就能够在这些概念模型之上理解UML2的结构,从而使用更深层次的语言特征开展构造工作。
UML概念模型主要由三部分组成:基本的构造块、运用于这些构造块的通用机制和组织UML视图的架构。每个部分又包含不同的子部分,具体的组成结构如图3-4所示。
图3-4 UML2概念模型
3.2.1 构造块(Building Blocks)
构造块是UML的基本建模元素,主要包括事物(Thing)、关系(Relationship)和图(Diagram)三个方面。
(1)事物(Thing)。在UML中,事物代表了基本的面向对象构造块。事物是对模型中核心要素的抽象,UML2中事物主要包括以下4种类型:
- 结构类事物(Structural Things):是模型中的静态部分,用以呈现概念或实体的表现元素,是软件建模中最常见的元素。主要包括类(Class)、接口(Interface)、协作(Collaboration)、用例(Use Case)、主动类(Active Class)、组件(Component)和节点(Node)等。
- 行为类事物(Behavioral Things):是模型中的动态部分,用于描述系统的行为。主要包括交互(Interaction)和状态机(StateMachine)两种。
- 分组类事物(Group Things):是UML模型的组织部分,用于将模型中的元素组织起来。最主要的是包(Package),包可以拥有其他元素,这些元素可以是类、接口、构件、节点、协作、用例和图,甚至可以是其他包。
- 注释类事物(Annotational Things):是UML模型的解释部分,用于描述、说明和标注模型的任何元素。最主要的注释事物是注解(Note),注解是一个依附于一个元素或一组元素之上,对它进行约束或解释的简单符号。
(2)关系(Relationship)。关系把事物紧密联系在一起。UML2中的主要关系包括依赖(Dependency)、泛化(Generalization,也称继承)、实现(Realization)和关联(Association)。其中,关联又可分为普通关联(Common Association)、聚合(Aggregation,也称聚集)和组合(Composition)。这些关系描述了事物之间的不同连接方式和交互方式。
(3)图(Diagram)。图是由很多相互关联的事物组成的,是对模型的可视化表示。UML2中的图主要包括类图(Class Diagram)、对象图(Object Diagram)、包图(Package Diagram)、组合结构图(Composite Structure Diagram)、构件图(Component Diagram)、部署图(Deployment Diagram)、顺序图(Sequence Diagram)、通信图(Comunication Diagram)、时间图(Timing Diagram)、交互概览图(Interaction Overview Diagram)、活动图(Activity Diagram)、状态机图(State Machine Diagram)和用例图(Use Case Diagram)等。这些图从不同的角度描述了系统的静态结构和动态行为。
3.2.2 通用机制
UML2的通用机制通过规格说明、修饰、通用划分和扩展机制等多方面的机制,帮助用户更好地理解、定制和扩展建模语言,提高了建模的表现力和适用性。
(1)规格说明:UML2的通用机制通过规格说明定义了模型元素的行为和语义,使得模型元素具有一致的理解和解释。这些规格说明包括了模型元素的属性、操作、关系等信息,帮助用户更好地理解和使用模型。
(2)修饰:修饰是UML2通用机制的重要组成部分,通过修饰可以对模型元素进行扩展和定制。修饰包括了标签、注释、约束等机制,可以对模型元素的行为和属性进行描述和限制,增强了模型的表现力和精确度。
(3)通用划分:UML2通过通用划分将模型元素进行分类和组织,使得模型具有更好的结构和层次。通用划分包括了元模型、包、分类等概念,帮助用户更好地管理和组织模型元素,提高了模型的可维护性和可扩展性。
(4)扩展机制:UML2提供了丰富的扩展机制,使用户可以根据实际需求对UML进行扩展和定制。扩展机制包括了面向对象、面向模型、面向元模型等不同的扩展方式,帮助用户实现自定义的建模需求,满足不同领域的建模需求。
3.2.3 架构(视图)
为了便于理解各种模型图之间的关系,UML2提供了5个视图(View)来组织UML模型,以实现不同的建模目标。
(1)用例视图(Use-Case View):用例视图主要描述系统的功能需求和用户需求,包括用例图、活动图等。通过用例视图,开发人员可以更好地了解系统需要实现的功能以及用户和系统之间的交互情况,有助于设计系统的功能模块和用户界面。
(2)逻辑视图(Logical View):逻辑视图主要表现系统的静态结构,包括类图、对象图、组件图等。在UML2中,逻辑视图主要关注系统中各个对象间的关系以及它们之间的交互方式,帮助开发人员理清系统的结构和设计。
(3)实现视图(Implementation View):实现视图主要关注系统中软件组件的实现和部署方式,包括包图、组件图、部署图等。通过实现视图,开发人员可以更好地了解系统中各个组件的具体实现方式以及它们之间的依赖关系。
(4)过程视图(Process View):过程视图主要描述系统中各个组件间的动态交互过程,包括活动图、状态图、时序图等。通过过程视图,开发人员可以清晰地了解系统在不同情况下的运行过程,帮助他们设计系统的交互逻辑和流程。
(5)部署视图(Deployment View):部署视图主要描述系统的物理部署情况,包括部署图等。通过部署视图,开发人员可以了解系统各个软件组件在硬件设备上的布置方式以及它们之间的连接关系,有助于设计系统的部署架构和优化系统性能。
3.3 主要的建模元素
为了便于理解,下面以前面讲到的“教学档案管理系统”(以下简称档案系统)为例,对UML主要的建模元素的用法给出说明。
3.3.1 注释
在UML图中,可以使用注释来提供额外的信息或解释。注释通常以矩形方框的形式显示在相关组件或元素旁边,以帮助读者更好地理解图中的内容和意图。注释可以包含文字描述、说明、约束条件或其他有用的信息。
图3-5是UML规范中给出的注释元素示意图,注释显示为右上角弯曲的矩形(也称为“注释符号”),通过虚线连接到需要注释的元素或组件。如果通过上下文可以清楚的了解到注释要表达的意思,则可以将其去掉。
图3-5 注释示例
3.3.2 用例图
用例图是从用户的视角描述系统功能的UML模型图,它由参与者(Actor,也称角色,对应数据流图中的外部实体)、用例(User Case)以及它们的关系连线组成。以档案系统为例,系主任和普通教师是其中的两个参与者,他们通过系统可以操作的功能就是用例,图3-6给出了对应的用例图。
图3-6 用例图
表3-1给出了用例图中常用关系连线的类型、图示及其含义。
表3-1 用例图中的关系连线
类型 | 图示 | 含义 |
泛化 | ![]() | 表示当前角色(或用例)是箭头指向角色(或用例)的一个特例,例如系主任就是一类特殊的教师。注意:(1)箭头指向被泛化的角色或用例。(2)只能出现在用例与用例之间、参与者与参与者之间。 |
关联 |
![]() | 表示参与者拥有或可以操作哪些用例,如果采用箭头方式,箭头指向用例。 |
包含 | ![]() | 表示主用例包含子用例,即子用例作为一个独立的功能,会被1个或多个主用例调用。注意:(1)虚线箭头指向子用例;(2)include两面分别为两个单独的尖括号而非书名号。 |
扩展 | ![]() | 表示主用例执行过程中可能会调用子用例。注意:(1)虚线箭头指向主用例。(2)扩展关系与包含关系的区别在于,前者子用例不一定会执行,后者子用例一定会被调用。 |
3.3.3 用例规约
通过用例图,可以直观的表达系统的参与者与系统之间有哪些交互场景,但仅仅通过用例图还不足以了解用户需求的细节。因此,我们需要以文档的形式将用例图中每个用例的细节加以定义并在开发方和用户之间达成一致,这里的文档主要是指用例规约。
用例规约(Use Case Specification)是对用例的详细描述文档,用于明确系统与外部参与者(Actor)之间的交互流程和规则。它是需求分析阶段的核心产出之一,确保开发团队和利益相关者对系统行为达成一致理解。表3-2给出了用例规约的参考模板及内容填写的简要说明,读者可以根据项目需要作适当的裁剪。
表3-2 用例规约参考模板
用例标识 | 用例的唯一标识,用于跟踪和管理(如UC-001) |
用例名称 | 用例的名称,要尽量简短且有明确的含义(如“用户登录”),要与用例图保持一致 |
简要描述 | 对用例本身及为什么要使用该用例作简要的描述 |
参与者 | 使用到该用例的参与者,名称要与用例图保持一致 注意:如果参与者之间有泛化关系,识别参与者时要综合考虑泛化参与者(如图3-6中的普通教师)和特化参与者(如图3-6中的系主任)。以图3-6为例,在编写“授课管理”用例的用例规约时,参与者就只需填“系主任”,但编写“归档”用例的用例规约时,就需要把系主任和普通教师都填写在这里。 |
涉众 | 与该用例相关的其他用户或部门,在此要说明该用例的执行会对这些用户或部门产生哪些影响 |
相关用例 | 与该用例存在关联关系的用例,对照用例图列出与该用例存在关联关系的用例并对关系做出简要描述 |
前置条件 | 执行用例前必须满足的条件(如对于图3-6中的“登录”用例,其前置条件可描述为:“用户已注册且账户未锁定”) |
后置条件 | 用例执行后系统的状态(如对于图3-6中的“登录”用例,其后置条件可描述为:“用户会话已建立”) |
基本事件流 描述参与者与系统的正常交互步骤(主成功场景),如对于图3-6中的“登录”用例,其基本事件流可描述为: 1.参与者输入用户名和密码; 2.系统验证凭证并返回成功状态; 3.系统跳转到用户主页。 | |
备选事件流 描述参与者与系统交互过程中的异常步骤(失败场景),如对于图3-6中的“登录”用例,其中的一个可能的备选事件流是“输入错误的密码”: 1.系统显示错误消息; 2.参与者重新输入或选择重置密码。 | |
补充约束 用于明确用例执行时必须满足的额外条件或限制,通常包括数据需求、业务规则、技术条件、非功能需求等。 1.数据需求:与用例相关的数据项。例如,图3-6中的“归档”用例,需要对归档类型、归档日期、归档内容等数据项做出说明。 2.业务规则:与用例相关的领域特定逻辑或策略。例如,图3-6中的“归档”用例,可能包含“归档的流程应满足《学院教学文档归档制度》的要求”这样的业务规则。 3.技术条件:系统实现用例时必须遵循的限制。例如,图3-6中的“归档”用例,可能限制“上传的单个文件大小不超过10MB”。 4.非功能需求:涉及性能、安全、可用性等约束条件。例如,“归档”用例可能需要考虑“归档文件需强制加密存储,访问控制需遵循最小权限原则。”这样的非功能约束。 | |
待解决问题 说明针对该用例还有哪些需求还没有明确 | |
相关图 与该用例业务流程、数据需求等相关的其他图,如活动图、类图等,也可以是非UML图(如流程图)。 |
3.3.4 类图
类图用于描述系统中各个对象的类型及其间存在的各种关系。一个系统有多幅类图,一个类也可以出现在几幅类图中。
3.3.4.1 分析类图
面向对象的系统建模过程中,系统的功能通过类的交互实现。从用例文档中识别潜在类,并将系统行为分配到这些类中,是面向对象分析的核心任务。分析类图作为主要成果,体现系统为满足需求应提供的对象结构,不涉及具体实现细节。
按照软件系统分析架构中的B-C-E三层备选架构,分析类图中的类分为三个层次:边界类、控制类和实体类。绘制分析类图的过程,实质上是通过分析用例文档,明确定义这三类分析类的职责与关系。
1、边界类
边界类位于系统顶层,负责归纳和抽象系统与外部环境的交互对象,明确系统与外部参与者(用户、其他系统或设备)的交互边界。其核心作用是封装交互细节,隔离外部变化对系统内部的影响,同时为后续设计阶段提供明确的交互服务依据。按照外部参与者的类型,可将边界类分为如下2类:
- 用户界面类:用于抽象用户与系统的交互操作,例如信息录入、查询提交等。分析阶段仅关注用户能通过界面执行哪些操作,而非界面具体形式(如布局、窗口数量)。
- 系统/设备接口类:用于定义系统与其他系统或设备(如打印机、第三方服务)的交互协议。分析阶段聚焦交互的规则与数据格式(如打印输出格式),而非协议的具体实现技术。
在分析阶段,针对参与者与用例之间的每一个关联关系都可以定义一个边界类。例如,用户登录用例对应一个用户界面类,打印机调用用例对应一个设备接口类。需注意:
- 分析阶段不涉及界面或接口的物理设计细节(如页面数量、协议实现),仅定义交互逻辑。
- 边界类的明确划分有助于降低系统复杂度,并为后续设计阶段的服务定义提供基础。
图3-7给出了边界类常见的3种表示形式,我们主要采用最后一种形式来表示。图3-8是与图3-6中普通教师归档用例对应的边界类。
图3-7 边界类的3种表示形式
图3-8 边界类分析示例
对于边界类的命名,一般采用“xx界面类”来表示用户界面,采用“xx接口类”来表示系统/设备接口类。
2、控制类
控制类位于三层架构的中间层,负责协调上层边界类与下层实体类之间的交互行为,充当用例行为的核心协调器。其设计优势体现在:将边界对象与实体对象分离,降低系统对边界对象变更的敏感性,提升系统适应性。同时,它将用例特有逻辑与实体对象隔离,增强实体对象的复用性。
其核心行为特征包括:
- 环境独立性:逻辑实现不依赖外部环境变化。
- 控制逻辑集中化:定义用例的事务管理与核心控制流程。
- 变更隔离性:实体类内部结构或行为变更时,控制类仅需微调协调逻辑,而非大规模修改。
- 动态执行:根据事件流的不同状态灵活调整运行逻辑,避免僵化模式。
在分析阶段,通常为每个用例分配一个专属控制类,确保行为隔离与职责清晰。
如图3-9所示,控制类也有3种表示形式,我们主要采用第3种。图3-10是与图3-6中普通教师归档用例对应的边界类。
图3-9 控制类的3种表示形式
图3-10 控制类分析示例
3、实体类
实体类是对业务领域中核心实体对象的抽象与归纳,用于描述系统需维护的数据结构及相关操作行为。它从逻辑数据的视角呈现系统功能,帮助理解系统应提供的核心服务。
识别实体类需分析用例事件流中的名词或名词短语,提取关键业务信息。与边界类和控制类不同,实体类具有跨用例甚至跨系统的复用性。其来源主要包括:
- 架构分析中的关键抽象
- 用例事件流中的业务名词
- 业务模型与领域词汇表
实体类的特征包括:
- 业务信息载体:以名词形式表征业务核心概念。
- 数据与行为封装:既包含持久化数据,也定义相关操作逻辑。
- 高复用性:通常不局限于单一用例或系统边界。
图3-11给出了实体类的3种表示形式,我们主要采用第3种。图3-12给出了基于图3-6中的普通教师归档用例来识别实体类的示例。
图3-11 实体类的3种表示形式
图3-12 实体类分析示例
扩展阅读:B-C-E三层架构 B-C-E(Boundary-Control-Entity)三层架构是软件设计中一种常见的分层模式,用于分离用户界面、业务逻辑和数据持久化层。它是MVC(Model-View-Controller)架构的变体,更强调职责的清晰划分。 1.Boundary层(边界层) Boundary层负责处理用户与系统的交互,包括输入输出、界面展示和外部系统对接。典型实现包括:
// 示例:Boundary层的API端点(Spring Boot) @RestController @RequestMapping("/api/users") public class UserBoundary { @GetMapping("/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { // 调用Control层 } } 2.Control层(控制层) Control层包含核心业务逻辑,协调Boundary与Entity层的交互。职责包括:
// 示例:Control层的业务逻辑 @Service public class UserControl { @Transactional public User createUser(User user) { // 验证逻辑 // 调用Entity层保存数据 } } 3.Entity层(实体层) Entity层代表领域模型和数据持久化,与数据库或外部存储直接交互。常见实现方式:
// 示例:Entity层的领域模型与JPA @Entity public class User { @Id @GeneratedValue private Long id; private String name; // Getters/Setters } 4.备选架构变体 1)BCE与MVC对比
2)BCE扩展模式
3)微服务架构中的BCE
5.适用场景
|
3.3.4.2 参与类类图
参与类类图(View Of Participating Classes Class Diagram,VOPC类图)用于描述系统中类之间的交互关系,尤其是类在特定场景或用例中的参与情况。它通常用于展示类在协作或交互中的角色和职责。图3-13给出了在图3-6中普通教师归档用例实现过程中各个类之间的交互关系。
图3-13 参与类类图示例
3.3.4.3 类的完整表示
通过参与类类图确定了用例实现过程中各个类之间的交互关系,进一步的,还需要从系统的角度,明确每一个类的功能和属性以及类之间的关系。
1、分析阶段类的完整表示。图3-14给出了普通教师归档用例分析过程中各个类的完整表示。
- 分析阶段只识别类的属性和方法,但一般不涉及诸如属性的类型、方法的参数等细节;
- 对于界面类和控制类,主要是识别其需要完成的功能;
- 对于实体类,主要是识别其属性,个别实体类也会包含一些特殊的操作,如实例中的归档信息类,它需要包含归档文件相关的操作。
图3-14 分析阶段类的完整表示
2、设计阶段类的完整表示。相比于分析阶段,设计阶段需要进一步明确类的实现细节,以便为面向对象的编码实现提供准确的参考。图3-15给出了与图3-14对应的设计阶段类的完整表示。
- 类的完整表示由类名、属性和方法组成;
- 属性的表示包含可见性、属性名及类型;
- 方法的标识包含可见性、方法名、参数列表及返回值类型;
- 可见性是指属性或方法对于外部的可访问特性,其表示方法包括:(1)+:公有(public),外部均可访问到;(2)-:私有(private),外部不可见;(3)#:保护(protected),只有子类可以访问;(4)~:包内可见(package),只有在同一个包内可以访问到。
- 设计阶段包含的类与分析阶段的类不是完全对应的,如图3-15所示,为了便于归档信息的记录,设计了名为FileInfo的类用于传递归档文件的相关信息。
图3-15 设计阶段类的完整表示
3.3.4.4 类之间的关系
在面向对象系统中,用例的实现需要多个对象间的协作,即通过对象间的消息传递来完成用例反映的业务功能。这种协作关系需要两个关键要素:类层面的结构关系和实例层面的调用链接,这种双重关系确保了系统既具有灵活的运行时可扩展性,又保持了严谨的设计约束。
具体而言,类之间主要存在关联(Association)、依赖(Dependence)、泛化(Generalization)、聚合(Aggregation)、组合(Composition)和实现(Implementation)。
1、关联(Association)
关联是类与类之间最常用的一种关系,它是一种结构化关系,用于表示一类对象与另一类对象之间有联系。这种联系可能是单向的,也可能是双向的。如图3-16给出了普通教师归档用例实现过程中各个类的关联关系。
图3-16类的关联关系(Association)
如图所示,类的关联通过连接两个类的直线或单向箭头来表示。
- 类的关联默认为双向的,通过不带箭头的实线表示;
- 单向关联通过单向实线箭头表示,箭头指向被关联的类;
- 线的两头通过数字或“*”标注类间关联的多重性限制:通常有“*”(表示所有,不限),“1”(表示有且仅有一个),“0...”(表示0个或者多个),“0,1”(表示0个或者一个),“n...m”(表示n到m个都可以),“m...*”(表示至少m个);
- 可通过文字表示关联关系的名称,如图中归档信息类(FMInfo)和文件信息类(FileInfo)之间就是单向“包含”关系。
2、依赖(Dependence)
依赖是一种使用关系,对于A、B两个类,如果A的变化会影响B的变化,则称B类依赖A类。如图3-17所示,我们新增了一个档案审核类FMAudit专门负责处理档案的审核处理,当该类的方法audit执行后,就会改变其FMInfo类参数所代表的档案信息的审核状态,因此,FMInfo类依赖于FMAudit类。
图3-17类的依赖关系(Dependence)
- 依赖关系用带箭头的虚线表示,由依赖的一方指向被依赖的一方;
- 依赖关系包括三种情况:(1)A类是B类中(某个方法)的局部变量;(2)A类是B类方法的一个参数;(3)B类向A类发送消息,从而影响B类发生变化;
- 大多数情况下,依赖关系体现在某个类的方法使用另一个类的对象作为参数。
3、泛化(Generalization)
泛化关系也就是继承关系,也称为“is-a-kind-of”关系,用于描述父类与子类之间的关系,父类又称作基类或超类,子类又称作派生类。如图3-18所示,系主任类(DepMaster)就是从教师类(Teacher)继承而来。
图3-18类的泛化关系(Generalization)
- 泛化关系用带空心三角形箭头的直线来表示,箭头指向父类;
- 在代码实现时,使用面向对象的继承机制来实现泛化关系,如在Java语言中使用extends关键字、在C++/C#中使用冒号“:”来实现、在Python中使用括号等。
4、聚合(Aggregation)
聚合表示类之间存在整体与部分的关系。通常在定义一个整体类后,再去分析这个整体类的组成结构,从而找出一些成员类,该整体类和成员类之间就形成了聚合关系。如图3-19所示,对于汽车类(Car)和引擎类(Engine)而言,后者就是前者的组成部分之一,因此,它们之间就构成了聚合关系。
图3-19类的聚合关系(Aggregation)
- 聚合关系用尾部带有空心菱形的直线箭头表示,箭头指向成员类;
- 在聚合关系中,成员类是整体类的一部分,即成员对象是整体对象的一部分,但是成员对象可以脱离整体对象独立存在。
5、组合(Composition)
组合也表示类之间整体和部分的关系,但是组合关系中部分和整体具有统一的生存期。一旦整体对象不存在,部分对象也将不存在,部分对象与整体对象之间具有“同生共死”的关系。如图3-20所示,学院类(Colledge)和部门类(Department)之间就是组合关系,不同学院之间的部门显然是不能互换的,学院不存在了,该部门也就没有了。
图3-20类的组合关系(Aggregation)
- 组合关系用尾部带有实心菱形的直线箭头表示,箭头指向成员类;
- 在组合关系中,成员类是整体类的一部分,而且整体类可以控制成员类的生命周期,即成员类的存在依赖于整体类。
为了便于理解,我们给出图3-19和3-20对应的Python代码实现如下。
# 引擎类
class Engine:# 构造函数# name-定义引擎的名称def __init__(self, name):self.name = name
# 汽车类
class Car:# 构造函数# engine-初始化要聚合的engine对象def __init__(self, engine):self.setEngine(engine)# 更换engine对象def setEngine(self, engine):self.__engine = engine# 获取当前engine对象def getEngine(self):return self.__engine
# 测试聚合关系
engine1 = Engine("东风1号")
car = Car(engine1)
engine = car.getEngine()
print(engine.name)
engine2 = Engine("东风2号")
car.setEngine(engine2)
engine = car.getEngine()
print(engine.name)#部门类
class Department:
# 构造函数
# name-部门名称
def __init__(self, name):self.name = name
#学院类
class Colledge:
# 构造函数
# name-学院名称
def __init__(self, name):self.name = nameself.__department1 = Department("部门1")self.__department2 = Department("部门2")
# 打印部门名称
def printDepartments(self):print(self.__department1.name)print(self.__department2.name)
# 测试组合关系
colledge = Colledge("软件学院")
colledge.printDepartments()
由上述代码可以看出,对于聚合关系,我们可以通过外部代码(19-27行)来更换成员对象;而对于组合关系,整体对象一旦实例化,其成员对象就不能被更换了。
6、实现(Implementation)
实现是用来规定接口和实线接口的类或者构建结构之间的关系。接口是操作的集合,实现接口的类需要定义这些操作的实现细节,以便对外提供相应的服务。如图3-21所示,交通工具类(Vehicle)是接口类,它规定了凡是交通工具必须具备移动(move)功能,而船舶类(Ship)和汽车类(Car)作为特殊的交通工具,需要实现接口类Vehicle的移动功能,对于外部而言,只要是实现了Vehicle接口的对象,就可以调用其move方法来实现移动操作。
图3-21类的实现关系(Implementation)
- 实现关系用带空心三角形箭头的虚线来表示,箭头指向接口类;
- 接口和实现类之间的实现关系中,类实现了接口,类中的操作实现了接口中所声明的操作。
同样的,为了便于理解,我们给出了对应的python代码如下。
from abc import ABC, abstractmethod
class Vehicle(ABC): @abstractmethoddef move(self):passclass Ship(Vehicle):def move(self):print("我是船,需要在水上移动")class Car(Vehicle):def move(self):print("我是汽车,需要在陆地上移动")# 测试
ship = Ship()
car = Car()
ship.move()
car.move()