类图

类图是一种软件工程中的**统一建模语言(UML)**的静态结构图,用于描述系统的类集合、类的属性和类之间的关系。它主要用于概念建模,将系统的模型转化为代码。

类图的主要元素包括:

  1. 类的三个区域:
    • 最上面是类的名称
    • 中间部分包含类的属性(字段)。
    • 底部部分包含类的操作(方法)。
  2. 成员可见性:
    • + 表示公共(public)成员。
    • - 表示私有(private)成员。
    • # 表示保护(protected)成员。
    • ~ 表示包(package)成员,即对包内其他成员可见。
  3. 类图可进一步结合状态图或UML状态机来描述系统的行为。

类图是一种强大的工具,通过图形化的方式展示了系统中的类、它们之间的关系以及类的内部结构,帮助我们更好地理解和设计软件系统。

类间关系

类之间的关系指的是在面向对象编程中,不同类之间可能存在的连接和依赖关系。这些关系描述了类之间的交互方式和相互影响。

类之间的关系主要分为以下六种:

  1. 依赖关系(Dependency):一个类的实现依赖于另一个类的定义,但两者并没有强耦合。一个类的变化不会影响另一个类的实现,只是依赖关系较弱的一种关系。

  2. 关联关系(Association):两个类之间有一定的关联,表示一个类知道另一个类的存在。关联关系可以是单向的或双向的。关联关系比依赖关系更强,两者之间有更紧密的联系。

  3. 聚合关系(Aggregation):表示整体与部分之间的关系,是一种强于关联关系的关系。聚合关系中,整体和部分可以分开存在。例如,一个班级包含多个学生,但学生可以存在于其他班级。

  4. 组合关系(Composition):也表示整体与部分之间的关系,但是组合关系中整体和部分之间具有更强的耦合。整体和部分的生命周期是相互依赖的,部分不能脱离整体而存在。例如,一个汽车包含引擎和轮胎,它们之间是组合关系。

  5. 泛化关系(Generalization):即继承的反方向,指的是一个类(称为父类、父接口)具有另外的一个(或一些)类(称为子类、子接口)的共有功能。子类可视为其父类的特例,并可以增加新功能。

  6. 实现关系(Realization):表示类与接口之间的关系,一个类实现了一个接口,必须实现接口中定义的所有方法。

依赖

依赖关系是一种比较松散的关系,表示一个类依赖于另一个类的定义,但两者之间的耦合度相对较低。在依赖关系中,一个类的变化不会直接影响到另一个类的实现。

你可以这么理解,一个类只要用(using)到了另一个类,那么它们之间就存在依赖关系。依赖关系仅仅描述了类与类之间的一种使用与被使用的关系。

比如现在有一个Driver类和一个Car类,Driver类依赖于Car类来实现其功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Car { // 汽车类
private String brand; //品牌
public Car(String brand) {
this.brand = brand;
}
public void move() {
System.out.println(brand+"汽车在行驶...");
}
}
class Driver { //驾驶员类
public void drive(Car car) { //方法参数类型为Car;Driver依赖 Car
car.move();
}
}
public class DriverAndCar {
public static void main(String[] args) {
new Driver().drive(new Car("奥迪"));
}
}

可以看到,Car(汽车)和Driver(驾驶员)之间存在依赖关系。

Driver类有一个drive方法,该方法的参数类型是Car,表明驾驶员依赖于汽车对象。在主类DriverAndCarmain方法中,创建了一个Driver对象并调用其drive方法,传入一个Car对象(奥迪),从而实现了驾驶员驾驶汽车的过程。

UML类图中,依赖关系用带箭头的虚线表示,由依赖的一方指向被依赖的一方

依赖关系的存在使得类与类之间的耦合度相对较低,有利于代码的灵活性和可维护性。

关联

关联关系,表示一类对象与另一类对象之间有联系的一种结构化关系。它反映了整体与部分之间的关系,通常表现为一个类(或接口)类型的对象作为另一个类的属性(字段)。

关联关系可以分为单向关联和双向关联。

  • 单向关联: 一个类的对象作为另一个类的属性,表示一种单向的拥有关系。例如,Customer与Address的关系,表示顾客拥有快递地址。

  • 双向关联: 两个类的对象互为属性,表示双向的关系。例如,Employee与Department的关系,表示员工与部门之间存在双向关联。

关联关系可细分为聚合和组合两种使用方式:

  • 聚合(Aggregation): 部分类对象可以独立存在于整体类对象之外,关系较为松散。例如,Company与Employee的关系,表示公司包含员工,但员工可以独立存在。

  • 组合(Composition): 部分类对象与整体类对象具有统一的生存期,整体类对象的生命周期控制部分类对象。例如,Head与Mouth的关系,表示头部包含口,口的生命周期与头的生命周期相关联。

此外,关联关系中还可能存在自关联,即一个类的属性对象类型为该类本身,例如单链表中的结点类。

在UML类图中,关联关系用实线连接有关联的两个类。单向关联用一个带箭头的实线表示,箭头从使用类指向被关联的类,双向关联用带箭头或者没有箭头的实线来表示。

聚合

聚合(Aggregation)是一种表示整体与部分的一类特殊的关联关系,可以理解为一种“… owns a …”(拥有)关系。

聚合关系是一种弱关联,部分对象可以独立存在,不依赖于整体对象。它们可以具有各自的生命周期。比如图书馆包含(owns a) 学生和书籍(即使没有图书馆,学生也可以存在)。再比如老师和课程之间的关系也是聚合。一个教授可以教授多门课程,而每门课程又可以由不同的教授来教,并且各自的生命周期可以独立存在。

在代码实现时,类对象通过构造器或setter方法进行注入。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Employee { // 部分类
private String name;
public Employee(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Company { //整体类
private String companyName;
private List<Employee> employees; //Company聚合Employee
public Company(String companyName) { //构造方法
this.companyName = companyName;
}
public String getCompanyName() { //getter
return companyName;
}
public void setEmployees(List<Employee> employees) { //setter
this.employees = employees;
}
public List<Employee> getEmployees() { //getter
return employees;
}
}

在这个例子中:

  • Company类(整体类): 代表了整体,即公司。它有一个属性是employees,用于存储雇佣的员工列表。

  • Employee类(部分类): 代表了部分,即员工。员工有一个属性name表示姓名。

通过这个设计,公司(整体类)可以包含多个员工(部分类),而员工可以独立存在,不依赖于任何特定的公司(公司关了,员工也不会消失)。这符合聚合关系的特性,即部分类对象可以脱离整体类对象而独立存在

类图如下:

聚合关系表示为一条实线,在关联端带有一个未填充的菱形,该实线连接到表示聚合的类。

组合

组合(Composition)关系是一类“强”的整体与部分的包含关系(" … is a part of …"),部分类对象与整体类对象具有统一的生存期。当整体类对象消亡时,部分类对象也将消亡。或者说,整体类对象控制了部分类对象的生命周期。

整体对象完全负责创建、销毁和管理部分类对象。部分类对象无法独立存在或被其他对象所拥有。在代码实现时,部分类对象在整体类属性声明时或它的构造方法里实例化

举例:

公司与公司部门之间的关系可以被视为组合关系。一个公司包含多个部门,而且公司的存在决定了部门的存在,整体对象(公司)管理部分对象(部门),整体对象的销毁将导致部分对象的销毁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.util.ArrayList;
import java.util.List;

class Company {
private String companyName;
private List<Department> departments;

public Company(String companyName) {
this.companyName = companyName;
this.departments = new ArrayList<>();
}

public String getCompanyName() {
return companyName;
}

public void addDepartment(Department department) {
departments.add(department);
}

public List<Department> getDepartments() {
return departments;
}

// 其他公司相关的方法...

public void closeCompany() {
// 公司关闭时,销毁所有部门
for (Department department : departments) {
department.closeDepartment();
}
System.out.println("Company " + companyName + " is closed.");
}
}

class Department {
private String departmentName;

public Department(String departmentName) {
this.departmentName = departmentName;
}

public String getDepartmentName() {
return departmentName;
}

// 部门相关的方法...

public void closeDepartment() {
// 部门关闭时的清理操作
System.out.println("Department " + departmentName + " is closed.");
}
}

类图如下:

在类图中,组合关系通常用实心的菱形箭尾和实线表示,箭头指向整体对象。

泛化

泛化(Generalization)关系指的是一个类(称为父类、父接口)具有另外的一个(或一些)类(称为子类、子接口)的共有功能。实际上就是类(或接口)之间的继承关系。

子类继承了父类的属性和方法,可以视为是父类的特例,同时具有扩展或增加新功能的能力。通过泛化,可以将通用的行为和属性提取到父类中,从而提高代码的复用性和可维护性。

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 父类
class Animal {
void eat() {
System.out.println("Animal is eating.");
}
}

// 子类
class Dog extends Animal {
void bark() {
System.out.println("Dog is barking.");
}
}

// 子类
class Cat extends Animal {
void meow() {
System.out.println("Cat is meowing.");
}
}

在这个例子中,DogCat类继承自Animal类,形成了泛化关系。Animal类包含了共有的eat方法,而DogCat类分别增加了独有的功能。

在类图中,泛化关系通常用带空心三角形的箭头和实线表示。箭头指向父类,表示子类继承自父类。

实现

实现(Realization)关系很简单,就是指一个class类实现interface接口(可以是多个)。实现类需要实现接口中声明的所有抽象方法。

在Java中此类关系通过关键字implements明确标识。

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Vehicle {//定义接口
void move();
}

class Ship implements Vehicle {//实现接口
@Override
public void move() {
System.out.println("船在水上行驶");
}
}

class Car implements Vehicle {//实现接口
@Override
public void move() {
System.out.println("汽车在公路上行驶");
}
}

在类图中,实现关系用带空心三角形箭头的虚线表示。