任课老师:Zhao
一、UML
(一)UML类之间的关系
1. 依赖(Dependency)
定义回顾
依赖是临时性、弱关联的关系,依赖方不持有被依赖方实例,仅在方法中临时使用。
C++ 示例代码
#include <iostream>
#include <string>
// 被依赖方:日志类
class Logger {
public:
void log(const std::string& msg) const {
std::cout << "[Log] " << msg << std::endl;
}
};
// 依赖方:业务服务类
class Service {
public:
// 仅在方法中临时使用Logger,不持有其实例
void doBusiness() {
Logger logger; // 临时创建Logger实例
logger.log("业务逻辑执行中"); // 依赖Logger的log功能
}
};
int main() {
Service service;
service.doBusiness();
return 0;
}
UML 图示(Mermaid)
classDiagram
Service ..> Logger : 依赖 (虚线箭头)
class Service {
+doBusiness()
}
class Logger {
+log(msg: string)
}
分析
Service的doBusiness方法中临时创建Logger实例并调用其log方法,无成员变量持有Logger,符合“临时性、弱关联”特点。- 若
Logger的log方法签名修改(如参数类型变化),Service的doBusiness方法需同步修改,体现“被依赖方变化影响依赖方”。
2. 泛化(Generalization)
定义回顾
对应继承关系,子类继承父类属性和方法,遵循“is-a”逻辑,UML 用带空心三角的实线表示(子类指向父类)。
C++ 示例代码
#include <iostream>
#include <string>
// 父类(一般类):动物
class Animal {
protected:
std::string name;
public:
Animal(const std::string& n) : name(n) {}
// 共性方法
void eat() const {
std::cout << name << " 进食" << std::endl;
}
void sleep() const {
std::cout << name << " 睡觉" << std::endl;
}
// 虚方法,允许子类重写
virtual void makeSound() const = 0; // 纯虚函数,抽象方法
};
// 子类(特殊类):狗
class Dog : public Animal {
public:
Dog(const std::string& n) : Animal(n) {}
// 重写父类虚方法(个性扩展)
void makeSound() const override {
std::cout << name << " 汪汪叫" << std::endl;
}
};
// 子类(特殊类):猫
class Cat : public Animal {
public:
Cat(const std::string& n) : Animal(n) {}
// 重写父类虚方法(个性扩展)
void makeSound() const override {
std::cout << name << " 喵喵叫" << std::endl;
}
};
int main() {
Dog dog("旺财");
dog.eat(); // 复用父类方法
dog.makeSound(); // 子类个性化实现
Cat cat("咪宝");
cat.sleep(); // 复用父类方法
cat.makeSound(); // 子类个性化实现
return 0;
}
UML 图示(Mermaid)
classDiagram
Animal <|-- Dog : 继承 (空心三角实线)
Animal <|-- Cat : 继承 (空心三角实线)
class Animal {
-name: string
+Animal(name: string)
+eat()
+sleep()
+makeSound()*
}
class Dog {
+Dog(name: string)
+makeSound()
}
class Cat {
+Cat(name: string)
+makeSound()
}
分析
Dog/Cat继承Animal,遵循“Dog is an Animal”“Cat is an Animal”的“is-a”逻辑。- 父类
Animal抽取共性(eat/sleep),子类通过重写makeSound实现个性扩展。 - 纯虚函数
makeSound强制子类实现,体现继承的多态特性。
3. 实现(Realization)
定义回顾
类实现接口/抽象类,需重写所有抽象方法,遵循“implements”逻辑,UML 用带空心三角的虚线表示(实现类指向接口)。
C++ 示例代码
#include <iostream>
#include <string>
// 接口(抽象类):用户服务
class UserService {
public:
// 纯虚函数(接口契约)
virtual std::string getUser(int id) const = 0;
virtual void addUser(const std::string& name) const = 0;
virtual ~UserService() = default; // 虚析构,避免内存泄漏
};
// 实现类:用户服务实现
class UserServiceImpl : public UserService {
public:
// 重写接口所有抽象方法
std::string getUser(int id) const override {
return "用户ID: " + std::to_string(id) + ",名称:默认用户";
}
void addUser(const std::string& name) const override {
std::cout << "添加用户:" << name << " 成功" << std::endl;
}
};
int main() {
UserService* service = new UserServiceImpl();
std::cout << service->getUser(1001) << std::endl;
service->addUser("张三");
delete service;
return 0;
}
UML 图示(Mermaid)
classDiagram
UserService <|.. UserServiceImpl : 实现 (空心三角虚线)
class UserService {
+getUser(id: int): string*
+addUser(name: string)*
}
class UserServiceImpl {
+getUser(id: int): string
+addUser(name: string)
}
分析
UserService作为接口(纯抽象类)定义“契约”(getUser/addUser),UserServiceImpl实现该接口并完成所有抽象方法的具体逻辑。- C++ 无原生
interface关键字,通过“纯虚函数 + 虚析构”模拟接口,符合“接口定义规范,实现类提供具体实现”的核心特点。 - 支持多实现:若有
PayService接口,UserServiceImpl可同时继承UserService和PayService(多继承)。
4. 关联(Association)
定义回顾
稳定、长期的结构关系,类之间持有对方实例引用,分单向/双向关联,遵循“has-a”逻辑,UML 用实线(带箭头表示单向)。
C++ 示例代码
#include <iostream>
#include <string>
#include <vector>
// 学生类
class Student;
// 教师类(单向关联:Teacher 持有 Student 引用)
class Teacher {
private:
std::string name;
std::vector<Student*> students; // 关联多个学生(1:N)
public:
Teacher(const std::string& n) : name(n) {}
void addStudent(Student* s);
void showStudents() const;
std::string getName() const { return name; }
};
// 学生类(双向关联:Student 持有 Teacher 引用)
class Student {
private:
std::string name;
std::vector<Teacher*> teachers; // 关联多个教师(N:M)
public:
Student(const std::string& n) : name(n) {}
void addTeacher(Teacher* t) {
teachers.push_back(t);
}
std::string getName() const { return name; }
};
// 实现Teacher的addStudent方法
void Teacher::addStudent(Student* s) {
students.push_back(s);
s->addTeacher(this); // 双向关联:学生也关联教师
}
void Teacher::showStudents() const {
std::cout << name << " 教授的学生:";
for (const auto& s : students) {
std::cout << s->getName() << " ";
}
std::cout << std::endl;
}
int main() {
Teacher t1("王老师");
Teacher t2("李老师");
Student s1("张三");
Student s2("李四");
t1.addStudent(&s1);
t1.addStudent(&s2);
t2.addStudent(&s1);
t1.showStudents(); // 王老师:张三 李四
t2.showStudents(); // 李老师:张三
return 0;
}
UML 图示(Mermaid)
classDiagram
Teacher "1" -- "*" Student : "Accociation Both Side"
class Teacher {
-name: string
-students: vector<Student*>
+Teacher(name: string)
+addStudent(s: Student*)
+showStudents()
+getName(): string
}
class Student {
-name: string
-teachers: vector<Teacher*>
+Student(name: string)
+addTeacher(t: Teacher*)
+getName(): string
}
分析
Teacher和Student是双向关联,双方都持有对方的实例引用(vector存储指针),符合“稳定、长期”的关联特点。- 关联线上标注的“1”“*”表示多重度:1个教师对应多个学生(1:N),1个学生对应多个教师(N:M)。
- 遵循“has-a”逻辑:Teacher has students,Student has teachers。
5. 聚合(Aggregation)
定义回顾
关联的特殊形式,“整体-部分”关系,部分可脱离整体独立存在(生命周期不绑定),UML 用带空心菱形的实线表示(菱形指向整体)。
C++ 示例代码
#include <iostream>
#include <string>
#include <vector>
// 部分类:车轮
class Wheel {
private:
std::string brand; // 车轮品牌
public:
Wheel(const std::string& b) : brand(b) {}
std::string getBrand() const { return brand; }
// 车轮可独立存在,有自己的生命周期
void repair() const {
std::cout << "维修 " << brand << " 品牌车轮" << std::endl;
}
};
// 整体类:汽车
class Car {
private:
std::string model; // 车型
std::vector<Wheel*> wheels; // 聚合多个车轮(整体持有部分引用)
public:
Car(const std::string& m) : model(m) {}
// 添加车轮(部分可从外部传入,不依赖Car创建)
void addWheel(Wheel* w) {
wheels.push_back(w);
}
void showWheels() const {
std::cout << model << " 的车轮品牌:";
for (const auto& w : wheels) {
std::cout << w->getBrand() << " ";
}
std::cout << std::endl;
}
~Car() {
// 聚合关系:Car销毁时不销毁Wheel(部分可独立存在)
std::cout << model << " 报废,但车轮可回收复用" << std::endl;
}
};
int main() {
// 1. 先创建车轮(部分独立存在)
Wheel w1("米其林");
Wheel w2("普利司通");
Wheel w3("固特异");
Wheel w4("马牌");
// 2. 将车轮组装到汽车(整体)
Car car("特斯拉Model 3");
car.addWheel(&w1);
car.addWheel(&w2);
car.addWheel(&w3);
car.addWheel(&w4);
car.showWheels();
// 3. 汽车销毁后,车轮仍可独立使用
w1.repair();
return 0;
}
UML 图示(Mermaid)
classDiagram
Car o-- Wheel : 聚合(空心菱形,菱形指向Car)
class Car {
-model: string
-wheels: vector<Wheel*>
+Car(model: string)
+addWheel(w: Wheel*)
+showWheels()
~Car()
}
class Wheel {
-brand: string
+Wheel(brand: string)
+getBrand(): string
+repair()
}
分析
Car(整体)聚合Wheel(部分),符合“整体-部分”关系;Wheel可先于Car创建,且Car销毁时不销毁Wheel(部分可独立存在、复用)。- 空心菱形指向
Car(整体),体现“弱整体-部分”关系:车轮可从汽车拆卸,安装到其他汽车或单独维修。
6. 组合(Composition)
定义回顾
关联的特殊形式,“整体-部分”关系,部分生命周期与整体绑定(整体销毁时部分也销毁),UML 用带实心菱形的实线表示(菱形指向整体)。
C++ 示例代码
#include <iostream>
#include <string>
// 部分类:心脏
class Heart {
private:
int beatRate; // 心率
public:
Heart() : beatRate(75) {
std::cout << "心脏创建,初始心率:" << beatRate << "次/分钟" << std::endl;
}
void beat() const {
std::cout << "心脏跳动,心率:" << beatRate << "次/分钟" << std::endl;
}
~Heart() {
std::cout << "心脏停止跳动" << std::endl;
}
};
// 整体类:人
class Person {
private:
std::string name;
Heart heart; // 组合关系:直接包含Heart对象(而非指针/引用)
public:
Person(const std::string& n) : name(n), heart() { // 整体创建时,部分自动创建
std::cout << name << " 出生" << std::endl;
}
void live() const {
std::cout << name << " 存活中:";
heart.beat();
}
~Person() { // 整体销毁时,部分自动销毁
std::cout << name << " 离世" << std::endl;
}
};
int main() {
{
Person p("张三");
p.live(); // 张三存活中:心脏跳动...
} // 作用域结束,Person销毁,Heart也随之销毁
// 无法单独创建Heart并复用(逻辑上,心脏不能脱离人独立存在)
return 0;
}
UML 图示(Mermaid)
classDiagram
Person *-- Heart : 组合(实心菱形,菱形指向Person)
class Person {
-name: string
-heart: Heart
+Person(name: string)
+live()
~Person()
}
class Heart {
-beatRate: int
+Heart()
+beat()
~Heart()
}
分析
Person(整体)组合Heart(部分),Heart是Person的成员变量(而非指针/引用),体现“强整体-部分”关系。- 生命周期绑定:
Person创建时Heart自动构造,Person销毁时Heart自动析构,符合“部分无法脱离整体独立存在”的核心特点。 - 逻辑上,心脏不能脱离人单独存在,完全依赖整体,是组合关系的典型体现。
总结:6种关系的核心区别
| 关系 | 核心逻辑 | 生命周期 | UML 特征 | C++ 实现要点 |
|————|—————-|—————-|—————————|———————————-|
| 依赖 | 使用(use-a) | 临时 | 虚线箭头(指向被依赖方) | 方法内临时创建/调用,无成员引用 |
| 泛化 | 继承(is-a) | 子类依赖父类 | 空心三角实线(子类→父类) | public 继承,重写父类方法 |
| 实现 | 实现(implements) | 实现类依赖接口 | 空心三角虚线(实现类→接口) | 继承纯抽象类,重写所有纯虚函数 |
| 关联 | 持有(has-a) | 长期绑定 | 实线(单向/双向) | 成员变量持有对方指针/引用 |
| 聚合 | 整体-部分(弱)| 部分独立 | 空心菱形实线(菱形→整体) | 整体持有部分指针,不管理生命周期 |
| 组合 | 整体-部分(强)| 部分依赖整体 | 实心菱形实线(菱形→整体) | 整体直接包含部分对象,管理生命周期 |
// 依赖:通过参数或局部变量
void process(Logger& logger) { /* ... */ }
// 泛化:公有继承
class Dog : public Animal { /* ... */ };
// 实现:纯虚函数
class IService { virtual void doWork() = 0; };
// 关联:成员指针
class Teacher { Student* student; };
// 聚合:不控制生命周期的指针
class Team { Member* member; };
// 组合:控制生命周期的对象
class Car { Engine engine; }; // Engine在Car内部创建
8. UML关联关系中“多重度”概念说明
“多重度”(Multiplicity)用于定义关联关系中,两个类的实例之间可以建立的对应数量关系,通常标注在关联线的两端,清晰界定实例交互的数量边界。
(1)常用多重度符号及含义
| 符号 | 名称 | 具体含义 |
|---|---|---|
1 |
一个 | 关联的一端必须且只能有1个实例 |
0..1 |
零或一个 | 关联的一端可以有0个实例(可选),也可以有1个实例 |
* 或 0..* |
零或多个 | 关联的一端可以有0个、1个或多个实例(数量无上限) |
1..* |
一个或多个 | 关联的一端至少有1个实例,也可以有多个实例 |
n..m(如2..5) |
范围数量 | 关联的一端实例数量在n到m之间(包含n和m),n、m为具体数字 |
(2)典型应用场景示例
- 1:1(一对一):一个实例仅对应另一个实例。例如“Person”与“IDCard”,一个人只能有一张身份证,一张身份证也仅属于一个人,关联线两端均标注
1。 - 1:N(一对多):一个实例可对应多个实例,反之仅对应一个。例如“Department”与“Employee”,一个部门可有多个员工,一个员工仅属于一个部门,部门端标注
1,员工端标注*。 - N:M(多对多):两端实例均可对应多个实例。例如“Student”与“Course”,一个学生可选多门课程,一门课程可被多个学生选择,关联线两端均标注
*。
(二)状态图
状态图(State Diagram)是UML行为图的一种,用于描述对象从创建到销毁的生命周期中,不同状态的转换逻辑,核心是“状态”与“转换”两大要素。以下是分步骤的绘制方法,适用于Markdown(需借助Mermaid语法渲染)或可视化工具(如Visio、StarUML)。
1. 明确绘制目标与核心要素
在动笔前先界定范围,避免信息冗余。状态图的核心要素只有3个,需提前梳理清楚:
- 状态(State):对象的行为模式或属性集合,分3类
- 初始状态:对象生命周期的起点,用“实心小圆”表示,一个图仅1个。
- 普通状态:对象的常规行为,用“圆角矩形”表示(如“待支付”“已发货”)。
- 终止状态:对象生命周期的终点,用“内部带实心圆的大圆”表示,可多个。
- 转换(Transition):状态间的切换,用“带箭头的实线”表示,箭头上需标注“触发事件[守卫条件]/动作”(如“支付成功[金额≥0]/生成订单”)。
- 对象(可选):若需明确归属,可在图顶部标注“对象名: 类名”(如“订单123: Order”)。
2. 按“生命周期流”梳理状态逻辑
以“电商订单”为例,按时间顺序拆解状态,避免逻辑断层,步骤如下:
- 确定起点:初始状态(订单创建)。
- 罗列普通状态:按流程拆解核心节点,如“待支付”→“支付中”→“已支付”→“已发货”→“已签收”。
- 补充分支状态:考虑异常或分支场景,如“待支付”可因“超时未支付”转入“已取消”(终止状态)。
- 确定终点:每个流程分支对应一个终止状态,如“已签收”“已取消”均为终止状态。
3. 用Mermaid语法编写Markdown状态图(实操示例)
Mermaid是Markdown中最常用的状态图渲染语法,无需手动拖拽,代码即图,以下是“电商订单”的完整示例:
stateDiagram-v2
[*] --> 待支付 : 初始状态→第一个普通状态,[*]代表初始
待支付 --> 支付中 : 点击支付按钮/发起支付请求
支付中 --> 已支付 : 支付成功[金额≥订单金额]/生成支付凭证
支付中 --> 待支付 : 支付失败[网络异常]/提示重试
待支付 --> 已取消 : 超时未支付[超过30分钟]/释放库存
已支付 --> 已发货 : 商家点击发货/生成物流单号
已发货 --> 已签收 : 买家确认收货/更新订单状态
已签收 --> [*] : 订单完成/关闭生命周期(终止状态)
已取消 --> [*] : 订单终止/释放资源(终止状态)
- 语法说明:
stateDiagram-v2:固定开头,确保渲染效果。[*]:代表初始/终止状态(起点用[*]-->状态,终点用状态-->[*])。- 转换规则:
源状态 --> 目标状态 : 触发事件[守卫条件]/执行动作,“[守卫条件]”和“/动作”可省略(如仅“点击支付”)。
4. 绘制注意事项(避坑指南)
- 避免“状态冗余”:只保留影响行为的核心状态,如“订单”无需拆分“待支付-等待1分钟”“待支付-等待2分钟”。
- 转换标注清晰:触发事件需具体(如“点击支付”而非“操作”),守卫条件需可判断(如“超时30分钟”而非“超时”)。
- 逻辑闭环:每个普通状态都要有后续转换(除终止状态),避免“孤立状态”(如“已支付”必须能到“已发货”或异常状态)。
(三)数据流图(DFD)详解与完整示例
1. 定义与特点
- 描述系统逻辑模型,仅描绘信息流动和处理情况,不涉及具体物理实现。
- 直观易懂,便于技术人员与需求方(客户、用户)沟通,体现工程思想。
- 属于行为模型、功能模型,不同于程序流程图。
2. 核心符号(术语)
| 符号类型 | 定义与说明 | 表示形式 |
|---|---|---|
| 外部实体 | 数据的源点或终点(如人、设备、其他系统)。 | 正方形 |
| 处理 | 数据转换器,将输入转换为输出。 | 圆形/矩形(带编号) |
| 数据流 | 数据在系统内的有方向流动。 | 带箭头的线段 |
| 数据存储 | 数据的静止存储状态(如文件、数据库)。 | 两条平行线 |
3. 绘图规则总结
- 加工:必须有输入和输出,标签用动词短语。
- 数据存储:数据不能直接在存储间流动,必须通过加工;标签用名词短语。
- 外部实体:数据不能直接从源点流向终点;标签用名词短语。
- 数据流:单向流动,分叉/汇合有意义,不能回流,标签用名词短语。
4. 绘制方法与分层原则
- 宏观原则:“由外向里”——先定系统边界,再画内部。
- 分层结构:
- 顶层(0层):系统整体边界,只有一个加工。
- 一级细化图:分解顶层加工为核心子加工(3-7个)。
- 二级及以下细化图:对单一加工继续分解,直至每个加工功能单一。
- 关键原则:
- 保持平衡:子图的输入输出必须与父图对应加工的输入输出完全一致。
- 编号规范:加工编号体现层级(如1.0, 1.1, 1.1.1)。
- 合理分解:每层加工数建议控制在7±2个。
5. 完整案例:在线购书系统DFD
(1)第一步:顶层DFD(0层)—— 定系统边界
graph TD
%% 外部实体
Customer[“顾客”]
Warehouse[“书店仓库”]
PaymentGateway[“支付平台”]
%% 顶层加工(系统整体)
System(0.0 在线购书系统)
%% 数据流
Customer -->|“1. 购书请求<br>(图书ID,数量,地址)”| System
PaymentGateway -->|“2. 支付确认<br>(订单号,状态)”| System
System -->|“3. 订单确认<br>(订单号,预计发货时间)”| Customer
System -->|“4. 发货通知<br>(订单号,图书清单,地址)”| Warehouse
要点:明确系统与外部世界的所有接口。
(2)第二步:一级DFD —— 分解核心流程
graph TD
%% 外部实体
C[“顾客”]
W[“书店仓库”]
PG[“支付平台”]
%% 一级子加工
P1(1.0 订单处理)
P2(2.0 库存检查)
P3(3.0 支付验证)
P4(4.0 订单管理与通知)
%% 数据存储
DS1[“F1 图书库存表”]
DS2[“F2 用户订单表”]
%% 外部→加工
C -->|“购书请求”| P1
PG -->|“支付确认”| P3
%% 加工→加工
P1 -->|“库存查询请求”| P2
P2 -->|“库存查询结果”| P1
P1 -->|“待支付订单”| P3
P3 -->|“支付验证结果”| P4
%% 加工↔数据存储
P2 <-->|“读/更新库存”| DS1
P4 <-->|“读/写订单”| DS2
%% 加工→外部
P4 -->|“订单确认”| C
P4 -->|“发货通知”| W
要点:展示系统内部核心处理流程和数据存储,保持与顶层图的输入输出平衡。
(3)第三步:二级DFD —— 细化“1.0 订单处理”
graph TD
%% 外部实体
C[顾客]
%% 父加工边界(子图)
subgraph 1_0_订单处理["1.0 订单处理"]
direction LR
SP1[1.1 接收与校验请求]
SP2[1.2 验证用户信息]
SP3[1.3 生成订单草稿]
end
%% 关联的加工和数据存储(本图外部)
P2(2.0 库存检查)
DS3[F3 用户信息表]
P3_Placeholder(3.0 支付验证)
%% 数据流
C -->|购书请求| SP1
SP1 -->|校验后的请求| SP2
SP2 <-->|查询用户| DS3
SP2 -->|有效用户信息| SP3
SP3 -->|库存查询请求| P2
P2 -->|库存结果| SP3
SP3 -->|待支付订单| P3_Placeholder
要点:展示单一加工的详细处理步骤,保持与父加工(1.0)的输入输出平衡。
(四)数据字典(DD)详解与示例
1. 定义与作用
- 对系统中所有数据元素的有组织列表及精确定义。
- 与DFD共同构成完整的系统逻辑模型,是需求规格说明书的核心。
2. 核心条目及描述要素
| 条目类型 | 关键描述要素 |
|---|---|
| 数据流 | 名称、说明、来源、去向、组成(数据结构)、流量 |
| 数据元素 | 名称、类型、长度、取值范围、相关元素 |
| 数据文件 | 名称、简述、输入/输出数据、组成、存储方式、存取频率 |
| 加工逻辑 | 名称、编号、简要描述、输入/输出、加工规则 |
| 源点/汇点 | 名称、简述、相关数据流 |
3. 数据定义式符号
| 符号 | 含义 | 示例 |
|---|---|---|
= |
定义为 | 订单编号 = 10{数字}10 |
+ |
与(顺序连接) | 购书请求 = 用户ID + 图书列表 + 收货地址 |
{ } |
重复(循环) | 图书列表 = 1{图书ID + 数量}10 |
\[ \| \] |
或(选择) | 支付状态 = [ “成功” \| “失败” \| “待处理” ] |
( ) |
任选(可选) | 发票信息 = (公司名称 + 税号) |
m..n |
界域(次数/值域) | 数量 = 1..999 |
“ ” |
基本数据元素 | 用户ID = “字符串” |
4. 数据字典示例(针对在线购书系统)
// ========== 数据流定义 ==========
1. 数据流名:购书请求
说明:顾客提交的购买图书的信息集合。
来源:外部实体“顾客”
去向:加工“1.1 接收与校验请求”
组成:用户ID + 图书列表 + 收货地址
流量:约500份/天,高峰时段100份/小时。
2. 数据流名:图书列表
组成:1{(图书ID + 数量)}10
说明:一次请求中最多购买10种图书。
// ========== 数据文件定义 ==========
1. 数据文件:F2 用户订单表
简述:存储所有生成订单的详细信息。
输入数据:订单草稿(来自加工1.3)、支付验证结果(来自加工3.0)
输出数据:订单详情(去向加工4.0)
组成:订单号 + 用户ID + 订单状态 + 下单时间 + 图书列表 + 总金额 + 收货地址 + (支付单号) + (发货时间)
存储方式:按订单号索引存储。
存取频率:非常频繁。
// ========== 数据元素定义 ==========
1. 数据元素:订单号
类型:字符串
长度:12位
格式:“DD”-“YYYYMMDD”-“0000”
取值范围:系统自动生成,唯一。
相关文件:F2 用户订单表
2. 数据元素:订单状态
类型:枚举字符串
长度:10
取值:[ “待支付” | “已支付” | “配货中” | “已发货” | “已完成” | “已取消” ]
(五)实体关系图(E-R图)详解与示例
1. 定义与作用
- 描述现实世界中实体及其关系的图示化概念模型,独立于系统实现。
- 核心用于数据库概念设计,是数据库逻辑设计的基础。
2. 核心组成成分
| 成分 | 定义 | 表示形式 |
|---|---|---|
| 实体 | 需维护数据的对象(人、物、事件、概念)。 | 长方形 |
| 属性 | 实体的命名特性。 | 椭圆形(连接实体) |
| 联系 | 实体实例间的关联。 | 菱形框(连接相关实体) |
3. 联系的类型(Cardinality)
- 1:1(一对一):一个A实体关联一个B实体。
- 1:N(一对多):一个A实体关联多个B实体。
- M:N(多对多):多个A实体关联多个B实体。
4. E-R图示例:在线购书系统核心实体关系
erDiagram
CUSTOMER ||--o{ ORDER : "提交"
CUSTOMER {
string customer_id PK "用户ID"
string name "姓名"
string phone "电话"
string address "地址"
}
ORDER ||--|{ ORDER_LINE : "包含"
ORDER {
string order_id PK "订单号"
date order_date "下单日期"
string status "状态"
decimal total_amount "总金额"
}
BOOK ||--o{ ORDER_LINE : "被订购"
BOOK {
string book_id PK "图书ID"
string title "书名"
string author "作者"
decimal price "单价"
int stock "库存量"
}
ORDER_LINE {
int quantity "购买数量"
decimal subtotal "小计"
}
PAYMENT ||--|| ORDER : "支付"
PAYMENT {
string payment_id PK "支付单号"
string method "支付方式"
decimal amount "支付金额"
string status "支付状态"
}
图例说明:
CUSTOMER(顾客)与ORDER(订单)是 1对多 关系:一个顾客可以提交多个订单。ORDER(订单)与ORDER_LINE(订单明细)是 1对多 关系:一个订单包含多条图书购买明细。BOOK(图书)与ORDER_LINE(订单明细)是 1对多 关系:一种图书可以出现在多个订单明细中。ORDER(订单)与PAYMENT(支付记录)是 1对1 关系:一个订单对应一次支付记录。- ORDER_LINE 是一个关联实体,用于解决
ORDER和BOOK之间直接的“多对多”关系,并记录“数量”、“小计”等关联属性。
(六)总结:结构化分析建模流程
- 需求调研:获取用户初始需求。
- 绘制顶层DFD:划定系统范围,识别所有外部实体和输入输出流。
- 逐层细化DFD:
- 分解加工,加入数据存储。
- 始终遵循平衡原则和绘图规则。
- 分解至功能足够简单、明确为止。
- 定义数据字典(DD):
- 对DFD中出现的所有数据流、数据存储、数据元素进行严格定义。
- 使用定义式符号清晰描述数据结构。
- 绘制E-R图:
- 从DFD和数据字典中识别需要持久化存储的核心数据对象(实体)。
- 分析实体间的静态业务关系(联系)。
- 确定联系的种类(1:1, 1:N, M:N)。
- 集成与验证:
- 检查DFD、DD、E-R图之间的一致性。
- 与用户共同评审模型,确保准确反映需求。
- 形成正式的《软件需求规格说明书(SRS)》。
通过 DFD(功能与流程)、DD(数据定义)、E-R图(数据关系) 这三者的有机结合,结构化分析方法能够从不同维度全面、清晰、无歧义地定义软件系统的需求,为后续的系统设计奠定坚实基础。
二、E-R图中联系的类型详解与示例
在E-R图中,联系(Relationship) 是实体之间的连接或关联。根据实体参与联系的方式和数量,主要有三种基本类型:
(一)三种基本联系类型
1. 一对一联系(1:1)
定义:实体集A中的每个实体至多与实体集B中的一个实体相关联,反之亦然。
表示:||---||(双竖线连接)
示例:
erDiagram
STUDENT ||--|| STUDENT_ID_CARD : "拥有"
STUDENT {
string student_id PK
string name
}
STUDENT_ID_CARD {
string card_number PK
date issue_date
date expiry_date
}
业务场景:
- 一个学生只有一张学生证,一张学生证只属于一个学生
- 一个人只有一个身份证号,一个身份证号只对应一个人
- 一个公司只有一个法人代表,一个法人代表只代表一个公司
2. 一对多联系(1:N)
定义:实体集A中的每个实体可以与实体集B中的任意多个实体相关联,但实体集B中的每个实体至多与实体集A中的一个实体相关联。
表示:||--o{(左边双竖线,右边空心圆+大括号)
示例:
erDiagram
DEPARTMENT ||--o{ EMPLOYEE : "包含"
DEPARTMENT {
string dept_id PK
string dept_name
}
EMPLOYEE {
string emp_id PK
string emp_name
string position
}
业务场景:
- 一个部门有多个员工,一个员工只属于一个部门
- 一个客户可以有多个订单,一个订单只属于一个客户
- 一个分类下有多个商品,一个商品只属于一个分类
3. 多对多联系(M:N)
定义:实体集A中的每个实体可以与实体集B中的任意多个实体相关联,反之亦然。
表示:}o--o{(两边都是空心圆+大括号)
示例:
erDiagram
STUDENT }o--o{ COURSE : "选修"
STUDENT {
string student_id PK
string name
string major
}
COURSE {
string course_id PK
string course_name
int credits
}
业务场景:
- 一个学生可以选修多门课程,一门课程可以被多个学生选修
- 一个作者可以写多本书,一本书可以有多个作者
- 一个订单可以包含多种商品,一种商品可以在多个订单中
(二)联系的重要概念
1. 联系的度(Degree)
- 一元联系(递归联系):同一实体集内的实体之间的联系
- 二元联系:两个实体集之间的联系(最常见)
- 多元联系:三个或更多实体集之间的联系
2. 参与约束(Participation Constraints)
- 完全参与:实体集中的每个实体都必须参与联系(用双线表示)
- 部分参与:实体集中的部分实体参与联系(用单线表示)
示例:
erDiagram
DEPARTMENT ||--|{ EMPLOYEE : "管理"
DEPARTMENT {
string dept_id PK
string dept_name
}
EMPLOYEE {
string emp_id PK
string emp_name
}
在这个例子中:
||--表示部门对员工是完全参与(每个部门都有员工)||{表示员工对部门是部分参与(可能有员工不属于任何部门)
(三)在线购书系统完整E-R图示例
erDiagram
CUSTOMER {
string customer_id PK
string name
string email
string address
}
ORDER {
string order_id PK
string customer_id FK
date order_date
decimal total_amount
string status
}
BOOK {
string book_id PK
string title
string isbn
decimal price
int stock
}
ORDER_LINE {
string order_id FK
string book_id FK
int quantity
decimal unit_price
decimal subtotal
}
CATEGORY {
string category_id PK
string name
}
BOOK_CATEGORY {
string book_id FK
string category_id FK
}
AUTHOR {
string author_id PK
string name
string bio
}
BOOK_AUTHOR {
string book_id FK
string author_id FK
string role
}
PAYMENT {
string payment_id PK
string order_id FK
decimal amount
string method
string status
}
SHIPMENT {
string shipment_id PK
string order_id FK
string tracking_no
string carrier
date ship_date
}
WAREHOUSE {
string warehouse_id PK
string name
string location
}
INVENTORY {
string book_id FK
string warehouse_id FK
int quantity
int reorder_level
}
%% 1:1 关系
ORDER ||--|| PAYMENT : "支付"
ORDER ||--|| SHIPMENT : "发货"
%% 1:N 关系
CUSTOMER ||--o{ ORDER : "创建"
CATEGORY ||--o{ BOOK_CATEGORY : "分类"
WAREHOUSE ||--o{ INVENTORY : "存放"
%% 通过关联实体表示的 M:N 关系
BOOK ||--o{ ORDER_LINE : "包含在"
ORDER ||--o{ ORDER_LINE : "包含"
BOOK ||--o{ BOOK_CATEGORY : "属于"
BOOK ||--o{ BOOK_AUTHOR : "由"
AUTHOR ||--o{ BOOK_AUTHOR : "撰写"
BOOK ||--o{ INVENTORY : "存放在"
(四)联系的高级概念
1. 弱实体与强实体
erDiagram
ORDER ||--||ORDER_LINE : "包含"
ORDER {
string order_id PK
date order_date
}
ORDER_LINE {
int line_no PK
int quantity
}
解释:
- 强实体:ORDER,有自己的主键(order_id)
- 弱实体:ORDER_LINE,依赖于ORDER存在,其主键包含父实体的主键(order_id + line_no)
2. 三元联系
erDiagram
DOCTOR }o--o{ PATIENT : "诊治"
DOCTOR }o--o{ MEDICINE : "开具"
PATIENT }o--o{ MEDICINE : "使用"
DOCTOR {
string doctor_id PK
string name
}
PATIENT {
string patient_id PK
string name
}
MEDICINE {
string medicine_id PK
string name
}
PRESCRIPTION {
datetime prescribe_date
string dosage "剂量"
string frequency "频率"
}
解释:医生、病人、药物之间的三元联系,通过处方(PRESCRIPTION)关联实体实现。
3. 递归联系(一元联系)
erDiagram
EMPLOYEE }o--o{ EMPLOYEE : "管理"
EMPLOYEE {
string emp_id PK
string name
string title
}
EMPLOYEE_MANAGEMENT {
string relationship_type "关系类型"
date effective_date "生效日期"
}
解释:员工实体集内部的”管理”联系,表示员工之间的上下级关系。
(五)实际设计中的注意事项
- 多对多联系的分解:在数据库实现中,M:N联系需要通过关联实体转换为两个1:N联系
- 联系属性:当联系本身具有属性时,必须创建关联实体
- 参与度约束:明确是完全参与还是部分参与,影响数据完整性和业务规则
- 基数约束:除了1:1、1:N、M:N,还可以指定具体数量范围(如1..5)
(六)总结
| 联系类型 | 符号表示 | 数据库实现 | 示例 |
|---|---|---|---|
| 1:1 | \|\|---\|\| |
外键放在任意一方,或合并为一张表 | 用户-用户档案 |
| 1:N | \|\|--o{ |
外键放在”多”的一方 | 部门-员工 |
| M:N | }o--o{ |
创建关联表(连接表) | 学生-课程 |
关键设计原则:
- 识别正确的联系类型:仔细分析业务规则
- 避免过度复杂:尽量使用二元联系,谨慎使用三元以上联系
- 考虑性能:联系类型影响查询效率和数据库设计
- 保持一致性:在整个E-R图中使用统一的符号和命名约定