02.UML类图

Posted oldmao_2000

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了02.UML类图相关的知识,希望对你有一定的参考价值。

文章目录

面向过程 vs 面向对象


图片来自百度,侵删。

面向过程面向对象
步骤1打开冰箱冰箱.open()
步骤2放入大象大象.move()
步骤3关上冰箱冰箱.close()
语法动词+宾语主语+谓语
特点强调解决问题的过程步骤强调解决问题过程中主体以及主体的行为

想象一下,如果我们现在要将程序改为把斑马放入冰箱,面向对象可以扩展动物类即可。
面向对象技术是分析问题和解决问题的方法,其基本出发点尽可能按照人类认识世界的方法和思维方式来分析解决问题。
面向对象技术主要支持三种基本的活动:识别对象和类、描述对象和类之间的关系、以及通过描述每个类的功能定义对象的行为。

类是对一组具有相同属性、操作、关系和语义的对象的描述。在UML中,类用矩形来表示,并且该矩形被划分为3个部分:名称部分(Name)、属性部分(Attribute)和操作/方法/函数部分(Operation)
在PD里面选择新建类图,类图可以直接生成对应的编程语言代码,可以看到PD支持Java,C++,C#等。

当然也可以从现有的代码中逆向工程生成类图,只需要通过菜单上的【File】,【Reverse Engineer】,【Object Language】:

当然这个效果估计要打个问号。
建好的类图也可以随时切换目标语言,具体步骤为:点击菜单上【Language】,【Change current object language】,在弹出窗口中选择要切换的语言即可。

下图是PD画出来的一个大象类:

在类的Preview页面可以看到该类对应的代码。

// File:    Elephant.cs
// Author:  mhq
// Created: 2022年8月21日 11:37:07
// Purpose: Definition of Class Elephant

using System;

public class Elephant

   private String name;
   private float weight;
   private int age;
   
   public int forward(float distance)
   
      throw new NotImplementedException();
   


切换为Java:

/***********************************************************************
 * Module:  Elephant.java
 * Author:  mhq
 * Purpose: Defines the Class Elephant
 ***********************************************************************/

import java.util.*;

/** @pdOid c7673b23-4153-47f3-b402-b7865987509c */
public class Elephant 
   /** @pdOid a13bc7b5-a910-4dc9-a522-0eb33ef08dc4 */
   private String name;
   /** @pdOid e2af1492-d0a2-4dbe-b583-4881bf927ded */
   private float weight;
   /** @pdOid 4ca41dce-8c1a-4e25-88ef-26127ffa06b6 */
   private int age;
   
   /** @param distance
    * @pdOid 84d74104-a00d-41a8-a7f2-097a67e4c7dc */
   public int forward(float distance) 
      // TODO: implement
      return 0;
   


在菜单上选择【Language】,【Generate *** Code】可以生成对应语言的代码文件。

继承Generalization

继承指的是一个类(子类)继承另外的一个类(超类)的同一功能,并增加它自己的新功能的能力。要在一个类图上建模继承,可在PD里面选择Generalization,然后从子类(要继承行为的类)拉出一条闭合的、单键头(或三角形)的实线指向超类。例如:

抽象Abstract

抽象类可以在类的属性General页面中勾选:

抽象方法可以在类的属性Operation页面中勾选:

抽象方法只有定义没有实现,必须在子类中实现抽象方法。抽象类也可以有非抽象方法。
下图中,类名Indianelephant使用 abstract \\\\textabstract\\ abstract标识。这说明Indianelephant类是一个抽象类,其中migrate方法是Indianelephant抽象类的操作。Indianelephant规定了migrate抽象操作,它的两个子类 Chinaelephant和Malaysiaelephant分别实现它们各自版本的migrate操作。

关联Association

在PD中,类与类之间是可以有关联关系的。关联使用一根连接类的实线表示,如果有箭头则表示关联的方向,如果不明确指明方向,则表示关联是双向的。例如:

在关联的Detail属性页面,可以对关联进行设置:

几个重要的设置项:
Multiplicity:

表示含义
0…10或1
0…*0到无穷
1…11
1…*1到无穷
*空到无穷
Array size:无穷的上限

单向/双向关联

Navigable:用于设置关联的方向,上图中的employee设置了Navigable,所以employee有箭头,表示对于company而言,其包含的employee信息是透明的,在其生成代码中会有employee的定义。

/***********************************************************************
 * Module:  Company.java
 * Author:  mhq
 * Purpose: Defines the Class Company
 ***********************************************************************/

import java.util.*;

/** @pdOid 8490c5dc-6d2c-46e3-b7d0-4a51e3911128 */
public class Company 
   /** @pdOid ed8fd0fa-c007-40af-8461-a96958ed96f0 */
   private String name;
   
   /** @pdRoleInfo migr=no name=Employee assc=association2 coll=java.util.Collection impl=java.util.HashSet mult=0..* */
   public java.util.Collection<Employee> employee;
   
   
   /** @pdGenerated default getter */
   public java.util.Collection<Employee> getEmployee() 
      if (employee == null)
         employee = new java.util.HashSet<Employee>();
      return employee;
   
   
   /** @pdGenerated default iterator getter */
   public java.util.Iterator getIteratorEmployee() 
      if (employee == null)
         employee = new java.util.HashSet<Employee>();
      return employee.iterator();
   
   
   /** @pdGenerated default setter
     * @param newEmployee */
   public void setEmployee(java.util.Collection<Employee> newEmployee) 
      removeAllEmployee();
      for (java.util.Iterator iter = newEmployee.iterator(); iter.hasNext();)
         addEmployee((Employee)iter.next());
   
   
   /** @pdGenerated default add
     * @param newEmployee */
   public void addEmployee(Employee newEmployee) 
      if (newEmployee == null)
         return;
      if (this.employee == null)
         this.employee = new java.util.HashSet<Employee>();
      if (!this.employee.contains(newEmployee))
         this.employee.add(newEmployee);
   
   
   /** @pdGenerated default remove
     * @param oldEmployee */
   public void removeEmployee(Employee oldEmployee) 
      if (oldEmployee == null)
         return;
      if (this.employee != null)
         if (this.employee.contains(oldEmployee))
            this.employee.remove(oldEmployee);
   
   
   /** @pdGenerated default removeAll */
   public void removeAllEmployee() 
      if (employee != null)
         employee.clear();
   


而employee中的代码没有对应的company信息:

/***********************************************************************
 * Module:  Employee.java
 * Author:  mhq
 * Purpose: Defines the Class Employee
 ***********************************************************************/

import java.util.*;

/** @pdOid 0c9bf268-48a9-4de6-a0c3-75792e6a3366 */
public class Employee 
   /** @pdOid 0cd3c02a-6979-44c1-b08f-34231e70bd9f */
   private String name;
   /** @pdOid ce567eb3-71f0-4db2-899b-07427848c89c */
   private Boolean sex;
   /** @pdOid 2fd0edbe-0056-4479-97ac-98803c817173 */
   private int age;
   
   /** @pdOid 26e945d8-2b3f-433f-9732-96cf078d8f8f */
   public int work() 
      // TODO: implement
      return 0;
   


如果将company的Navigable也勾上,那么就是一条无箭头的实线。

employee代码就会出现company的信息:

/***********************************************************************
 * Module:  Employee.java
 * Author:  mhq
 * Purpose: Defines the Class Employee
 ***********************************************************************/

import java.util.*;

/** @pdOid 0c9bf268-48a9-4de6-a0c3-75792e6a3366 */
public class Employee 
   /** @pdOid 0cd3c02a-6979-44c1-b08f-34231e70bd9f */
   private String name;
   /** @pdOid ce567eb3-71f0-4db2-899b-07427848c89c */
   private Boolean sex;
   /** @pdOid 2fd0edbe-0056-4479-97ac-98803c817173 */
   private int age;
   
   /** @pdRoleInfo migr=no name=Company assc=association2 mult=0..1 side=A */
   public Company company;
   
   /** @pdOid 26e945d8-2b3f-433f-9732-96cf078d8f8f */
   public int work() 
      // TODO: implement
      return 0;
   
   
   
   /** @pdGenerated default parent getter */
   public Company getCompany() 
      return company;
   
   
   /** @pdGenerated default parent setter
     * @param newCompany */
   public void setCompany(Company newCompany) 
      if (this.company == null || !this.company.equals(newCompany))
      
         if (this.company != null)
         
            Company oldCompany = this.company;
            this.company = null;
            oldCompany.removeEmployee(this);
         
         if (newCompany != null)
         
            this.company = newCompany;
            this.company.addEmployee(this);
         
      
   


简而言之就是关联方向与代码生成过程中的变量引用有关系。
除了正常的关联关系外,PD里面还有三种特殊关联关系:

自反关联Reflexive Association

自反关联就是类自身关联自身,例如在员工表中,每个员工有0或1个工头。

聚合关联Aggregation

聚合关联表示“有一个”关系,在整体类一端加上空心菱形箭头表示。例如下图中公司有多个员工,公司即使破产,员工还在,可以跳槽换一个公司,相当于员工聚合成公司。类似还有:班级与学生的关系。

PD中聚合关联可以从工具栏直接选择聚合关联,从聚合主类(整体)指向聚合子类(个体)。

组合关联Composition

组合关联表示组成关系,说明子类实例的生命周期依赖于父类实例的生命周期,父类销毁,子类也将不存在。绘制组合关系的方法同绘制聚合关系的方法基本一样,组合关系用实心菱形箭头表示。例如:房子由墙组成,那么就可使用组合关联,如果房子拆掉,墙也没有了。类似还有:人与器官的关系(通常情况)。

PD中组合关联可以从工具栏直接选择组合关联,从组合主类指向组合子类。

依赖Dependency

UML中的依赖是两个元素(类图、对象图、结构图、用例图、组件图等)之间的关系,对一个元素(提供者)的改变可能会影响或提供消息给其他元素(客户)。也就是说,客户以某种方式依赖于提供者。
从语义上理解,关联、实现和泛化都是依赖关系(关系更紧密),但因为它们有更特别的语义,所以在UML中被分离出来作为独立的关系。
在图形上,UML把依赖描述成一条有向的虚线,指向被依赖的对象。
类图中,依赖就是存在操作上的参数引用关系,如果存在变量的引用,则变成关联关系。

接口

接口是描叙类的部分行为的一组操作,它也是一个类提供给另一个类的一组操作。接口包含操作但不包含属性,并且它没有对外界可见的关联。一个类和它的接口之间的关系叫做实现(implement)。
学过OO的应该知道,C++里面有多继承,而Java里面没有,但是Java可以利用接口来实现类似多继承的功能。
先在PD中创建一个Action接口(interface),并在该接口中添加一个名为Fly的Operation,以及一些属性:

然后创建一个小飞象类(Dumbo),继承自Elephant类:

在工具栏中选择【Realization】,并从Dumbo类指向Action接口,得到实现接口的关系:

此时的Dumbo还没有对应接口的实现代码:

/***********************************************************************
 * Module:  Dumbo.java
 * Author:  mhq
 * Purpose: Defines the Class Dumbo
 ***********************************************************************/

import java.util.*;

/** @pdOid be2b3eb1-010e-432e-9b53-2f5f70033524 */
public class Dumbo extends Elephant implements Action 

需要在Dumbo的【Operation】属性页中,点击下图中的【unimplement operations】按钮

勾选Action接口中的Fly方法,并点击【OK】

可以看到最后结果为:

具体代码为:

/***********************************************************************
 * Module:  Dumbo.java
 * Author:  mhq
 * Purpose: Defines the Class Dumbo
 ***********************************************************************/

import java.util.*;

/** @pdOid be2b3eb1-010e-432e-9b53-2f5f70033524 */
public class Dumbo extends Elephant implements Action 
   /** @param direction 
    * @param distance
    * @pdOid 79542836-dced-4c75-8dc5-c689159925cb */
   public int fly(String direction, Float distance) 
      // TODO: implement
      return 0;
   


补充知识:
A是B,那么A可以继承B(继承强调A是啥)
A可以做B,那么A可以实现B(实现强调A能干啥)
例如:小飞象是象,因此小飞象可以继承大象类,而不是继承鸟类。
小飞象可以飞,因此小飞象可以实现包含飞的接口。

实例-图书管理系统中借阅操作类图

制作在图书借阅过程中读者借阅图书操作的类图设计。按分层的思路进行设计:
实体类,只包含get和set方法
业务类,主要负责处理
数据访问对象类,主要负责数据的读写


思考:为什么不把添加借阅信息操作放到读者类中?
借阅服务与数据访问对象之间关系除了依赖还能是什么?
其他细节:

/***********************************************************************
 * Module:  BorrowService.java
 * Author:  mhq
 * Purpose: Defines the Class BorrowService
 ***********************************************************************/

package borrow;

import java.util.*;

/** 业务类负责业务处理,借阅服务涉及的业务操作为:
 * 1.数据库借阅表中插入一条借阅记录
 * 2.更新Book的借阅状态
 * 
 * @pdOid f3ba46a9-0735-414a-b6ed-2597b9bb729e */
public class BorrowService 
   /** @param reader 
    * @param book
    * @pdOid 8d1c21be-306c-4ccb-99d5-477f5fb07fc1 */
   public int borrow(Reader reader, Book book) 
      // TODO: implement
      BookDao.BSUpdate(book);
      BorrowInfoDao.BIInsert(reader,book);
      return 0;
   


逻辑视图-包图

包是一个容器,提供对UML元素进行分组的功能,主要表现在:把一个大的系统分解为多个小的系统,分解是控制软件复杂性的重要手段;结构化方法中,对功能进行分解:面向对象方法中,可将相关类放在一起。
包中的元素可以有:类、接口、组件、用例、包等。
包中的元素也具有可见性。可分为public、protected、private
在上面的例子中,就用到了包:

从对象浏览器中看是这个样子的:

当然包里面还可以创建包,按照分层思路做一下修改:

当然,还可以加依赖上关系:

将上面创建的类挪到各自的包下面:

注意有快捷方式,例如原理借阅类图变成:

代码也有变化:

/***********************************************************************
 * Module:  BorrowService.java
 * Author:  mhq
 * Purpose: Defines the Class BorrowService
 ***********************************************************************/

package BMS.business;

import BMS.entity.Reader;
import BMS.entity.Book;
import java.util.*;

/** 业务类负责业务处理,借阅服务涉及的业务操作为:
 * 1.数据库借阅表中插入一条借阅记录
 * 2.更新Book的借阅状态
 * 
 * @pdOid f3ba46a9-0735-414a-b6ed-2597b9bb729e */
public class BorrowService 
   /** @param reader 
    * @param book
    * @pdOid 8d1c21be-306c-4ccb-99d5-477f5fb07fc1 */
   public int borrow(Reader reader, Book book) 
      // TODO: implement
      BookDao.BSUpdate(book);
      BorrowInfoDao.BIInsert(reader,book);
      return 0;
   


包里面还可以创建用例图等其他UML的图。

以上是关于02.UML类图的主要内容,如果未能解决你的问题,请参考以下文章

面向对象编程原则(02)——UML与类图

Visual Paradigm创建Java类图时如何绘制实线箭头?

UML类图详解

UML类图详解

UML类图

UML 类图口诀