一文搞懂 UML 类图!!!

面向对象设计 主要就是使用UML的类图,类图用于描述系统中所包含的类以及它们之间的相互关系,帮助人们简化对系统的理解,它是系统分析设计阶段的重要产物,也是系统编码和测试的重要模型依据。

一、UML类图简介

    统一建模语言 UML (Unified Modeling Language) 类图是一种用于描述系统结构的图形化工具。它以类和对象为基础,主要用于表示系统中的类、接口、继承关系、关联关系等元素,以及它们之间的静态结构和关系。在本文中,将深入介绍UML类图的基本元素关系类型以及如何创建一个简单而有效的类图。

    类图以反映类的结构(属性、操作)以及类之间的关系为主要目的,描述了软件系统的结构,是一种静态建模方法。类图用来描述系统中有意义的概念,包括具体的概念、抽象的概念、实现方面的概念等,是对现实世界中事物的抽象

    类图的主要作用是对系统的词汇进行建模、对简单的协作进行建模和对逻辑数据库模式进行建模。类图显示集合的类,接口,关联,协作和约束,它也被称为作为结构图。

UML类图是在 设计程序之前 画,而不是等写完程序再画!!!

在这里插入图片描述

  • Java 中有接口
  • C++ 中没有接口,只有抽象类

二、类的UML画法

使用工具

    Visio 或者 processon 在线作图。
在这里插入图片描述

在类图中一共包含了以下几种模型元素,分别是:Class)、接口Interface)以及类之间的关系

    class / struct)封装了数据行为,是面向对象的重要组成部分,它是具有相同 属性操作关系对象集合总称,是 对具有相同属性和行为的对象的一种抽象表示。在系统中,每个类都具有一定的职责,职责指的是类要完成什么样子的功能,要承担什么样子的义务。一个类可以有多种职责,但是设计得好的类一般只有一种职责。

    接口Interface)是一种特殊的类,具有类的结构但 不可被实例化,只可以被实现继承)。在某种程度上,一个 包含纯虚函数的抽象类 可以被认为是一个接口。然而,严格来说,C++中没有显式的“接口”关键字,而是通过抽象类和纯虚函数的组合 来实现 接口的概念

    比如,我现在定义了猎人类:

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;

    上面这个类对应的类图应该是这样的:
在这里插入图片描述

  • 类名:图中 最上面 的矩形框中为类名。(如果字体为 斜体,表示为 抽象类)
  • 类的属性类名下方的区域。
    • 格式:[可见性] [属性名称] : [类型] = { 默认值,可选 }
  • 类的方法:图中的下面部分
    • 格式:[可见性] [方法名称] ([参数名 : 参数类型, …]) : [返回值类型]

冒号 : 前是 方法名/变量名(根据有无括号区分)
冒号后 :返回参数/变量类型(根据有无括号区分)
如果没有冒号的话表示 方法返回空(也有人通过:void表示返空)

符号解释

  • 说明:属性和方法 前面的 “+” “-” 和 “#” 表示 可见性
    • +public公用的,对 所有类 可见
    • -private私有的只对 该类本身 可用
    • #protected受保护的,对该类的 子孙 可见
    • 不带符号:表示 default
可见性符号当前类当前包子孙类其他包
public+
protected#
default
private-
  • 其他解释说明:
    • ~package包的,只对同一包声明的其他类可见
    • =表示默认值
    • __(下划线):表示static
    • 斜体抽象 (注意也可以用两个尖括号包裹来表示抽象,比如 —— <<我是 抽象类 or 接口 >>)

    如果我们定义的类是一个 抽象类(类中有纯虚函数),在画UML类图的时候,类名需要使用斜体显示

在这里插入图片描述

在使用UML画类图的时候,虚函数 的表示方式跟随类名,也就是使用斜体如果是 纯虚函数 则需要在最后给 函数指定 = 0

三、类与类之间的关系

    UML中的关系是面向对象关系。如果不以面向对象的思维去考虑会感觉到有很多关系认为是一样的。

关系说明表示
继承(泛化)继承关系,指向方(符号左)子类,被指向方(符号右)为父类。在这里插入图片描述
实现接口的实现关系,指向方(符号左)实现类,被指向方(符号右)为接口类。在这里插入图片描述
组合整体和部分关系,指向方(符号左)整体类,被指向方(符号右)为部分类。在这里插入图片描述
聚合整体和部分关系,指向方(符号左)整体类,被指向方(符号右)为部分类。在这里插入图片描述
关联是类与类之间的联结,将一个类的对象(被指向方)作为另一个类(指向方)的属性。在这里插入图片描述
依赖使用关系,指向方(符号左)使用类,被指向方(符号右)为被使用类。在这里插入图片描述

3.1 继承(泛化)关系

    继承 也叫作 泛化Generalization),用于描述父子类之间的关系,父类又称为 基类 或者 超类子类又称作 派生类。在UML中,泛化关系带空心三角形的实线 来表示。

    关于继承关系一共有两种:普通继承关系抽象继承关系,但是不论哪一种表示继承关系的线的样式是不变的

    假如现在我定义了一个父类Bird)和两个子类ParrotEagle):

class Bird
{
public:
    string getName()
    {
        return m_name;
    }
    void setName(string name)
    {
        m_name = name;
    }

    virtual void fly() {}
    virtual void eat() {}
    
protected: //可以被子类继承
    string m_name;
};

class Parrot : 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()虚函数,它有两个子类PorrotEagle在这两个子类中重写了父类的虚函数,在使用 带空心三角形的实线 表示继承关系的时候,有空心三角的一端指向父类,另一端连接子类

3.2 组合关系

    组合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的表示方法为:
在这里插入图片描述

  • 代码实现组合关系,通常 在整体类的 构造方法直接实例化成员类,因为组合关系的整体和部分是共生关系整体的实例对象被析构 的时候它的 子对象也会一并被析构。如果通过外部注入,即使整体不存在了,部分还是存在的,这样的话就变成聚合关系了。

3.3 聚合关系

    聚合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绘图软件在这一端还带一个箭头)。

3.4 关联关系

     关联Assocition)关系是类与类之间最常见的一种关系,它是一种结构化的关系,表示一个对象与另一个对象之间有联系,如汽车和轮胎、师傅和徒弟、班级和学生等。在UML类图中,用 带接头不带箭头 的)实线 连接有关联关系的类。在C++中这种关联关系在类中是这样体现的,通常 将一个类的对象 作为 另一个类的成员变量

    类之间的关联关系有三种,分别是:单向关联双向关联自关联。下面逐一给大家进行介绍。

(a) 单向关联关系

    单向关联指的是关联只有一个方向,比如每个孩子Child)都拥有一个父亲Parent),其代码实现为:

class Parent
{
};

class Child
{
private:
    Parent m_father;
};
  • 通过UML来说描述这两个类之间的关系,如下图:

在这里插入图片描述

  • 如果是单向关联,使用的连接线是 带单向箭头的实线 , 哪个类作为了当前类的成员变量,那么箭头就指向哪个类。在这个例子中 Father 类 作为了Child 类成员变量,因此箭头端应该指向 Father 类 ,另一端连接Child 类
(b) 双向关联关系

    现实生活中每个孩子都有父母,每个父母同样自己的孩子,如果想要通过类来描述这样的亲情关系,代码如下:

class Parent
{
private:
    Child m_son;
};

class Child
{
private:
    Parent m_father;
};
  • 通过UML来说描述这两个类之间的关系,如下图:

在这里插入图片描述

  • 在画UML类图的时候,一般使用 没有箭头的实线 来连接有双向关联关系的两个类,这两个类的对象分别作为了对方类的成员变量。

有些UML绘图软件使用的是 带双向箭头的实线 来表示双向关联关系。

在这里插入图片描述

(c ) 自关联关系

    自关联指的就是当前类中 包含一个自身类型的对象成员,这在 链表 中非常常见,单向链表中都会有一个指向自身节点类型的后继指针成员,而双向链表中会包含一个指向自身节点类型的前驱指针和一个指向自身节点类型的后继指针。就以双向链表节点类为例,它的C++写法为:

class Node 
{
private:
    int m_data = 0;
    Node* m_prev;
    Node* m_next;
};

对应的UML类图应当是:
在这里插入图片描述

  • 一般使用 带箭头的实线 来描述自关联关系,我中有我,独角戏

有些UML绘图软件表示类与类的关联关系,使用的就是一条实线,没有箭头

3.5 依赖关系

    依赖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. 在一个类的方法中 调用 另一个类的 静态方法

类之间的关系强弱顺序是这样的:继承(泛化) > 组合 > 聚合 > 关联 > 依赖

组合、聚合、关联关系之间的区别

  • 组合聚合 的区别则在 语义实现 上都有差别:

    • 组合的两个对象之间生命周期有很大的关联,被组合的对象组合对象创建的 同时或者创建之后 创建在组合对象销毁之前销毁聚合无需考虑这些事情
    • 一般来说 被组合对象 不能脱离 组合对象独立存在,而且也只能属于 一个 组合对象,聚合则不一样,被聚合的对象可以属于 多个 聚合对象
  • 关联聚合 的区别主要在于 语义 上:关联的两个对象之间一般是平等的,聚合则一般是不平等的。


(实际应用中,这三种关系的界限划分其实没有那么清楚,有些时候我们会感觉组合和聚合没什么区别,所以,在设计的时候没必要死抠细节,只要能够利用对象之间的关系设计出可行的解决方案即可。 如果同时有多个关系,只需画出最强的关系即可。)

最后,再举例子来描述一下这三种关系:

  • 人和自己的心脏属于组合关系,因为心脏不能脱离人体而独自存在。
  • 图书馆看书的时候,人和书属于聚合关系。书是可以独立存在的,而且书不仅可以属于自己,也可以属于别人。
  • 朋友之间属于关联关系,因为这种关系是平等的,关联关系只是用于表示两个对象之间的一种简单的联系而已。

注释 使用右上角的 带三角折痕的矩形加虚线 来表示注释。
在这里插入图片描述

参考:https://subingwen.cn/design-patterns/UML-class-diagrams/

注:仅供学习参考,如有不足,欢迎指正!

  • 37
    点赞
  • 111
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Linux内核是一种开源的操作系统内核,是Linux操作系统的核心组成部分。它提供了操作系统与硬件之间的抽象层,负责管理系统的资源、调度任务、提供驱动程序等功能。 Linux内核采用分层的架构,包括硬件抽象层、系统调用层、进程管理层、文件系统层和网络层等。硬件抽象层负责将不同硬件设备的接口统一起来,使得上层的软件可以方便地与硬件进行通信。系统调用层提供了一组API供用户进程调用,如文件操作、网络通信等。进程管理层负责进程的创建、销毁以及调度等任务。文件系统层负责文件的管理和存储。网络层负责网络协议的实现和网络通信。 Linux内核的工作原理可以简单概括为以下几个关键步骤。首先,当一台计算机启动时,BIOS会加载内核映像到内存中,并执行启动代码。然后,内核初始化各种数据结构、驱动程序和关键服务。接下来,内核创建一个初始的用户空间进程,称为init进程。init进程是所有其他进程的祖先进程。在此之后,内核根据调度算法来决定哪个进程可以使用CPU,并依次执行。同时,内核会提供一个中断机制,以便处理硬件事件的优先级。 内核还提供了许多系统调用供用户进程调用,以实现对各种功能的访问。当用户进程需要操作文件、创建进程或进行网络通信时,会通过系统调用将请求传递给内核,由内核代表用户进程执行相应的操作。内核通过调度算法来分配CPU时间片,并通过虚拟内存管理来管理内存资源的分配和回收。 总而言之,Linux内核是一个高度可配置和模块化的操作系统内核,通过分层架构和系统调用机制实现了对硬件的抽象和对用户进程的管理。了解Linux内核的架构和工作原理,有助于深入理解Linux操作系统以及开发和调试相关应用程序。 ### 回答2: Linux是一种开源的操作系统内核,其设计目标是为了在不同的计算机硬件平台上提供高效的、稳定的和安全的操作系统服务。 Linux内核的架构可以分为三个主要部分:进程管理、内存管理和文件系统管理。 在进程管理方面,Linux内核使用了多任务处理技术,可以同时运行多个进程。每个进程都有独立的地址空间和资源,通过调度算法可以合理分配CPU时间片,优化系统的响应速度和资源利用率。 在内存管理方面,Linux内核使用了虚拟内存技术,将物理内存和逻辑内存进行了映射,使得每个进程都有独立的地址空间。当物理内存不足时,Linux内核会通过页面置换算法将暂时不使用的页写入磁盘交换空间,以释放物理内存供其他进程使用。 在文件系统管理方面,Linux内核支持多种文件系统,包括传统的ext3和ext4文件系统,以及现代的Btrfs和XFS文件系统。它负责文件的读写操作,以及文件的权限控制和磁盘空间的管理。 Linux内核的工作原理可以简单概括为以下几个步骤:首先,启动引导程序将内核加载到内存中,并进行初始化。然后,内核分配一部分内存作为内核空间,用于存放内核代码和数据结构。接着,内核根据系统的硬件配置进行设备的初始化和驱动程序的加载。之后,内核根据系统的启动参数和配置文件进行一系列的初始化工作,包括启动系统服务和加载用户程序。最后,内核进入主循环,不断地处理中断、调度进程、管理内存和文件系统,以提供稳定的操作系统服务。 总之,Linux内核是一个复杂而高效的软件系统,它通过进程管理、内存管理和文件系统管理等功能,实现了操作系统的基本功能。了解Linux内核的架构和工作原理,有助于我们更好地理解和使用这个优秀的开源操作系统。 ### 回答3: Linux内核是一个开放源代码的操作系统内核,由一个核心程序和一组通用的系统工具组成。它是Linux操作系统的核心,负责处理硬件设备、管理系统资源、实现进程管理、文件系统和网络功能等。 Linux内核的架构可以分为两个层次:用户空间和内核空间。用户空间包括用户应用程序,如图形界面、终端程序等,它们通过系统调用接口与内核进行通信。内核空间包括内核核心的数据结构和程序,用于管理和控制硬件资源。 Linux内核的工作原理可以概括为以下几个方面: 1. 进程管理:内核负责创建、调度和终止进程。它使用进程描述符(task_struct)来跟踪进程的状态和资源使用情况,并根据调度算法分配CPU时间片给不同的进程。 2. 内存管理:内核负责管理系统的物理内存和虚拟内存。物理内存管理包括内存分配和释放,虚拟内存管理包括页面置换和页面回写等策略,以优化内存的使用效率。 3. 文件系统:内核提供文件系统接口,管理文件和目录的创建、读写和删除等操作。它通过虚拟文件系统层(VFS)将不同的文件系统统一管理,如ext4、NTFS等。 4. 设备驱动:内核提供了访问硬件设备的接口,通过设备驱动程序与硬件交互。不同的硬件设备需要不同的驱动程序,如网卡、显卡、声卡等。 5. 网络功能:内核提供TCP/IP协议栈和网络设备驱动程序,用于实现网络通信功能。它提供网络连接的建立、数据传输和断开等功能,支持各种网络协议,如HTTP、FTP、SSH等。 总的来说,Linux内核是一个非常复杂且功能强大的软件,它负责管理计算机的各种资源和提供操作系统的各种功能。通过深入理解其架构和工作原理,我们可以更好地理解和使用Linux操作系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酷酷的懒虫

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值