关于UML

关于UML

Scroll Down

概述

UML 是让系统可视化、让规格和设计文档化的表现方法,它是 Unified Modeling Language(统一建模语言)的简称。

本书使用 UML 来表现各种设计模式中类和接口的关系,所以我们在这里稍微了解一下 UML,以方便后面的阅读。但是请大家注意,在说明中我们使用的是 Java 语言的术语。例如讲解时我们会用 Java 中的“字段”(field)取代 UML 中的“属性”(attribute),用 Java 中的“方法”(method)取代 UML 中的“操作”(operation)。

类图

UML 中的类图(Class Diagram)用于表示类、接口、实例等之间相互的静态关系。虽然名字叫作类图,但是图中并不仅仅只有类。

image.png

类与层次结构

Java 程序及其对应的类图。

abstract class ParentClass {
    int field1;
    static char field2;
    abstract void methodA();
    double methodB() {
        // ...
    }
}

class ChildClass extends ParentClass {
    void methodA() {
         // ...
    }
    static void methodC() {
         // ...
    }
}

image.png

该图展示了 ParentClassChildClass 两个类之间的关系,其中的空心箭头表明了两者之间的层次关系。箭头由子类指向父类,换言之,这是表示继承(extends)的箭头。

ParentClassChildClass 的父类,反过来说,ChildClassParentClass 的子类。父类也称为基类或超类,子类也称为派生类。

图中的长方形表示类,长方形内部被横线自上而下分为了如下 3 个区域。

  • 类名
  • 字段名
  • 方法名

有时,图中除了会写出类名、字段名和方法名等信息外,还会写出其他信息(可见性、方法的参数和类型等)。反之,有时图中也会省略所有不必要的项目 ( 因此,我们无法确保一定可以根据类图生成源程序 ) 。

abstract 类(抽象类)的名字以斜体方式显示。例如,在 上图 中 ParentClass 是抽象类,因此它的名字以斜体方式显示。

static 字段(静态字段)的名字带有下划线。例如,在图 上图 中 field2 是静态字段,因此名字带有下划线。

abstract 方法(抽象方法)的名字以斜体方式显示。例如,在图 上图 中 methodA 是抽象方法,因此它以斜体方式显示。

static 方法(静态方法)的名字以下划线显示。例如,在图 上图 中 ChildClass 类的 methodC 是类的静态方法,因此它的名字带有下划线。

接口与实现

image.png

该图表示 PrintClass 类实现了 Printable 接口。空心箭头代表了接口与实现类的关系,箭头从实现类指向接口。换言之,这是表示实现(implements)的箭头。

UML 以 <> 表示 Java 的接口。

interface Printable {
    abstract void print();
    abstract void newPage();
}

class PrintClass implements Printable {
    void print() {
        // ...
    }
    void newPage() {
        // ...
    }
}

聚合

class Color {
    // ...
}

class Fruit {
Color color;
    // ...
}

class Basket {
    Fruit[] fruits;
    // ...
}

image.png

该图展示了 Color(颜色)、Fruit(水果)、Basket(果篮)这 3 个类之间的关系。Basket 类中的 fruits 字段是可以存放 Fruit 类型数据的数组,在一个 Basket 类的实例中可以持有多个 Fruit 类的实例;Fruit 类中的 color 字段是 Color 类型,一个 Fruit 类实例中只能持有一个 Color 类的实例。通俗地说就是在篮子中可以放入多个水果,每个水果都有其自身的颜色。

我们将这种“持有”关系称为聚合(aggregation)。只要在一个类中持有另外一个类的实例——无论是一个还是多个——它们之间就是聚合关系。就程序上而言,无论是使用数组、java.util.Vector 或是其他实现方式,只要在一个类中持有另外一个类的实例,它们之间就是聚合关系。

在 UML 中,我们使用带有空心菱形的实线表示聚合关系,因此可以进行联想记忆,将聚合关系想象为在空心菱形的器皿中装有其他物品。

可见性(访问控制)

class Something {
    private   int privateField;
    protected int protectedField;
    public    int publicField;
    int packageField;
    private   void privateMethod() {
    }
    protected void protectedMethod() {
    }
    public    void publicMethod() {
    void packageMethod() {
    }
}

image.png

该图标识出了方法和字段的可见性。在 UML 中可以通过在方法名和字段名前面加上记号来表示可见性。

“+”表示 public 方法和字段,可以从类外部访问这些方法和字段。

“-”表示 private 方法和字段,无法从类外部访问这些方法和字段。

“#”表示 protect 方法和字段,能够访问这些方法和字段的只能是该类自身、该类的子类以及同一包中的类。

“~”表示只有同一包中的类才能访问的方法和字段。

类的关联

可以在类名前面加上黑三角表示类之间的关联关系

image.png

时序图

UML 的时序图(sequence diagram)用来表示程序在工作时其内部方法的调用顺序,以及事件的发生顺序。

类图中表示的是“不因时间流逝而发生变化的关系(静态关系)”,时序图则与之相反,表示的是“随时间发生变化的东西(动态行为)”。

class Client {
    Server server;
    void work() {
        server.open();
        server.print("Hello");
        server.close();
    }
    // ...
}

class Server {
    Device device;
    void open() {
        // ...
    }
    void print(String s) {
        device.write(s);
        // ...
    }
    void close() {
        // ...
    }
    // ...
}

class Device {
    void write(String s) {
        // ...
    }
}

image.png

右侧是时序图示例,左侧是与之对应的代码片段

该图中共有 3 个实例,如图中最上方的 3 个长方形所示。在长方形内部写有类名,类名跟在冒号(:)之后,并带有下划线,如 :Client:Server:Device,它们分别代表 Client 类、Server 类、Device 类的实例。

如果需要,还可以在冒号(:)之前表示出实例名,如 server:Server

每个实例都带有一条向下延伸的虚线,我们称其为生命线。这里可以理解为时间从上向下流逝,上面是过去,下面是未来。生命线仅存在于实例的生命周期内。

在生命线上,有一些细长的长方形,它们表示实例处于某种活动中。

横方向上有许多箭头,请先看带有 open 字样的箭头。黑色实线箭头(→)表示方法的调用,这里表示 client 调用 serveropen 方法。当 serveropen 方法被调用后,server 实例处于活动中,因此在 open 箭头处画出了一个细长的长方形。

而在 open 箭头画出的长方形下方,还有一条指向 client 实例的虚线箭头(<---),它表示返回 open 方法。在上图中,我们画出了所有的返回箭头,但是有些时序图也会省略返回箭头。

由于程序控制已经返回至 client,所以表示 server 实例处于活动状态的长方形就此结束了。

接着,client 实例会调用 server 实例的 print 方法。不过这次不同的是在 print 方法中,server 会调用 device 实例的 write 方法。

这样,我们就将多个对象之间的行为用图示的方式展示出来了。时序图的阅读顺序是沿着生命线从上至下阅读。然后当遇到箭头时,我们可以顺着箭头所指的方向查看对象间的协作。