公司动态

UML 类图

📅 2026/8/28 13:25:50
UML 类图
面向对象设计主要就是使用UML的类图类图用于描述系统中所包含的类以及它们之间的相互关系帮助人们简化对系统的理解它是系统分析和设计阶段的重要产物也是系统编码和测试的重要模型依据。下面基于C这门语言给大家讲一下UML类图的画法。类的UML画法类class / struct封装了数据和行为是面向对象的重要组成部分它是具有相同属性、操作、关系的对象集合的总称。在系统中每个类都具有一定的职责职责指的是类要完成什么样子的功能要承担什么样子的义务。一个类可以有多种职责但是设计得好的类一般只有一种职责。比如我现在定义了猎人类无注释版class Hunter { public: int m_age 32; static int m_times; string getName() { return m_name; } void setName(string name) { m_name name; } void goHunting() { aiming(); shoot(); } static void saySorry() { string count to_string(m_times); cout Say sorry to every animal count times! endl; } protected: string m_name Jack; void aiming() { cout 使用 m_gunName 瞄准猎物... endl; } private: string m_gunName AK-47; void shoot() { cout 使用 m_gunName 射击猎物... endl; } }; int Hunter::m_times 3;有注释版/** * * 文件Hunter.cpp * 功能定义猎人(Hunter)类展示C类的核心特性 * 核心考点访问修饰符(public/protected/private)、 * 静态成员变量、静态成员函数、封装 * */ /** * 【猎人】类 * 展示了C类的完整结构公有接口、受保护实现、私有细节 * 面试核心访问控制、静态成员、静态函数的限制 */ class Hunter { public: // 公有成员 /** * 【普通成员变量】年龄 * 公有访问任何地方都可以直接访问 * 面试点公有成员破坏了封装性实际项目中不推荐 * 改进应该设为private通过getter/setter访问 */ int m_age 32; /** * 【静态成员变量】猎杀次数 * 属于类本身所有Hunter对象共享同一个m_times * 必须在类外单独定义初始化 * 面试必问静态成员变量为什么要在类外定义 * 答因为静态成员不属于某个对象属于整个类 * 在类内只是声明需要在类外分配存储空间 */ static int m_times; /** * 【普通成员函数】获取姓名 * 返回受保护的成员变量m_name * 面试点公有成员函数是访问私有/受保护成员的窗口 * 体现了封装性——对外暴露接口隐藏实现 */ string getName() { return m_name; // 可以访问protected成员 } /** * 【普通成员函数】设置姓名 * 参数name - 要设置的名字 * 面试点setter函数可以添加验证逻辑如检查名字是否为空 */ void setName(string name) { m_name name; // 修改protected成员 } /** * 【普通成员函数】去打猎 * 封装了完整的打猎流程瞄准 → 射击 * 面试核心这里体现了【封装】——外部调用者只需调用goHunting() * 不需要知道内部如何瞄准、如何射击 * 设计模式外观模式Facade的简化版 */ void goHunting() { aiming(); // 调用受保护成员函数 shoot(); // 调用私有成员函数 } /** * 【静态成员函数】道歉 * 静态函数属于类不属于对象 * 面试必问静态函数的特点 * 1. 只能访问静态成员m_times * 2. 不能访问非静态成员m_name、m_age等 * 3. 没有this指针 * 4. 可以通过类名直接调用Hunter::saySorry() * 5. 也可以通过对象调用hunter.saySorry()但不推荐 */ static void saySorry() { // ⚠️ 关键静态函数只能访问静态成员 string count to_string(m_times); // ✅ m_times是静态成员 // cout m_name; // ❌ 错误不能访问非静态成员m_name // cout m_age; // ❌ 错误不能访问非静态成员m_age // aiming(); // ❌ 错误不能调用非静态函数 cout Say sorry to every animal count times! endl; } protected: // 受保护成员 /** * 【受保护成员变量】姓名 * protected访问在类内部和派生类中可以访问 * 外部不能直接访问必须通过getter/setter * 面试点protected vs private的区别 * - protected允许子类访问 * - private不允许任何人包括子类访问 */ string m_name Jack; /** * 【受保护成员函数】瞄准 * 受保护函数类内部和派生类可以调用 * 外部不能调用 * 设计意图允许子类重写瞄准行为但外部不可见 */ void aiming() { // 可以访问私有成员m_gunName cout 使用 m_gunName 瞄准猎物... endl; } private: // 私有成员 /** * 【私有成员变量】枪名 * private访问只能在类内部访问 * 外部和子类都无法访问 * 面试点这是封装的核心——隐藏实现细节 */ string m_gunName AK-47; /** * 【私有成员函数】射击 * 私有函数只能在类内部调用 * 外部无法调用子类也无法重写 * 设计意图射击细节完全隐藏外部只知道可以打猎 */ void shoot() { cout 使用 m_gunName 射击猎物... endl; } }; /** * 【静态成员变量定义】 * 必须在类外进行定义和初始化 * 格式类型 类名::变量名 初始值; * 面试必问为什么不能在类内初始化静态成员变量 * 答类内只是声明告诉编译器有这个静态成员存在。 * 类外定义是分配存储空间确保只有一个实例。 * 例外const static int 可以在类内初始化整数类型 */ int Hunter::m_times 3;上面这个类对应的类图应该是这样的可以看到该图分为上中下三部分上层是类名中间层是属性类的成员变量下层是方法类的成员函数。可见性 表示public、# 表示protected、- 表示private、__(下划线)表示static 属性的表示方式【可见性】【属性名称】:【类型】 { 缺省值可选 } 方法的表示方式【可见性】【方法名称】(【参数名 : 参数类型……】:【返回值类型】如果我们定义的类是一个抽象类类中有纯虚函数在画UML类图的时候类名需要使用斜体显示。在使用UML画类图的时候虚函数的表示方式跟随类名也就是使用斜体如果是纯虚函数则需要在最后给函数指定0。类与类之间的关系继承关系继承也叫作泛化Generalization用于描述父子类之间的关系父类又称为基类或者超类子类又称作派生类。在UML中泛化关系用带空心三角形的实线来表示。关于继承关系一共有两种普通继承关系和抽象继承关系但是不论哪一种表示继承关系的线的样式是不变的。假如现在我定义了一个父类Bird和两个子类Cuckoo、Eagleclass Bird { public: string getName() { return m_name; } void setName(string name) { m_name name; } virtual void fly() {} virtual void eat() {} protected: string m_sex; string m_name; }; class Cuckoo : public Bird { public: void fly() override { cout 我拍打翅膀飞行... endl; } void eat() override { cout 我喜欢吃肉肉的小虫子... endl; } }; class Eagle : public Bird { public: void fly() override { cout 我展翅翱翔... endl; } void eat() override { cout 我喜欢吃小动物... endl; } };使用UML表示上述这种关系应当是父类Bird中的fly()和eat()是虚函数它有两个子类Cuckoo和Eagle在这两个子类中重写了父类的虚函数在使用带空心三角的实现表示继承关系的时候有空心三角的一端指向父类另一端连接子类。关联关系关联Assocition关系是类与类之间最常见的一种关系它是一种结构化的关系表示一个对象与另一个对象之间有联系如汽车和轮胎、师傅和徒弟、班级和学生等。在UML类图中用带接头或不带箭头的实线连接有关联关系的类。在C中这种关联关系在类中是这样体现的通常将一个类的对象作为另一个类的成员变量。类之间的关联关系有三种分别是单向关联、双向关联、自关联。下面逐一给大家进行介绍。单向关联关系单向关联指的是关联只有一个方向比如每个孩子Child都拥有一个父亲Parent其代码实现为class Parent { }; class Child { private: Parent m_father; };通过UML来说描述这两个类之间的关系如下图如果是单向关联使用的连接线是带单向箭头的实线, 哪个类作为了当前类的成员变量那么箭头就指向哪个类。在这个例子中 Parent 类 作为了Child 类的成员变量因此箭头端应该指向Parent 类另一端连接 Child 类。双向关联关系现实生活中每个孩子都有父母每个父母同样有自己的孩子如果想要通过类来描述这样的亲情关系代码如下class Parent { private: Child* m_son; }; class Child { private: Parent* m_father; };通过UML来说描述这两个类之间的关系如下图在画UML类图的时候一般使用没有箭头的实线来连接有双向关联关系的两个类这两个类的对象分别作为了对方类的成员变量。有些UML绘图软件使用的是带双向箭头的实线来表示双向关联关系。自关联关系自关联指的就是当前类中包含一个自身类型的对象成员这在链表中非常常见单向链表中都会有一个指向自身节点类型的后继指针成员而双向链表中会包含一个指向自身节点类型的前驱指针和一个指向自身节点类型的后继指针。就以双向链表节点类为例它的C写法为class Node { private: void* m_data; Node* m_prev; Node* m_next; };对应的UML类图应当是一般使用带箭头的实线来描述自关联关系我中有我独角戏。有些UML绘图软件表示类与类的关联关系使用的就是一条实线没有箭头。聚合关系聚合Aggregation关系表示整体与部分的关系。在聚合关系中成员对象是整体的一部分但是成员对象可以脱离整体对象独立存在。在UML中聚合关系用带空心菱形的直线表示下面举两个聚合关系的例子汽车Car与 引擎Engine、轮胎Wheel、车灯Light森林Forest与 植物Plant、动物Animal、水Water、阳光Sunshine以森林为例对应的C类的定义如下class Plant { // 植物 }; class Animal { // 动物 }; class Water { // 水 }; class Sunshine { // 阳光 }; class Forest { public: Forest(Plant p, Animal a, Water w, Sunshine s) : m_plant(p),m_animal(a),m_water(w),m_sun(s) { } private: Plant m_plant; Animal m_animal; Water m_water; Sunshine m_sun; };对应的UML类图为代码实现聚合关系成员对象通常以构造方法、Setter方法的方式注入到整体对象之中因为成员对象可以脱离整体对象独立存在。表示聚合关系的线有空心菱形的一端指向整体对象另一端连接局部对象有些UML绘图软件在这一端还带一个箭头。组合关系组合Composition关系也表示的是一种整体和部分的关系但是在组合关系中整体对象可以控制成员对象的生命周期一旦整体对象不存在成员对象也不存在整体对象和成员对象之间具有同生共死的关系。在UML中组合关系用带实心菱形的直线表示下面举两个组合关系的例子头Head和 嘴巴Mouth、鼻子Nose、耳朵Ear、眼睛Eye树Tree和 树根Root、树干Trunk、树枝Branch、树叶Leaf以树为例对应的C类的定义如下class Root { }; class Trunk { }; class Branch { }; class Leaf { }; class Tree { public: Tree() { m_root new Root; m_trunk new Trunk; m_branch new Branch; m_leaf new Leaf; } ~Tree() { delete m_root; delete m_trunk; delete m_branch; delete m_leaf; } private: Root* m_root; Trunk* m_trunk; Branch* m_branch; Leaf* m_leaf; };其UML的表示方法为代码实现组合关系通常在整体类的构造方法中直接实例化成员类因为组合关系的整体和部分是共生关系整体的实例对象被析构的时候它的子对象也会一并被析构。如果通过外部注入即使整体不存在了部分还是存在的这样的话就变成聚合关系了。依赖关系依赖Dependency关系是一种使用关系特定事物的改变有可能会影响到使用该事物的其他事物在需要表示一个事物使用另一个事物时使用依赖关系大多数情况下依赖关系体现在某个类的方法使用另一个类的对象作为参数。在UML中依赖关系用带箭头的虚线表示由依赖的一方指向被依赖的一方下面举两个依赖关系的例子驾驶员Driver开车需要将车Car对象作为参数传递给 Driver 类的drive方法。class Car { public: void move() {} }; class Driver { public: void drive(Car car) { car.move(); } };树木Tree的生长需要将空气Air、水Water、土壤Soil对象作为参数传递给 Tree 类的 grow方法。class Water { }; class Air { }; class Soil { }; class Tree { public: void grow(Water w, Air a, Soil s) { cout 借助 w 中的水分, s 中的养分和 a 中的二氧化碳, 我就可以茁壮成长了; } };关于树木这个类它对应的UML类图为依赖关系通常通过三种方式来实现1.将一个类的对象作为另一个类中方法的参数2.在一个类的方法中将另一个类的对象作为其对象的局部变量3.在一个类的方法中调用另一个类的静态方法类之间的关系强弱顺序是这样的继承泛化 组合 聚合 关联 依赖。关联关系、聚合关系、组合关系之间的区别从上文可以看出关联关系、聚合关系和组合关系三者之间比较相似最后就来总结一下这三者之间的区别1.关联和聚合的区别主要在于语义上关联的两个对象之间一般是平等的聚合则一般是不平等的。2.聚合和组合的区别则在语义和实现上都有差别组合的两个对象之间生命周期有很大的关联被组合的对象在组合对象创建的同时或者创建之后创建在组合对象销毁之前销毁聚合则无需考虑这些事情。一般来说被组合对象不能脱离组合对象独立存在而且也只能属于一个组合对象聚合则不一样被聚合的对象可以属于多个聚合对象。最后再举例子来描述一下这三种关系1.朋友之间属于关联关系因为这种关系是平等的关联关系只是用于表示两个对象之间的一种简单的联系而已。2.图书馆看书的时候人和书属于聚合关系。书是可以独立存在的而且书不仅可以属于自己也可以属于别人。3.人和自己的心脏属于组合关系因为心脏不能脱离人体而独自存在。不过实际应用中这三种关系的界限划分其实没有那么清楚有些时候我们会感觉组合和聚合没什么区别所以在设计的时候没必要死抠细节只要能够利用对象之间的关系设计出可行的解决方案即可。笔记在做项目之前一般情况下都会对这个项目的需求进行分析然后再根据分析把对应的项目架构设计出来对于项目架构的设计最直观的方式就是通过图表的方式把它呈现出来对于画图比较专业的方式就是通过uml的类图把类和类之间的关系描述出来uml 统一建模语言这个int是静态成员变量这个变量不属于对象属于类意味着我们并没有创建hunter类的对象的时候这个m_times变量仍然是存在的没有创建hunter类的对象的时候这个m_age 变量则不存在存在的因为普通的非静态成员变量是属于对象的静态成员变量属于类的int m_age 32; static int m_times;静态成员函数也是属于类的不属于对象静态成员函数直接通过类名就可以调用而不需要通过对象调用需注意静态成员函数只能够使用静态成员变量非静态成员变量是不能够被直接使用的注意这里说的是不能够直接使用但是可以间接使用通过传参把参数传给函数就可以在函数中使用这个变量。static void saySorry() { string count to_string(m_times); cout Say sorry to every animal count times! endl; }类图分为三个部分第一部分是类名第二部分是类里面的属性属性就是类的成员变量号说明访问权限是public如果有下划线说明是一个静态的成员变量#号说明是受保护的成员变量-号说明私有的成员变量格式为 访问权限 变量名:类型 默认值可选第三部分是成员方法类名是斜体说明这个类是抽象类泛化关系用带空心三角形的实线来表示聚合Aggregation关系表示整体与部分的关系在聚合关系中成员对象是整体的一部分但是成员对象可以脱离整体对象独立存在。在UML中聚合关系用带空心菱形的直线表示部分指向整体组合通过delete一并释放资源