类图表示的是类与类之间的关系,此种关系通常用UML(Unified Modeling Language,统一建模语言)表示。类图在软件设计及应用架构前期设计中是不可或缺的一部分,它的主要组成部分包括类名、类方法(也叫成员方法、成员函数)和成员变量,其中类方法包含返回值类型,成员变量包含数据类型,这些组成部分由一个矩形框包围起来。一个UML类图的组成部分的完整表达式如下。
[是否可见] [成员变量名称/类方法名称]:[数据类型/返回值类型] [=默认值(可选)]
其中部分符号的含义如下。
+:可见(public)。
−:自身可见(private)。
#:继承可见(protected)。
因此前面提到的Car类的UML类图如图1-1所示。
▲图1-1 Car类的UML类图
图1-1完整表示了一个UML类图的组成,汽车类名称Car正上居中;虚接口SetDiffTire()返回string类型参数;公有成员函数SetCommonEngine()无返回值;Car(string en)为自身构造函数;公有成员变量engineName的类型是string。
在软件设计或架构设计中,类通常不是单独存在的,如上文提到的DZ类与Car类存在继承关系,这里说的继承是一种泛指的关系。之所以说是泛指关系,是因为类之间的关系根据耦合度由强到弱又分为接口实现关系、继承泛化关系、不可分离组合关系、可分离聚合关系、关联关系和依赖关系。下面分别阐述各个类间关系的UML类图的表示方式。
接口实现关系就是派生类必须重写接口中的所有方法,在UML类图中用“虚线+空心箭头”表示,其中箭头指向基类。
例如,不同品牌计算机的售价不同,用UML类图表示的小米笔记本电脑和华为笔记本电脑各自实现的笔记本电脑类的售价接口如图1-2所示。
▲图1-2 接口实现关系
图1-2说明了接口实现关系的UML类图的组成,基类笔记本电脑Computer包含一个笔记本电脑售价的公有虚方法SalePrice(Computer*sonCom;)小米笔记本电脑类XiaoMiComputer和华为笔记本电脑类HuaWeiComputer均继承基类Computer,并实现各自的具体售价接口SalePrice(Computer *sonCom),返回int类型的笔记本电脑售价参数,完成接口的实现。
注:函数有时也称为方法。
继承泛化关系就是常说的继承关系,派生类继承基类,基类被看作“一般设计”,派生类被看作“特殊设计”,因此继承泛化关系也被看作一般与特殊的关系,在UML类图中用“实线+实心箭头”表示,其中箭头指向基类。
例如,动物类会走路、吃东西和发声,猫类和狗类继承动物类,猫类会爬树而狗类会看门,并且它们发声的方式也不一样。用UML类图表示的动物类、猫类和狗类的关系如图1-3所示。
▲图1-3 继承泛化关系
图1-3说明了继承泛化关系的UML类图的组成,基类动物类Animal含有保护(protected)类型的成员变量animalName和animalColor,走路方法OnFoot()和吃东西方法Eat()是动物的共性,不同的发声方式Say()被定义为虚接口。派生类猫类Cat和狗类Dog继承自动物类Animal,实现具体的Say()方法;并且Cat类有自身独有的爬树方法OnTree(),Dog类有自身独有的看门方法LookDoor(),这样就完成了继承并且实现了扩展的功能。
不可分离组合关系可以用整体和部分之间的关系来解释,部分是不能脱离整体单独存在的。部分对象与整体对象是不可分离的,一旦整体对象析构,部分对象就会随之消失,它们属于同一个生命周期。不可分离组合关系在UML类图中用“实线+实心菱形”表示,其中实心菱形指向整体。
例如,一个人的身体包含脚部、手部和头部等,各个部分实现各自的功能,但各个部分又不能脱离身体而单独存在,用UML类图表示的身体类、脚部类、头部类、手部类的关系如图1-4所示。
▲图1-4 不可分离组合关系
图1-4说明了不可分离组合关系的UML类图的组成,整体身体类Body由私有成员变量头部类对象Head、脚部类对象Feet、手部类对象Hand和私有成员方法ComBody()组成;脚部类Feet实现走路方法Onfoot(),头部类Head实现吃饭方法Eat()和观察世界方法See(),手部类Hand实现操作方法OnHand(。)各个部分对象不能独立于整体对象存在,部分与整体是一种不可分离的组合关系。
注:类对象是类的实例。
可分离聚合关系也可以说成是整体与部分的关系,它与不可分离组合关系的区别是,这种整体与部分是可以分离的,也就是说部分是可以脱离整体单独存在的。UML类图中用“实线+空心菱形”表示这种关系,其中空心菱形指向整体。
例如,一所学校中有教师和学生,教师和学生都是可以作为个体存在的,用UML类图表示的学校类、教师类、学生类的关系如图1-5所示。
图1-5说明了可分离聚合关系的UML类图的组成,学校类School包含私有成员变量教师类对象集合set<Teacher>、学生类对象集合list<Student>,公有成员方法招聘教师方法RecruitedTeacher(set<Teacher>)、学生考试方法ExamStudent(list<Student>)等。教师类Teacher含有教书方法Teach(),学生类Student含有学习方法Study()和玩耍方法Play(。)值得注意的是,部分是可以单独存在的,部分可以脱离整体,这种关系为可分离聚合关系。
▲图1-5 可分离聚合关系
关联关系顾名思义就是一个类与另一个类在对象之间的联系,联系可以是双向的,也可以是单向的。在UML类图中,双向关联关系用没有箭头的实线表示,单向关联关系用“实线+箭头”表示,箭头指向被关联的类。
例如,医生与病人之间的关系,个体与自身手机号、身份证号之间的关系。在代码中,将一个类的对象作为另一个类的成员变量来达到两者关联的目的。
其中,医生与病人双向关联关系的UML类图如图1-6所示。
▲图1-6 双向关联关系
图1-6说明了双向关联关系的UML类图的组成,医生类Doctor和病人类Patient为双向关联,医生类Doctor包含私有成员变量病人类对象集合list<Patient>、医生姓名doctorName和公有类方法医生诊断方法Diagnosis(;)病人类Patient包含私有成员变量医生类对象集合list<Doctor>、病人姓名patientName和公有类方法病人看病方法SeeADoctor();Doctor类和Patient类分别包含对方的类对象作为成员变量,从而实现双向关联关系。
其中,个体与手机号、身份证号单向关联关系的UML类图如图1-7所示。
图1-7说明了单向关联关系的UML类图的组成,个体类People包含手机号类Phone和身份证号类Identity这两个私有成员变量;People类实现个体标志方法IdPeople(),Phone类实现设定手机号方法SetPhoneNum(int* pn),Identity类实现设定身份证号方法SetIdNum(int* in);People类指向Phone类和Identity类,实现了一种单向的关联关系。
▲图1-7 单向关联关系
只有在一个类依赖另一个类中的方法时才存在依赖关系,一般将类作为参数传递,通过对方法的调用实现一个类访问另一个类的功能。在UML类图中,使用带箭头的虚线表示这类关系,箭头指向被依赖的类。
例如,同事之间通过邮件进行工作交流,用UML类图表示的同事类、邮件类的关系如图1-8所示。
▲图1-8 依赖关系
图1-8说明了依赖关系的UML类图的组成,同事类Colleague由私有成员变量同事名称collName、同事工号numId和工作方法Work(Mail mail)组成,其中Work(Mail mail)中的形参是依赖关系实现的关键;邮件类Mail包含私有成员变量邮件主题mailTopic和发送时间sendTime,并且实现发送消息方法TransferMessage();Colleague类中的Work(Mail mail)方法完成对TransferMessage()的调用,Colleague类只有依赖Mail类才能工作。