UML类图关系

UML——Unified modeling language UML(统一建模语言),是一种用于软件系统分析和设计的语言工具,它用于帮助软件开发人员进行思考和记录思路的结果

UML本身是一套符号的规定,就像数学符号和化学符号一样,这些符号用于描述软件模型中的各个元素和他们之间的关系,比如类、接口、实现、泛化、依赖、组合、聚合

UML 图

画UML图与写文章差不多,都是把自己的思想描述给别人看,关键在于思路和条理,UML图分类:

  • 用例图(use case)
  • 静态结构图: 类图、对象图、包图、组件图、部署图
  • 动态行为图:交互图(时序图与协作图)、状态图、活动图
  • 类图是描述类与类之间的关系的,是UML图中最核心的

UML 类图

用于描述系统中的类(对象)本身的组成和类(对象)之间的各种静态关系。
类之间的关系: 依赖泛化继承)、实现关联聚合组合

类图简单示例

代码:

1
2
3
4
5
6
7
8
9
class Person:
id: int
name: str

def set_name(self, name: str) -> None:
self.name = name

def get_name(self) -> str:
return self.name

类图:

类图关系

面向对象是符合人们对现实世界的思维模式,利用面向对象设计,特别是采用各种设计模式来解决问题时,会设计多个类,然后创建多个对象,一个设计良好的类,应该是兼顾信息和行为并且高内聚而不同的类之间,应该做到松耦合

当面对应用系统或者需要解决的问题经常是复杂的、高度抽象的,我们 创建的多个对象往往是有联系的,通常对象之间的关系可以分为以下几类:

  • 泛化关系
  • 实现关系
  • 依赖关系
  • 关联关系
  • 聚合关系
  • 组合关系

对于继承(泛化)、实现(实现)这两种关系比较简单,它们体现的是一种类与类、或者类与接口之间的纵向关系。 其他的四种关系则体现的是类与类、或者类与接口之间的引用/横向关系。这四种关系所表现的强弱程度来看,从强到弱依次为:组合>聚合>关联>依赖。

泛化关系(generalization)

  • 泛化关系其实就是继承关系:指的是一个类(称为子类、子接口)继承(extends)另外的一个类(称为父类、父接口)的功能,并可以增加自己额外的一些功能,继承是类与类或者接口与接口之间最常见的关系;
  • 在UML类图中,继承通常使用 空心三角+实线 表示

接口之间的泛化关系: 接口用圆心表示

类之间的泛化关系:

泛化关系表的设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class User:
"""普通用户"""
id: int
name: str


class Employee(User):
"""员工"""
salary: int


class Customer(User):
"""客户"""
address: str

对于面向对象中的继承关系,设计表的时候有三种情况:

  • 共用一张表

    idnamesalaryaddresstype
    1张三1
    2李四20002
    3王五广州3
    4赵六30002
    1. 需要添加一列作为鉴别列,区分不同的用户类型
    2. 能否给员工的salary做约束,非空约束?

    单表查询效率是最高的

  • 每个子类一张表

    User表

    idname
    1张三
    2李四
    3王五
    4赵六

    Employee表

    idsalary
    22000
    43000

    Customer表

    idaddress
    3广州

    把子类共同的信息存储到一张表,各个子类单独一张表,只存储自己的信息

    在查询的时候通过使用JOIN横向连接查询

  • 每个类一张表

    User表

    idname
    1张三

    Employee表

    idnamesalary
    101李四1000
    102王五2000

    Customer表

    idnameaddress
    1001赵六广州

    查询所有的用户(普通用户/员工/客户)

    使用UNION的方式纵向查询.

实现关系(realization)

  • 实现关系:指的是一个class类实现 interface接口(可以实现多个接口)的功能;实现是类与接口之间最常见的关系;
  • 在UML类图中,实现通常使用空心三角+虚线表示

依赖关系(dependent)

依赖关系表示

部分文献中也有用到以下这种

虚线实心箭头也表示依赖,但是比较权威的还是第一种,如果遇到第二种知道也是表示的依赖关系即可

  • 依赖关系:指的是类与类之间的联接。依赖关系表示一个类依赖于另一个类的定义。一般而言,依赖关系在Java语言中体现为成员变量、局域变量、方法的形参、方法返回值,或者对静态方法的调用。
  • 表示一个A类依赖于B类的定义,如果A对象离开B对象,A对象就不能正常编译,则A对象依赖于B对象(A类中使用到了B对象);
  • 比如某人要过河,需要借用一条船,此时人与船之间的关系就是依赖; 表现在代码层面,类B作为参数被类A在某个method方法中使用。
  • 在UML类图中,依赖通常使用虚线箭头表示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class B:
pass


class A:
# 依赖关系情况1: 成员变量.这也是关联关系
b_obj: B

# 依赖关系情况2: 方法参数
def func1(self, b: B):
pass

def func2(self):
# 依赖关系情况3: 方法内的局部变量
bb: B = B()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from abc import ABCMeta


class IA(metaclass=ABCMeta):
pass


class B:
pass


class C:
pass


class A(IA):
b: B

def func(self, c: C):
pass

在python中,没有接口一说,接口的特向可以通过抽象类来模拟;以上代码折射的UML类图为:

关联关系(association)

  • 关联关系:指的是类与类之间的联接,它使一个类知道另一个类的属性和方法(实例变量体现)。A类依赖于B对象,并且把B作为A的一个成员变量, 则A和B存在关联关系.
  • 关联可以是双向的,也可以是单向的。两个类之前是一个层次的,不存在部分跟整体之间的关系。
  • 在UML类图中,关联通常使用实线箭头表示

按照多重性分类:

  • 一对一:一个A对象属于一个B对象,一个B对象属于一个A对象。
  • 一对多:一个A对象包含多个B对象。
  • 多对一:多个A对象属于一个B对象,并且多个A对象中的每个A对象只能属于一个B对象。
  • 多对多:一个A对象属于多个B对象,一个B对象属于多个A对象。

按照导航性分类:

  • 单向:只能从A通过属性导航到B,B不能导航到A。

  • 双向:A可以通过属性导航到B,B也可以通过属性导航到A。

关联关系的判断方法:

  • 判断都是从对象的实例上面来看的
  • 判断关系必须确定一对属性
  • 判断关系必须确定具体需求

一对一

一个A对象属于一个B对象,一个B对象属于一个A对象

单向:python类设计

zone表示QQNumber中关联QQZone对象的关键字

双向:python类设计

表的设计
  1. 唯一外键约束

  2. 共享主键(主外键)

    在开发中,针对one2one情况,使用方式1,但是我们不会使用外键约束来限定,而是通过业务代码来做到唯一

一对多

多个A对象属于一个B对象,并且每个A对象只能属于1个B对象

单向一对多

单向多对一

双向一对多 | 多对一

A可以通过属性导航到B,B也可以通过属性导航到A,其实就是把两个单向关系组合起来。

表的设计

外键应该在多的一方

保存的时候:先保存one方再保存many方,会更加合理一些,性能更好(SQL要少些)
案例:保存一个部门,两个员工.
情况1:先保存部门,再保存员工:

1
2
3
4
5
-- 先添加部门
INSERT INTO department (name) VALUES( ? );

-- 第二个?的值,就该是保存部门之后的ID值
INSERT INTO enployee(nane, dept_id) VALUES (?,?);

情况2:先保存员工,再保存部门:

1
2
3
4
5
6
7
8
-- 先保存员工,此时没有对应部门的ID
INSERT INTO enployee(name, dept_id) VALUES (?,NULL );

-- 添加部门
INSERT INTO department(name) VALUES (NULL );

-- 额外发送SQL来维护dept_id这一列的值
UPDATE employee set dept_id = ? where id = ?;

多对多

一个A对象属于多个B对象;一个B对象属于多个A对象

表的设计采用中间表的形式,记录两个表行主键

聚合关系(aggregtion)

  • 聚合关系是关联关系的一种特例,他体现的是整体与部分,是一种“弱拥有”的关系,即has-a的关系。聚合是整体和个体之间的关系。
  • 例如,汽车类与引擎类、轮胎类,以及其它的零件类之间的关系便是整体和个体的关系。
  • 与关联关系一样,聚合关系 也是通过实例变量实现的。但是关联关系所涉及的两个类是处在同一层次上的,而在聚合关系中,两个类是处在不平等层次上的,一个代表整体,另一个代表部分。
  • 聚合关系表示整体和个体的关系,整体和个体可以相互独立存在,一定是有两个模块分别管理整体和个体。
  • 在UML类图中,聚合通常使用空心菱形+实线箭头表示

如同手机和手机卡,手机卡是组成手机的一部分,但是手机卡也可以独立存在;同样手机没有手机卡,依然是一部手机,仅仅不能通话罢了。

组合关系(composition)

  • 组合关系是关联关系的一种特例,他体现的是一种contains-a(包含)的关系,这种关系比聚合更强,也称为强聚合。
  • 它要求普通的聚合关系中代表整体的对象负责代表部分对象的生命周期,组合关系是不能共享的。代表整体的对象需要负责保持部分对象和存活,在一些情况下将负责代表部分的对象湮灭掉。代表整体的对象可以将代表部分的对象传递给另一个对象,由后者负责此对象的生命周期。换言之,代表部分的对象在每一个时刻只能与一个对象发生组合关系,由后者排他地负责生命周期。部分和整体的生命周期一样。
  • 整体和个体不能独立存在,一定是在一个模块中同时管理整体和个体,生命周期必须相同(级联)。
  • 在UML类图中,组合通常使用实心菱形+实线箭头表示

这个例子很贴切,线是由点组成的,没有了线代表组成线的点也不存在了;同样没有点根本组成不了线。与聚合关系相比,四个字形容:生死与共

相互之间的区别

聚合与组合

  • 聚合与组合都是一种结合关系,只是额外具有整体-部分的意涵。

  • 部件的生命周期不同

    聚合关系中,整件不会拥有部件的生命周期,所以整件删除时,部件不会被删除。再者,多个整件可以共享同一个部件。 组合关系中,整件拥有部件的生命周期,所以整件删除时,部件一定会跟着删除。而且,多个整件不可以同时间共享同一个部件。

  • 聚合关系是“has-a”关系,组合关系是“contains-a”关系。

关联和聚合

I. 表现在代码层面,和关联关系是一致的,只能从语义级别来区分。
II. 关联和聚合的区别主要在语义上,关联的两个对象之间一般是平等的,例如你是我的朋友,聚合则一般不是平等的。
III. 关联是一种结构化的关系,指一种对象和另一种对象有联系。
IV. 关联和聚合是视问题域而定的,例如在关心汽车的领域里,轮胎是一定要组合在汽车类中的,因为它离开了汽车就没有意义了。但是在卖轮胎的店铺业务里,就算轮胎离开了汽车,它也是有意义的,这就可以用聚合了。

关联和依赖

I. 关联关系中,体现的是两个类、或者类与接口之间语义级别的一种强依赖关系,比如我和我的朋友;这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的。
II. 依赖关系中,可以简单的理解,就是一个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A。

泛化和实现

实现表示类对接口的实现关系,表示方式:用一条带有空心三角箭头的虚线指向接口。
泛化表示类与类之间的继承关系、接口与接口之间的继承关系,表示方式一条带有空心三角箭头的实线指向基类(父接口)。

综合比较

这几种关系都是语义级别的,所以从代码层面并不能完全区分各种关系;但总的来说,后几种关系所表现的强弱程度依次为: 组合>聚合>关联>依赖
其中依赖(Dependency)的关系最弱,而关联(Association),聚合(Aggregation),组合(Composition)表示的关系依次增强。换言之关联,聚合,组合都是依赖关系的一种,聚合是表明对象之间的整体与部分关系的关联,而组合是表明整体与部分之间有相同生命周期关系的聚合。
而关联与依赖的关系用一句话概括下来就是,依赖描述了对象之间的调用关系,而关联描述了对象之间的结构关系。