三、UML
www.omg.org/spec/UML/
浅谈UML中常用的9种图 - 知乎 (zhihu.com)
| 统一建模语言(Unified Modeling Language,UML) |
|---|
| UML属于面向对象建模语言,它贯穿于面向对象分析、设计和实现三个阶段。 |
| 就像软件设计师的公共语言,避免歧义。 |
| UML图不仅帮助开发团队在设计阶段进行系统规划,也促进了项目干系人之间的交流,确保软件开发的各个阶段都能保持清晰的理解和一致的期望。 |
分类
| 动态行为图(Dynamic Diagrams) |
|---|
| 行为图关注系统的动态行为,即系统运行时元素间的交互和系统状态的变化。这类图揭示了系统随时间演变的行为特征。 |
| 用例图、时序图、通信图、活动图、状态图、交互概览图。 |
| 静态结构图(Static Diagrams) |
|---|
| 结构图描述系统的静态结构,即在系统运行过程中不随时间改变的部分。这类图主要关注系统的构成元素及其关系,而不涉及元素间的实际交互。 |
| 类图、对象图、包图、组件图、部署图。 |
用例图
| 用例图(Use Case Diagram) |
|---|
| 描述系统或组件的功能需求,展示外部参与者(Actors)与系统之间的交互,以及系统提供的服务(用例)。 |
| 从用户的角度展示系统的功能。 |
1.组成元素
| 参与者(Actor) |
|---|
| 参与者是在系统外部与系统交互的人、系统或设备,它们代表了系统的所有外部用户或交互实体。在图中通常用一个小人图标表示。 |
| 用例(Use Case) |
|---|
| 用例代表系统提供的一个完整功能,是系统对外部参与者提供的一个服务或一组相关服务。它描述了系统如何响应来自参与者的请求,以实现某种特定目标。用例在图中通常用椭圆形表示,并在椭圆内标注用例的名称。 |
| 用例是谓语动词+宾语结构的词组。 |
2.关系
| 关联(Association) |
|---|
| 表示参与者与用例之间的连接,表明参与者可以启动或参与一个用例。 |
| 泛化(Generalization) |
|---|
| 类似于面向对象编程中的继承,一个抽象的、更通用的用例可以有多个更具体的子用例,子用例继承父用例的行为和属性。 |
| 包含(Include) |
|---|
| 表示一个用例包含另一个用例的行为。被包含的用例(也称作“基础用例”)是独立的,可以被一个或多个用例重用。 |
| 当可以从两个或两个以上用例中提取公共行为的时候,应该使用包含关系来表示它们。其中这个提取出来的公共用例称之为抽象用例,而把原始用例称为基本用例和扩展用例。 |
| 虚线箭头+<>字样,箭头指向基础用例。 |
| 扩展(Extend) |
|---|
| 表示一个用例可以在特定条件下扩展另一个用例的行为。扩展用例提供额外的行为,但不是基础用例执行所必需的。 |
| 虚线箭头+<>字样,箭头指向基础用例(即被扩展的用例)。 |
| 吃饭(基础用例) 喝饮料(扩展用例) |
时序图
| 顺序图/序列图/时序图(Sequence Diagram) |
|---|
| 描述对象之间的动态交互,展示消息发送的顺序,以及对象如何按照时间顺序执行操作。 |
| 用于描述对象之间的传递消息的时间顺序(包括发送消息、接收消息、处理消息、返回消息等)。 |
| 时序图描述类系统中类和类之间的交互,它将这些交互建模成消息交换。也可用于系统之间的交互行为。 |
1.组成元素
| 生命线(Lifeline) |
|---|
| 生命线是一条垂直的虚线,表示时序图中的对象在一段时间内的存在。生命线是一个时间线,从时序图的顶部一直延伸到底部,所用的时间取决于交互持续的时间。 |
| 激活块(Activation Block) |
|---|
| 激活代表时序图中的对象执行一项操作的时期。激活表示该对象被占用已完成某个任务,去激活指的是对象处于空闲状态,在等待消息。图中的矩形成为激活条或控制期,对象就是在激活条的顶部被激活的。 |
| 消息(Message) |
|---|
| 1.同步消息(Synchronous Message): 同步消息要求接收方在处理完消息后立即回复。发送方在发送同步消息后会暂停自己的执行,等待接收方的响应(返回消息)。这种消息通常表示直接的方法调用,在时序图中表现为实线箭头。 |
| 2.异步消息(Asynchronous Message): 异步消息允许发送方在发送消息后立即继续执行,无需等待接收方的回复。接收方在接收到异步消息后会在后台处理,完成后可能通过触发事件或发送其他消息来告知结果。异步消息在时序图中常以虚线箭头表示。 |
| 3.自我消息(Self Message): 自我消息表示对象在其内部触发某个操作或事件,消息的发送者和接收者是同一个对象。在时序图中,自我消息的箭头从对象生命线的一个点指向同一生命线上的另一个点。 |
类图
| 类图(Class Diagram) |
|---|
| 表示系统的静态结构,展示类、接口、它们之间的关系(如继承、实现、关联、聚合和组合)以及类的属性和操作。 |
1.组成元素
| 类(Class) |
|---|
| 表示一个实体,通常包含属性(Attributes,也称作成员变量)和操作(Operations,也称作方法或函数)。类名写在顶部,属性和操作分别列在其下,属性和操作通常带有可见性修饰符(如+ public, - private, # protected)。 |
| 接口(Interface) |
|---|
| 定义一组操作的集合,没有属性的具体实现,仅声明方法。接口用一个带有<>标签的矩形框表示。 |
2.关系(Relationships)
Inheritance
| 继承(Inheritance) |
|---|
| 用一个空心三角形和实线表示,子类指向父类,表示“is-a”关系。 |
| 继承:子类视角 泛化:父类视角。 |
Realization
| 实现(Realization) |
|---|
| 类对接口的实现,使用一个空心三角形和虚线,类指向接口。 |
Association
| 关联(Association) |
|---|
| 表示类与类之间的结构关系,可以是单向或多向的,用实线连接两个类,并可标注角色名和多重性(如“1..*”表示一个类可以关联到零个或多个另一个类的实例)。 |
| 关联关系表示一个类的对象与另一个类的对象之间的结构关系,这种关系说明一个类(源类)的实例可以拥有或引用另一个类(目标类)的实例。 |
| 关联关系可以是一对一、一对多、多对一或多对多。 |
Java代码示例来说明关联关系,我们以员工(Employee)和部门(Department)为例,一个员工属于一个部门,而一个部门可以有多个员工,这体现了多对一的关联关系。
class Department {
private String departmentName;
private List<Employee> employees;
public Department(String departmentName) {
this.departmentName = departmentName;
this.employees = new ArrayList<>();
}
public void addEmployee(Employee employee) {
employees.add(employee);
}
}
class Employee {
private String name;
private Department department;
public Employee(String name, Department department) {
this.name = name;
this.department = department;
}
public Department getDepartment() {
return department;
}
}
public class Main {
public static void main(String[] args) {
Department engineering = new Department("Engineering");
Employee alice = new Employee("Alice", engineering);
Employee bob = new Employee("Bob", engineering);
engineering.addEmployee(alice);
engineering.addEmployee(bob);
System.out.println(alice.getName() + " works in " + alice.getDepartment().getDepartmentName());
System.out.println("Employees in " + engineering.getDepartmentName() + ":");
for (Employee emp : engineering.getEmployees()) {
System.out.println("- " + emp.getName());
}
}
}
| 1.多重性(Multiplicity) |
|---|
| 它定义了源类可以有多少个目标类的实例。例如,"一个订单有多个商品",这里的“多个”就体现了多重性。 |
| 关系具有多重性:如“1”(表示有且仅有一个),“0...”(表示0个或者多个 )“0,1”(表示0个或者一个),“n...m”(表示n到 m个都可以),“m...”(表示至少m个)。2..*:2个或多个对象。 |
| 2.导航性(Navigability) |
|---|
| 表示哪个类的对象可以导航到另一个类的对象。即,是否可以从一个类直接访问到与之关联的另一个类的实例。 |
| 如果关联关系中有一端带有箭头,这指向的方向表示可以从该端的类直接导航到另一端的类。没有箭头或者两端都有箭头通常表示这是一个双向关联,双方都可以互相导航。 |
| 3.角色名 |
|---|
| 在关联两端可以命名角色,以明确每个类在关联中的角色或目的。 |
例如,如果有一个Student类与一个Course类之间存在关联,我们可以在关联的两端添加角色名来详细说明这种关系的性质。比如,可以在从Student到Course的关联上标记角色名“enrolledIn”,而在从Course到Student的关联上标记为“students”。这样,即使不看任何额外的文档,阅读类图的人也能立刻明白,一个Student对象“enrolledIn”(注册了)一个或多个Course对象,同时,一个Course对象有多个“students”(学生)。 |
| 4.关联类 |
|---|
| 在复杂情况下,关联本身可能还需要额外的属性和操作来描述其特征,这时会使用关联类来建模。 |
| 例如,考虑学生和课程之间的注册关系,除了知道学生注册了哪些课程外,可能还需要记录注册的时间、成绩等信息,这时就可以引入一个关联类“注册记录”,来存储这些附加信息。 |
| 自身关联(Self-Association) |
|---|
| 自身关联是面向对象设计中的一种特殊类型的关联关系,它发生在同一个类的实例之间。这意味着类的一个对象可以通过某种方式引用同一个类的另一个对象,或者是自己。自身关联体现了类的实例能够相互连接或递归结构的特性。 |
| 1.单向自身关联:一个类的实例有一个或多个指向同类其他实例的引用,但方向是单向的。 |
| 2.双向自身关联:类的实例相互之间都有引用,形成网状结构。 |
| 3.递归关联:自身关联的一种特殊情况,用于描述具有层次结构或递归定义的对象。例如,在树形结构中,一个节点(对象)可以有零个或多个子节点,而子节点也是同一类型的对象。 |
Aggregation
| 聚合(Aggregation) |
|---|
| 一种特殊的关联,表示整体与部分的关系,使用菱形空心箭头指向整体,表示部分可以独立存在。 |
| 部分对象的生命周期并不由整体对象来管理。也就是说,当整体对象已经不存在的时候,部分的对象还是可能继续存在的。比如:一只大雁脱离了雁群,依然是可以继续存活的。 |
import java.util.ArrayList;
import java.util.List;
public class Course {
private String courseName;
public Course(String courseName) {
this.courseName = courseName;
}
public String getCourseName() {
return courseName;
}
}
public class Student {
private String studentName;
private List<Course> courses = new ArrayList<>();
public Student(String studentName) {
this.studentName = studentName;
}
public void registerCourse(Course course) {
courses.add(course);
}
public List<Course> getCourses() {
return courses;
}
public void printStudentInfo() {
System.out.println("Student: " + studentName);
System.out.println("Courses:");
for (Course course : courses) {
System.out.println("- " + course.getCourseName());
}
}
}
public class Main {
public static void main(String[] args) {
Course math = new Course("Mathematics");
Course physics = new Course("Physics");
Student john = new Student("John Doe");
john.registerCourse(math);
john.registerCourse(physics);
john.printStudentInfo();
}
}
Composition
| 组合(Composition) |
|---|
| 比聚合更强烈的部分与整体关系,部分不能脱离整体而存在,同样使用菱形实心箭头指向整体。 |
| 在组合中,部分与整体生命期一致,部分与组合同时创建并同时消亡 。比如:鸟与翅膀的关系。 |
class CPU {
String model;
CPU(String model) {
this.model = model;
}
String getModel() {
return model;
}
}
class Memory {
int capacity;
Memory(int capacity) {
this.capacity = capacity;
}
int getCapacity() {
return capacity;
}
}
class Computer {
private CPU cpu;
private Memory memory;
public Computer(CPU cpu, Memory memory) {
this.cpu = cpu;
this.memory = memory;
}
public void displayConfiguration() {
System.out.println("CPU Model: " + cpu.getModel());
System.out.println("Memory Capacity: " + memory.getCapacity() + "GB");
}
}
public class Main {
public static void main(String[] args) {
CPU computerCpu = new CPU("Intel i7");
Memory computerMemory = new Memory(16);
Computer myComputer = new Computer(computerCpu, computerMemory);
myComputer.displayConfiguration();
}
}
Dependency
| 依赖(Dependency) |
|---|
| 表示一个类使用另一个类的服务,但不拥有它,用虚线带箭头表示。这种关系并不涉及所有权,而是侧重于源类在执行某些操作时需要目标类的协助,比如通过方法参数传递、局部变量引用或静态方法调用等方式。 |
| 只要是在类中用到了对方,那么他们之间就存在依赖关系。如果没有对方,连编译都通过不了。 |
| 所谓依赖关系,就是构造个类的时候,需要依赖其他的类,比如:动物依赖水和氧气。 |
| B 依赖于 A ,也就是说, A 发生变化会影响 B ,但是反过来,当 B 的一个实例被删除,不会影响 A 的实例。 |
public class Engine {
public void start() {
System.out.println("Engine is starting.");
}
}
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void startCar() {
engine.start();
System.out.println("Car is starting.");
}
}
public class Main {
public static void main(String[] args) {
Engine engine = new Engine();
Car car = new Car(engine);
car.startCar();
}
}
根据我最初提到的成员变量引用情况,例子实际上展示了组合关系,同时也体现了依赖关系,因为Car的正常运作依赖于Engine。
class Logger {
void log(String message) {
System.out.println("Logging: " + message);
}
}
class Application {
void performTask() {
Logger logger = new Logger();
logger.log("Task started.");
logger.log("Task completed.");
}
}
public class Main {
public static void main(String[] args) {
Application app = new Application();
app.performTask();
}
}
在上述例子中,Application 类并不持有 Logger 类的实例作为成员变量,而是在 performTask 方法内部创建了一个 Logger 对象并调用了其 log 方法来记录日志。这种设计表明了 Application 类在执行某些操作时依赖于 Logger 类的服务,但并没有拥有一个 Logger 实例,体现了依赖关系的特性。如果将来需要更改日志记录的实现方式,只需修改 performTask 方法内如何实例化和使用 Logger,而无需修改 Application 类的定义,这增加了系统的灵活性和可维护性。
3.附录
public class Employee {
private int id;
private String name;
private Employee manager;
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Employee getManager() {
return manager;
}
public void setManager(Employee manager) {
this.manager = manager;
}
}
public class Main {
public static void main(String[] args) {
Employee john = new Employee(1, "John Doe");
Employee jane = new Employee(2, "Jane Smith");
jane.setManager(john);
}
}
假设我们要表示一个简单的社交网络模型,其中每个人(Person)可以有多个朋友,同时也被这些朋友所认识,这就可以通过双向自身关联来实现。
public class Person {
private String name;
private List<Person> friends;
public Person(String name) {
this.name = name;
this.friends = new ArrayList<>();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Person> getFriends() {
return friends;
}
public void addFriend(Person friend) {
if (!friends.contains(friend)) {
friends.add(friend);
friend.addFriend(this);
}
}
}
public class Main {
public static void main(String[] args) {
Person alice = new Person("Alice");
Person bob = new Person("Bob");
alice.addFriend(bob);
}
}
在这个例子中,Person类通过friends列表实现了与其他Person实例的双向自身关联。当alice添加bob为朋友时,通过addFriend方法的内部逻辑,也会使bob的朋友列表中包含alice,这样就形成了双方都承认的朋友关系,体现了双向自身关联的特点。需要注意的是,在实际应用中,为了避免循环引用导致的问题,特别是在序列化和垃圾回收方面,需要谨慎设计和管理这类关系。
public class TreeNode {
private int value;
private TreeNode leftChild;
private TreeNode rightChild;
public TreeNode(int value) {
this.value = value;
this.leftChild = null;
this.rightChild = null;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public TreeNode getLeftChild() {
return leftChild;
}
public void setLeftChild(TreeNode leftChild) {
this.leftChild = leftChild;
}
public TreeNode getRightChild() {
return rightChild;
}
public void setRightChild(TreeNode rightChild) {
this.rightChild = rightChild;
}
}
public class Main {
public static void main(String[] args) {
TreeNode root = new TreeNode(1);
TreeNode leftNode = new TreeNode(2);
TreeNode rightNode = new TreeNode(3);
root.setLeftChild(leftNode);
root.setRightChild(rightNode);
}
}