一文搞懂 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/

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

猜你喜欢

转载自blog.csdn.net/weixin_43412762/article/details/134677819