效率提升 | UML类图

·  阅读 1313
效率提升 | UML类图

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

前言

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

在经过思考后,我发现合理使用UML图可以提高我们阅读源码的效率,可以快速构建模块之间(类之间)的关系,更好地理解代码。

本篇文章先从类图来介绍UML图。

正文

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

UML图

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

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

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

依赖关系

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

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

//汽车
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

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

Mermaid使用

理解了上面6种关系后,尤其需要从代码角度来理解,还是非常好理解的。然后我们就可以使用工具来画出UML类图,这里具体的工具,我就不推荐了,我这里推荐一个使用markdown编辑器来绘图。

为什么我建议使用markdown呢?原因是使用一些画图工具很容易丢失原图文件,而使用一些MD编辑器绘制的UML图是和文本保存在一块的,不容易丢失也容易再次修改。

现在很多MD编辑器都使用了Mermaid工具,项目地址如下:

github.com/mermaid-js/…

下面我就以掘金的MD为例来介绍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 ..> classBimage.png
关联关系表示拥有的关系,使用在类中表示为成员变量,用实线加箭头表示classA --> classBimage.png
聚合关系关联关系的一种,表示部分和整体的关系,但是部分可以脱离整体存在,用空的菱形加实线和箭头表示classA o--> classBimage.png
组合关系比聚合关系更强一点,表示部分不能脱离整体存在,用实心菱形加实线和箭头表示classA *--> classBimage.png
实现关系表示代码中实现接口的关系,用虚线加三角箭头表示classA ..|> classBimage.png
泛化关系表示代码中的继承关系,用实线加三角箭头表示classA --|> classBimage.png

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

关系添加标签

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

image.png

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

classDiagram
classB <|.. classA:implement

再比如下面代码:

image.png

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

classDiagram
classB <|-- classA:extends

关系的多重性

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

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

符号含义
1仅有一个
0..10个或者1个
1..*1个或者多个
nn个,n>1
0 .. n0到n个,n>1
1 .. n1到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

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

总结

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

分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改