效率提升 | UML类图

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情

前言

为什么写这篇文章呢?原因是最近在阅读源码相关的知识时,里面涉及的知识点特别多,调用链也非常长,而之前我又没有画类图的习惯,导致走了不少弯路。

所以本章内容,重点来说一下UML关系图,这个东西用好了,可以极大地提高我们阅读、理解代码的效率。

正文

本篇文章从俩个方面来介绍UML类图,首先是UML类图的含义,各种图形表示什么意思,然后再说一下我们如何使用编辑器来绘制UML类L图以及IDE中生成UML类图。

UML图

UML类中的类关系从强到弱可以分为:泛化关系 = 实现关系 > 组合关系 > 聚合关系 > 关联关系 > 依赖关系,关于这个看似简单的东西,但是想记住和理解清楚还是挺麻烦的。

我找了一些资料,问题大致出在这俩种情况:一种是从概念出发,单独理解这几个概念,脱离实际业务,让人晦涩难懂;一种是觉得类型太多,不好记忆,而且图形的虚线、实线、箭头指向等也不好记忆。

我现在从Java代码的角度来仔细分析,让你理解其中含义,记住其中的符号。

依赖关系

依赖关系是所有关系中耦合最小的,它可以描述为"Uses a",即"使用"关系。

这个依赖和我们平时所理解的依赖有一点点不一样,比如下面类:

//汽车
public class Car {

    //引擎
    private Engine engine;

    void installEngine(Engine e){
        this.engine = e;
    }

    //打电话
    void callUp(Phone phone){
        
    }
}
复制代码

按我们正常对依赖的理解,想使Car类完全正常使用,我们需要Engine和Phone的实例,所以Car类就依赖于Engine和Phone类。

但是在UML类图中却不是的,在UML中定义依赖为偶合最小的关系,这里只有Phone为依赖关系。因为这里的Phone是方法的参数,不仅仅是参数,还包括方法中的变量和返回值,Phone的实例在方法执行完就销毁了,就认为和原类Car的关系最小。

从另外一个角度来说,假如我们就只想定义一下依赖关系,即Car依赖于Phone,我们使用UML类图表示如图(虚线加箭头):

classDiagram
Car ..> Phone

这时把上述关系转换成代码后,Car类中并不会有Phone类型的成员变量,这就是关键。

小节:

  1. 依赖关系认为是耦合最小的关系,即被依赖的对象只会是方法的局部变量、参数和返回值,不会是成员变量
  2. 使用虚线加箭头表示,虚线表示关系很浅,耦合少,箭头指向被依赖的类,表示需要这个类。

关联关系

关联关系比依赖关系更进一步,它可以描述为"Has a",即"拥有"关系。

上面依赖关系理解清楚后,关联关系就是一句话:被关联类的实例是本类的成员变量。比如还是上面代码,Engine的实例engine是Car的成员变量,这时Engine和Car的关系就是关联关系。

还是一样逆向思维,假如我们用类图说明Car关联Engine如图(实线加箭头):

classDiagram
Car --> Phone

上述UML图转换成代码后,Car类中就会生成一个Engine类型的成员变量,这是关键。

和依赖关系一起小节:

  1. 关联关系比依赖关系更加强,因为被关联的对象是本类成员变量,没有它,更没法使用。
  2. 从生命周期角度来看,关联关系的类实例和本类的生命周期是一样的,而依赖关系只和其中方法的生命周期一样。
  3. 表示都是线加箭头,箭头的意思就是"找到"的意思,我需要找到这个类,才可以完成我自己的功能,而线的虚实就是关系的强弱

聚合关系和组合关系

聚合关系和组合关系是关联关系的分类,即关联关系可以分为聚合关系和组合关系,其实这俩种关系不用分的如此仔细,在实际使用中就可以用关联关系来表示。

既然提及,就要说一下,组合关系比聚合关系更强,这里的思考角度就要从整体和部分之间的关系来说:

  1. 聚合关系表示整体和部分之间的关系,比如学校和老师这俩种关系密切的类,我定义学校类时可以在其中定义一个老师列表的成员变量,同时定义老师类时也可以定义在职学校的成员变量。

我们可以说这2个类是聚合关系,这里可以把学校看成整体,老师看成整体的一部分,但是老师可以脱离整体(学校)单独存在,他可以选择不在学校里教书,比如私教。

  1. 组合关系比聚合关系更强一步就是上面最后一点不一样,组合从字面意思理解就是是组成的一部分,所以在组合关系中,部分对象不能单独存在,即这个部分不能脱离整体存在

其实这个聚合和组合从抽象概念的意思上容易理解,比如身体和大脑的关系,大脑就无法单独存在。

说完了概念,就回到开头说的问题之一:太过抽象,那在代码中如何看出区别呢?因为聚合和组合都是关联关系的一种,而关联关系在代码中体现就是成员变量,所以这里关键就是成员变量的表现形式。

首先是GooseGroup(雁群)和Goose(大雁)的聚合关系:

//雁群
public class GooseGroup {

    public Goose goose;

    public GooseGroup(Goose goose){
        this.goose = goose;
    }
}
复制代码

用UML图如下:

classDiagram

GooseGroup o--> Goose

class GooseGroup{
    + Goose goose
    GooseGrooup(Goose goose)
}

这里用空心菱形加实线和箭头表示,即在关联关系上更进一步,那这个有什么特点呢?

上面代码中,通过构造函数把大雁传递进来,而大雁是可以单独存在的,这种情况下就是聚合关系。我们接着看一下Goose(大雁)和Wing(翅膀)之间的组合关系:

public class Goose{

    //翅膀
    private Wing wing;

    public Goose(){
        this.wing = new Wing();
    }
}
复制代码

用UML图如下:

classDiagram

Goose *--> Wing

class Goose{
    -Wing wing
    Goose(Wing wing)
}

这里用实心菱形加实线和箭头表示,即在聚合关系上更进一步,那这个有什么特点呢?

从上面代码可以看出,在构造函数中并没有从外面传递翅膀实例进来,这是因为从实际来看翅膀实例是没有意义的,从代码来看,创建Goose实例时就需要在其内部创建必要的成员变量Wing,这个和上面雁群有着本质区别,即Wing对于Goose来说更不可分

实现关系

上面说的类关系都是局部变量(包括方法参数和返回值)和成员变量和其他类的关系,而实现关系就是Java中实现接口的意思,就是该类实现了接口中的抽象方法。

实现关系比较容易理解,直接看下面代码:

//飞翔接口
public interface IFly {

    void fly();

}
复制代码
//实现接口
public class Goose implements IFly{
    @Override
    public void fly() {

    }

}
复制代码

用类图表示如下:

classDiagram
IFly <|.. Goose
class IFly{
    fly();
}

这里使用虚线加三角箭头表示,其中我的理解是这样:前面例子中Car需要Engine实例,所以从Car指向Engine是我需要这个实例,而本例中Goose实例创建也需要IFly类,但是从类角度来说,IFly为Goose的父类,所以这里的三角箭头类似找到父类的含义,而虚线则是比后面泛化的实现关系更弱一点。这里理解,这里面的线段就很好记忆。

泛化关系

泛化其实就是我们平时代码中的继承关系,通过类的继承我们可以大量复用父类的功能,所以泛化关系是所有关系中最强的。这里为什么叫做泛化呢?其实继承是我们从子类的角度来看的,而泛化表示一个通用的模型,他是从父类的角度来看的。

所以泛化的关系更容易理解了:

//大雁继承至动物类
public class Goose  extends Animal{

    private Wing wing;

    public Goose(Wing wing){
        this.wing = wing;
    }
}
复制代码

用类图表示如下:

classDiagram
Animal <|-- Goose

这里用实线加三角箭头来表示,这里我的理解是上面实现关系的更进一步,所以用实线来表示。

编辑器快速绘图

理解了上面6种关系后,其实从代码角度来看还是非常好理解的,我们就可以在写文章时或者记笔记时来使用了,这时我建议的方法是使用自带的mermaid工具,而不是图片,因为图片容易丢失,而文本不容易。

简单使用

以掘金MD为例,首先点击上面工具栏种的类图:

image.png

默认效果如下图:

classDiagram
Animal <|-- Duck
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
Animal : +String gender
Animal: +isMammal()
Animal: +mate()
class Duck{
+String beakColor
+swim()
+quack()
}
class Fish{
-int sizeInFeet
-canEat()
}
class Zebra{
+bool is_wild
+run()
}

而上面的UML图的代码如下:

image.png

所以现在我就来带大家来学习一下这个Mermaid中的类图使用。

Mermaid类图使用

定义类

UML提供了表示一个类信息的机制,包括类的属性和方法,以及一些额外的比如依赖、继承等关系,一个单独的类在UML中被分为3个部分:

  1. 顶部部分包含类的名字,加粗和居中显示,第一个字母大写,还可以包含一些描述类信息的额外注释文本。
  2. 中间部分包含类的属性,他们都是靠左排列,而且属性名第一个字面小写。
  3. 下面部分包含该类可执行的操作,就是指方法,同样靠左排列,小写开头。

比如下面代码:

image.png 或者

image.png

效果如图:

classDiagram
class BankAccount
BankAccount : +String owner
BankAccount : +Bigdecimal owner
BankAccount : +deposit(amount)
BankAccount : +withdrawal(amount)

上面代码非常容易理解,也是使用class关键字来定义类,然后使用{}直接定义属性和方法,或者使用:来定义属性和方法。

可见性修饰符

我们在定义Java类时,可以对类的变量设置可见性修饰符,在Mermaid中,使用下面几个符号来简化可见性修饰符:

  1. + 代表 Public
  2. - 代表 Private
  3. # 代表 Protected
  4. ~ 代表 Package/Internal

这个就不举例了,上面的例子中,都有使用这些符号。

定义关系

类与类之间的关系在前面说类图关系我们已经说过了,相信大家多看几遍,也是很容易记住的,现在我们来看看在Mermaid中是如何表示的。

所有的关系和使用的符号以及效果如下表:

关系类型 描述 使用符号 效果
依赖关系 最弱的关系,表示用的关系,使用在类中的局部变量和返回类型,用虚线加箭头表示 classA ..> classB image.png
关联关系 表示拥有的关系,使用在类中表示为成员变量,用实线加箭头表示 classA --> classB image.png
聚合关系 关联关系的一种,表示部分和整体的关系,但是部分可以脱离整体存在,用空的菱形加实线和箭头表示 classA o--> classB image.png
组合关系 比聚合关系更强一点,表示部分不能脱离整体存在,用实心菱形加实线和箭头表示 classA *--> classB image.png
实现关系 表示代码中实现接口的关系,用虚线加三角箭头表示 classA ..|> classB image.png
泛化关系 表示代码中的继承关系,用实线加三角箭头表示 classA --|> classB image.png

其实把UML图中各种关系搞明白和图示搞明白,在Mermaid中用的符号也非常好理解,比如 -- 代表实线,.. 表示虚线,o 表示空菱形,* 表示实菱形,> 表示箭头,|> 表示三角箭头。

关系添加标签

有时为了更好理解,可以在定义关系时,额外添加说明。比如下面代码:

image.png

这里添加额外说明是实现关系,效果如图:

classDiagram
classB <|.. classA:implement

再比如下面代码:

image.png

这里额外添加说明是继承关系,效果如图:

classDiagram
classB <|-- classA:extends

关系的多重性

这个是啥意思呢?比如前面所说的Car类和Engine类,就是一对一的关系,即一辆车就一辆引擎;但是比如GooseGroup和Goose就是一对多的关系,所以这里可以使用符号来表示这种关系。

使用的符号和含义如下表:

符号 含义
1 仅有一个
0..1 0个或者1个
1..* 1个或者多个
n n个,n>1
0 .. n 0到n个,n>1
1 .. n 1到n个,n>1

比如下面代码:

image.png

效果如下:

classDiagram
Customer "1" --> "*" Ticket
Student "1" --> "1..*" Course
Galaxy --> "many" Star

这里也很容易理解,就不用赘述。

IDE插件

上面我们介绍了使用MD编辑器来快速绘制类图,可以在我们阅读源码时,快速构建各个类之间的关系,除了这种方法外,我们还可以使用Android Studio的插件来生成类图,可以快速帮助我们建立思维联系。

这里推荐使用一个插件:PlantUML,大家可以自己在插件市场中进行安装,安装重启后,我们来看看效果。

比如我这里以MagicIndicator库为例,点击整个Java代码包,右击->PlantUML Parser,经过短暂的解析过程后,就得到了如下的类图:

t-0.png

部分截图:

image.png

这里可以发现生成的类图特别大,但是内容特别全,对于我们看源码有极大帮助,可以快速建立类之间的关系。

总结

关于类图的使用,还需要多使用、多思考,这样才可以在工作、学习中快速总结和提高。

猜你喜欢

转载自juejin.im/post/7130890838658203679