UML 类图其实在大一下学习 C++ 的时候,老师有讲过一些。当时我以为无非就是用画图的方式来表示一些类和类之间的关系,感觉好像没啥大用处,就随便看了看,处于一下感觉会了,但是好像又不会的状态。
一转眼,看到别人讲解底层原理的文章,看到好多类,好多接口,一层套一层的,看得算是一个眼花缭乱;在想一想自己,写了这么多代码,搞出来的东西却都是很简单的类,很简单的接口,没有一点复杂度。现在看看,只能说年轻人太年轻了,还需要多多锻炼。
上面的 UML 类图呢,第一眼看上去就感觉好复杂,内心有一种抗拒感觉,但是这种东西不是你抗拒就能拒绝的,如果只会一味的偷懒,那么什么时候才能提升自己呢?俗话说好的,勤能补拙,我没有一个好的脑袋瓜子,只能多动动手,多花一花时间来弥补我的缺漏,所以就借此机会重新学习一下 UML 类图的基础知识和概念。
统一建模语言(英語:Unified Modeling Language,縮寫UML)是非专利的第三代建模和规约语言。UML是一种开放的方法,用于说明、可视化、构建和编写一个正在开发的、面向对象的、软件密集系统的制品的开放方法。UML展现了一系列最佳工程实践,这些最佳实践在对大规模,复杂系统进行建模方面,特别是在软件架构层次已经被验证有效。
类图就是帮助我们简化类的结构,以便我们能更好的理解它们之间的关系。(进而去构建更复杂的系统?我只是想看懂别人画的图就好了。)
为了表示类与类之间的关系,首先需要清楚知道如何表示一个具体的类。先想一想如何写一个类?首先,我们需要一个类名,起一个好的类名是一件很困难的事情;有了类名,就需要确定有哪些属性,毕竟面向对象其中一个特点就是封装。有了属性还不够,还需要确定属性的属性,即它是公开的(public)?私有的(private)?还是受保护的(protected)?有一些编程语言的设计,简化了这个步骤,即通过命名方式来反应其属性,例如约定下划线 “_” 开头的都是私有的属性。
在 UML 中具体类在类图中用矩形框表示,矩形框分为三层:
成员变量以及方法前的访问修饰符用符号来表示:
上面需要注意的地方就是,“Hourly”是一个类的名称,对应在 UML 的矩形图,就位于第一层,知道这点基本是就够了。
有的编程语言里面并没有专有的“interface”用来表示接口,而是通过抽象类来表示一个接口。在 UML 中,表示一个类是抽象的,只需要将类名改成斜体的就行了;表示一个接口,只需要在前面加上 <<interface>> 即可,这两个应该是很好区分的。
使用 UML 来表示类与类之间的关系,才是本篇内容的重点,也是比较实用的一个技能,不然会看不懂别人画的图。(有些作者虽然也画有类的关系图,但是他们其实画的是思维导图)。
一个类如果有一个接口所定义的所有方法,就说这个类实现了这个接口,一般编程语言都会使用“implements”来明确指出,这个类实现了什么接口。
使用“虚线”+“空心三角箭头”来表示“实现关系”,上图中,“MyClass”实现了接口“MyInterface”。
泛化关系(Generalization)是指对象与对象之间的继承关系。如果对象 A 和对象 B 之间的“is a”关系成立,那么二者之间就存在继承关系,对象 B 是父对象,对象 A 是子对象。
在UML类图中,泛化关系用空心三角和实线组成的箭头表示,从子类指向父类。这点应该比较好理解,有一些编程语言是单基础的,因此会简化许多,一般使用“extends”表示继承。
关联关系(Association)是指对象和对象之间的连接,它使一个对象知道另一个对象的属性和方法(持有另一个对象的引用)。如果一个对象的类代码中,包含有另一个对象的引用,那么这两个对象之间就是关联关系。
关联关系有单向关联和双向关联。如果两个对象都知道(即可以调用)对方的公共属性和操作,那么二者就是双向关联。如果只有一个对象知道(即可以调用)另一个对象的公共属性和操作,那么就是单向关联。大多数关联都是单向关联,单向关联关系更容易建立和维护,有助于寻找可重用的类。
在UML图中,双向关联关系用带双箭头的实线或者无箭头的实线双线表示。单向关联用一个带箭头的实线表示,箭头指向被关联的对象,这就是导航性(Navigatity)。
一个对象可以持有其它对象的数组或者集合。在UML中,通过放置多重性(multipicity)表达式在关联线的末端来表示。多重性表达式可以是一个数字、一段范围或者是它们的组合。
多重性允许的表达式示例如下:
关联关系又分为依赖关联、聚合关联和组合关联三种类型。
依赖(Dependency)关系是一种弱关联关系。如果对象 A 用到对象 B,但是和 B 的关系不是太明显的时候,就可以把这种关系看作是依赖关系。如果对象 A 依赖于对象 B,则 A “use a” B。比如驾驶员和汽车的关系,驾驶员使用汽车,二者之间就是依赖关系。
在UML类图中,依赖关系用一个带虚线的箭头表示,由使用方指向被使用方,表示使用方对象持有被使用方对象的引用。
依赖关系在具体代码表现形式为 B 为 A 的构造器或方法中的局部变量、方法或构造器的参数、方法的返回值,或者 A 调用 B 的静态方法。
聚合(Aggregation)是关联关系的一种特例,它体现的是整体与部分的拥有关系,即 “has a” 的关系。此时整体与部分之间是可分离的,它们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享,所以聚合关系也常称为共享关系。例如,公司部门与员工的关系,一个员工可以属于多个部门,一个部门撤消了,员工可以转到其它部门。
在UML图中,聚合关系用空心菱形加实线箭头表示,空心菱形在整体一方,箭头指向部分一方。
组合(Composition)也是关联关系的一种特例,它同样体现整体与部分间的包含关系,即 “contains a” 的关系。但此时整体与部分是不可分的,部分也不能给其它整体共享,作为整体的对象负责部分的对象的生命周期。这种关系比聚合更强,也称为强聚合。如果A组合B,则A需要知道B的生存周期,即可能A负责生成或者释放B,或者A通过某种途径知道B的生成和释放。
例如,人包含头、躯干、四肢,它们的生命周期一致。当人出生时,头、躯干、四肢同时诞生。当人死亡时,作为人体组成部分的头、躯干、四肢同时死亡。
在UML图中,组合关系用实心菱形加实线箭头表示,实心菱形在整体一方,箭头指向部分一方。
聚合和组合关系中的部分对象是整体对象的一个成员变量。但是,在实际应用开发时,两个对象之间的关系到底是聚合还是组合,有时候很难区别。仅从类代码本身是区分不了聚合和组合的。如果一定要区分,那么如果在删除整体对象的时候,必须删掉部分对象,那么就是组合关系,否则可能就是聚合关系。从业务角度上来看,如果作为整体的对象必须要部分对象的参与,才能完成自己的职责,那么二者之间就是组合关系,否则就是聚合关系。
例如,汽车与轮胎,汽车作为整体,轮胎作为部分。如果用在二手车销售业务环境下,二者之间就是聚合关系。因为轮胎作为汽车的一个组成部分,它和汽车可以分别生产以后装配起来使用,但汽车可以换新轮胎,轮胎也可以卸下来给其它汽车使用。如果用在驾驶系统业务环境上,汽车如果没有轮胎,就无法完成行驶任务,二者之间就是一个组合关系。再比如网上书店业务中的订单和订单项之间的关系,如果订单没有订单项,也就无法完成订单的业务,所以二者之间是组合关系。而购物车和商品之间的关系,因为商品的生命周期并不被购物车控制,商品可以被多个购物车共享,因此,二者之间是聚合关系。