本文介绍在面向对象的程序设计中,类的设计是系统设计的基本单元,类的结构的UML视图,学习面向对象的封装性并运用在聊天系统的设计中。
声明:本系列博文为"爱校码.中国"原创博文,版权所有,未经授权,不得转载或抄袭,若发现将依法追责。
在面向对象的程序设计中,类的设计是系统设计的基本单元。在聊天系统中的核心类的设计为该任务的主要内容,类的结构采用UML的形式,分为类名、属性和方法三部分的内容结构,其特点是可视化,能够直接观察分析,有关UML的知识点在本任务的扩展知识点介绍。
在现实世界中,可以用对象来描述事物,例如在超市中的收银员和顾客,他们都可以看作为对象,在计算机程序中可以模拟现实世界中的概念,能够用类似的实体模拟现实世界中的实体。
面向对象的编程对软件开发领域带来了震撼的影响,具有革命性的变革,它对客观世界的事物描述更加形象,由此分析、设计和对系统建模更加简单,可以描述更加抽象的事物和构建更加复杂的大型程序。在前面情景中讲到Java语言是真正的面向对象的编程语言,Java程序内的一切都可以是对象。
面对激烈的商业竞争,越来越多的企业意识到要想在市场上立足,不但需要提高产品的质量,更需要为客户提供高水平的服务,很多企业需要开发“会员管理系统”
为客户提供个性化的优质服务。可以采用面向对象的分析与设计,使用Java进行编程。系统要求如下:
①系统规模:中小规模系统
单机版 或 网络版
②系统定位:
a.应用领域:
商业,餐饮,美容,服务、娱乐、宾馆、酒吧、俱乐部、会所等。
b.会员卡:电话号码或微信二维码
③系统功能 :
a.系统维护管理:
对操作员设置不同的权限,小票打印机设置等。
b.会员资料录入: 手工录入或二维码录入
c.会员资格管理: 普通会员、高级会员
d.会员档案信息管理:
记录会员详细的个性化信息,比如会员的姓名、照片、国籍、手机号码、电子邮件、生日、公司、职位、爱好,以及其他个性化信息等等。
e.客户关系管理:
支持按会员信息中各字段并行或者交叉查询、检索会员信息,系统支持对客户的特殊信息进行定时提醒,提供各种客户沟通手段,比如客户生日,系统会提前一天或几天提醒,然后根据设定自动发送手机短信、电子邮件、微信问候等。
f.会员消费信息管理 :
记录会员所有的消费信息,自动统计分析客户消费特征,帮助了解会员爱好信息,深入提供个性化服务。
g.会员积分管理
h.查询与统计报表
④使用数据库
a.桌面数据库管理系统
hsqldb(Java嵌入式数据库)。
b.大型关系数据库管理:DBMS
Oracle、 MySql等。
针对以上会员管理系统,可以看作是对象的集合,例如,像系统管理员、收银员和会员等都认为是对象,它们按照类
(class)来划分归类。
面向对象的系统是基于类
的,而一个类规整了一系列的具有共同属性
和行为
的对象
,属性
表示类所具有的特性,行为
表示需要由这个类的对象完成的操作。例如,考虑一个类为消费者
(Customer),这个类有姓名(Name)、年龄(Age)、地址(Address)等属性,也有购买商品(Buy)或消费(consume)等行为。因此,每个会员对象是消费者(Customer)类的一个实例,具有这个类的属性和行为。
面向对象系统的构建需要面向对象的分析与设计,主要分为两个阶段:面向对象的分析
(Object Oriented Analysis)和面向对象的设计
(Object Oriented Design)。分析阶段主要考虑业务环境或问题域的系统解决方案。体现用户和系统开发人员对系统的共同理解,通过用例
驱动和表现。设计阶段主要考虑系统的架构
和确定系统功能的类
。
面向对象的分析与设计需要面向对象的建模
,模型有助于形象化的明确说明一个系统的不同部分,也能展示不同部分如何与另一部分的关联,有助于可视化它们的工作。面向对象的建模基于一系列的符号集
和规则
,利于将复杂的问题分解为容易理解和能够实现解决方案的小单元。在本情景的该任务的扩展知识点中,将对建模语言UML
进行讨论。
面向对象编程旨在计算机程序中模拟现实世界中的概念,能够在计算机程序中用类似的实体模拟现实世界中的实体,它是设计和实现软件系统的方法。在 OOP
(Object Oriented Programming) 中,现实世界的所有事物全都被视为对象。
对象
是面向对象编程的核心,是理解面向对象技术的关键,为计算机应用程序提供实用基础。对象是存在的具体实体,具有明确定义的状态和行为。对象表示现实世界中的实体,以完成特定任务。环顾四周,您现在会发现现实世界对象的例子很多:您的狗,您的办公桌,您的电视机,您的自行车等。
现实世界中的对象享有两个特点:它们都具有状态
和行为
。狗
有状态
(名称,颜色,品种,饥饿)和行为
(吠叫,取,摆尾)。自行车
也有状态
(当前档位,当前踏板节奏,当前速度)和行为
(改变齿轮,改变踏板节奏,施加制动)。针对真实世界的对象识别其状态和行为,开始用面向对象编程的角度来思考,这是一个伟大的方式。
现在花一分钟时间,观察一下在附近区域的现实世界对象。对于您所看到的每个对象,问自己两个问题: “这个对象可能是什么状态?”
和“这个对象可能执行什么样的行为?”
。请务必记下您的看法。当您这样做,就会发现,现实世界中的对象变化的复杂性;您的台灯可能仅有两种状态(开和关)和两个可能的行为(打开,关闭)。不过您的收音机
(已过时的产品,在此只是分析)可能有额外的状态(开,关,当前的音量,当前电台)和行为(打开,关闭,增大音量,减小音量,搜索,扫描,调频)。您可能也注意到,一些对象包含其他对象。这些现实世界的观察结果都可以转化为面向对象的编程设计。
软件对象在概念上类似于真实世界的对象:它们也由状态
和相关的行为
组成。对象存储它的状态字段
(在某些编程语言中的变量),并通过方法
(在某些编程语言中的函数)公开其行为。方法操作一个对象的内部状态,并作为对象之间通信的主要机制。隐藏其内部状态,并要求所有的互动,通过一个对象的方法来进行,这被称为数据封装
,也是面向对象程序设计的基本原则
。
对象之间通过传递消息
实现相互通信,而消息
指一个对象为执行某项特定操作而向另一个对象发送的请求。当需要执行一项特定操作时,通过向已为其定义此项操作的对象发送消息
来请求。
在现实世界中,经常会发现许多单个对象具有所有的同一类型
。有可能存在成千上万个自行车,所有的具有相同品牌
和型号
。每个自行车是从相同的蓝图中构建,因此而含有相同的成分。按照面向对象的术语,可以说您的自行车是被称为自行车类
的一个实例。一个类
是创建单个对象的蓝图
。
类
是Java中面向对象编程的基本单元,一个类用于描述客观现实的一个实体,定义实体的共同特性和操作。类是一种模板
,它是一种组合各个对象(类的实例)的所共有行为和属性的机制
,也可以这样说:“类是具有相同行为和属性的一组对象的集合”,它为该类的所有对象提供了统一的抽象描述。
在会员管理系统中会员消费管理的“输入会员消费单信息”
用例中,分析的类有“收银员
(Clerk)”、“消费单控制器
(BillControler)”、“消费单
(Bill)”和“消费品
(Product)”。它们给出了创建各自对象的一种模板,那么,如何设计这种模板,就是类的定义,格式如下:
[类的修饰符] class 类名 [extends 父类名][implements 接口列表]{
[变量修饰符] 类型 变量名1;
[变量修饰符] 类型 变量名2;
[变量修饰符] 类型 变量名n;
[方法修饰符] 类名 ([参数列表]){ // 构造方法
<语句或语句块>;
}
[方法修饰符] 类型 方法名1([参数列表])[throws 异常列表]{
<语句或语句块>;
}
[方法修饰符] 类型 方法名2([参数列表])[throws 异常列表]{
<语句或语句块>;
}
[方法修饰符] 类型 方法名n ([参数列表])[throws 异常列表]{
<语句或语句块>;
}
}
上面类定义的格式中,方括号[]中的内容是可选的,类的修饰符表示[public][abstract|final]
,说明类的含义,关键字extends
表明类的继承关系,关键字implements
表明声明的类实现了接口列表中的接口。
变量的修饰符表示[public | protected | private ] [static] [final] [transient] [volatile]
,说明类的成员变量的含义;方法的修饰符表示[public | protected | private ] [static] [final | abstract] [native] [synchronized]
,说明类的成员方法的含义。关键字throws
表明方法引起的异常类型。
既然类的定义是一种模板声明,利用UML表示更能形象的说明,如图所示。最上层的Bill为类名;第二层的矩形框内的内容为类的属性变量;第三层的矩形框内的内容为类的方法。
用Java的格式表示如下:
public class Bill {
private Integer billNo;
private String product;
private Integer quantityBought;
public void addProductDetail() {
}
public void editProductDetail() {
}
public void deleteProductDetail() {
}
}
再进一步的解释一下类的修饰符,有关变量修饰符和方法修饰符将在后面内容中详细解释。在关键字class
的前面可以由以下修饰符来制定:
public
: 访问控制符,表示声明的类为公共类,可以被任何类引用,这时Java源文件的文件名与类名相同,如果一个Java源文件中包含多个类的定义时,必须有一个而且只能有一个类用public修饰,此类与文件名对应。如果一个类没有public修饰,则默认为friendly,表示该类只能被同一个包中的类引用。abstract
: 类型说明符,表示声明的类为抽象类,不能实例化为对象,同时也说明类中含有抽象方法,有关抽象方法在后续内容中详述。final
: 类型说明符,表示声明的类为最终类。如果是最终类,则该类不能有子类。它通常是完成一个标准功能。以下分别举例说明抽象类和最终类的声明:
① 声明一个抽象类,类名为Employee
(雇员类),包含雇员的基本信息和薪资,以及雇员薪资的计算方法,用UML表示如图所示:其类名、属性和方法位于图中矩形的上层、第二层和第三层。
清单4-1:
1: public abstract class Employee {
2: private String name; // 存储姓名
3: private double salary; // 存储薪资
4: private String address; // 存储地址
5: public Employee() { // 构造方法
6: }
7: /**
8: * 构造方法.
9: * @param myname 传递至构造方法的参数
10: * @param mysalary 传递至构造方法的参数
11: * @param address 传递至构造方法的参数
12: */
13: public Employee(String myname,double mysalary,String myaddress){
14: name = myname;
15: salary = mysalary;
16: address = myaddress;
17: }
18: /**
19: * 计算休假后的净工资的方法.
20: * @return pay 为 double 型
21: * @param leave 为 int 型
22: */
23: public double calculatePay(int leave) {
24: double pay;
25: if (leave <= 5) {
26: pay = (0.25 * salary);
27: }
28: else {
29: pay = (0.5 * salary);
30: }
31: return pay;
32: }
33: /**
34: * 抽象方法声明
35: * @return double 值
36: */
37: public abstract double totalPay();
38:}
② 声明一个最终类,类名为Calculate
(计算类),进行基本的数学计算,用UML表示如图所示:其类名、属性和方法位于图中矩形的上层、第二层和第三层。
清单4-2:
1:public final class Calculate {
2: private double operator1; //操作数1
3: private double operator2; //操作数2
4: private double result; //计算结果
5: private int calculateOperator; //运算符选项
6: /**
7: * 构造方法,初始化属性值
8: */
9: public Calculate() {
10: operator1 = 0;
11: operator2 = 0;
12: result = 0;
13: calculateOperator = 1;
14: }
15: /**
16: * 复位方法
17: */
18: public void clear() {
19: operator1 = 0;
20: operator2 = 0;
21: result = 0;
22: calculateOperator = 1;
23: }
24: /**
25: * 运算方法,为final修饰的最终方法
26: * @return result为 double 型
27: * @param op 为 int 型
28: */
29: public final double calculateResult(int op ) throws ArithmeticException {
30: switch (op){
31: case 1:
32: result = operator1 + operator2;
33: break;
34: case 2:
35: result = operator1 - operator2;
36: break;
37: case 3:
38: result = operator1 * operator2;
39: break;
40: case 4:
41: result = operator1 / operator2;
42: break;
43: case 5:
44: result = Math.sin(operator1);
45: break;
46: case 6:
47: result = Math.cos(operator1);
48: break;
49: case 7:
50: result = Math.tan(operator1);
51: break;
52: }
53: return result;
54: }
55: /**
56: * calculateOperator的GET方法
57: * @return calculateOperator 为int型
58: */
59: public int getCalculateOperator() {
60: return calculateOperator;
61: }
62: /**
63: * calculateOperator的SET方法
64: * @param aCalculateOperator 为 int 型
65: */
66: public void setCalculateOperator(int aCalculateOperator){
67: calculateOperator = aCalculateOperator;
68: }
69: /**
70: * operator1的GET方法
71: * @return operator1 为double型
72: */
73: public double getOperator1() {
74: return operator1;
75: }
76: /**
77: * operator1的SET方法
78: * @param aOperator1为double型
79: */
80: public void setOperator1(double aOperator1) {
81: operator1 = aOperator1;
82: }
83: /**
84: * operator2的GET方法
85: * @return operator2 为double型
86: */
87: public double getOperator2() {
88: return operator2;
89: }
90: /**
91: * operator2的SET方法
92: * @param aOperator2为double型
93: */
94: public void setOperator2(double aOperator2) {
95: operator2 = aOperator2;
96: }
97: /**
98: * result的GET方法
99: * @return result为double型
100: */
101: public double getResult() {
102: return result;
103: }
104: /**
105: * result的SET方法
106: * @param aResult为double型
107: */
108: public void setResult(double aResult) {
109: result = aResult;
110: }
111:}
对象
是类
的实例,是由模板产生的客观世界的具体实体,它与类的关系就像是模具
与铸件
的关系,具有明确定义的状态
和行为
,要使用对象的属性状态和方法行为,必须先由类创建对象,再由对象调用属性
和方法
。类
是概念模型,定义对象的所有特性和所需的操作,类
是对象
的原型,所有属于同一个类的对象都具有相同的特性和操作
。在情景2中,讨论了Java的原始数据类型
和引用数据类型
,可以使用Java的类名作为引用数据类型
,而变量
就代表该类的对象,称变量为对象的引用
。
定义对象的格式如下:
类名 对象名[,对象名,…]
例如:
Bill firstbill, secondbill, thirdbill;
所有对象都是类模板的一个不同备份,只不过带有自己的数据。创建对象是在内存中分配一块区域,把属性和方法放在这个区域中以便调用。创建对象通过new
运算符后面跟类的构造方法
实现,如果类中没有声明,类的默认构造方法名
与类名
相同,没有带参数,有关带参数的类的构造方法将在后续内容中描述,格式如下:
对象名 = new 类名(); 或者 类名 对象名 = new 类名();
例如:
firstbill = new Bill(); 或者 Bill firstbill = new Bill();
这里new
运算符创建一个类的实例并返回对象的引用,变量firstbill只是对象的引用,它并不包含实际的对象,也就是说,对象名是该对象在内存中的地址的标识,如果为同一个对象创建了多个引用,把一个对象引用赋予另一个对象引用,赋予的只是对象的地址。
Bill secondbill=firstbill;
将firstbill赋给secondbill 并没有分配内存或复制对象的任何部分,secondbill只是获得了原对象的地址。有时也可称为对象的句柄
,通常,对象的声明创建的只是对象句柄
,而不是对象本身。例如:
Bill firstbill;
仅仅创建了对象句柄firstbill
,要想创建对象本身,并使句柄与对象连接,必须使用new运算符,例如:
firstbill = new Bill();
当一个对象不再被任何变量引用时,Java将自动收回
该对象占用的内存空间,这种机制就是Java的“垃圾回收”
。垃圾回收器是在系统空闲时由系统自动启动的一个线程,并与用户程序并行运行,它自动扫描对象的动态内存区,对没有被引用的对象进行收集并释放内存。由于垃圾回收器可能在任何时间运行,因此不能准确的知道它在何时启动,有时当对内存要求苛刻时,可通过代码强制运行
垃圾回收器进行清理工作:
System.gc();
System.runFinalization();
属性是在类中表示对象或实体拥有的特性,每个对象的每个属性都拥有其特有的值,属性在类中表示为变量,其名称由类中的所有对象共享。该变量也称为类的成员变量,其声明方式如下:
[public | protected | private ] [static][final] [transient] [volatile] 类型 变量名;
其中,方括号里的内容为可选项,是成员变量的修饰符,它们的含义为:
在UML中,属性的访问控制称为属性可见性,public对应”+”(图标 ),protected对应”#”(图标
),private对应”-”(图标
),默认(package)对应”~”(图标
)。如图所示:图中矩形的第二层内容为类的属性与对应的可见性图标。
Java中可见性的作用范围比较如下:
同一个类 同一个包 不同包的子类 不同包非子类
private *
default * *
protected * * *
public * * * *
访问对象中的属性是通过点(.)运算符进行,基本格式为:
对象名.变量名;
这里对象名是一个对象的引用,而变量名是需要访问的成员变量。下面是访问计算对象的成员变量:
Calculate cal = new Calculate ();
cal.operator1=0;
cal.operator2 =0;
cal.result=0;
由于cal对象的属性变量的修饰符为private,只能通过对象本身的方法对其访问。可在Calculate类内增加一个main方法,将以上代码加在其中就可以访问cal对象的成员变量。有时不是通过main方法访问类内部的成员变量,而是通过类内部的其它方法访问,例如【清单 4-2】中Calculate类的clear()方法:
public void clear() {
operator1 = 0;
operator2 = 0;
result = 0;
calculateOperator = 1;
}
也可以写成:
public void clear() {
this.operator1 = 0;
this.operator2 = 0;
this.result = 0;
this.calculateOperator = 1;
}
以上代码中的this
代表了调用该方法的当前对象。有时采用this可以解决方法中参数名与成员变量名相同的问题,clear()方法可以改写为:
public void clear(int operator1,int operator2,int result,
int calculateOperator ) {
this.operator1 = operator1;
this.operator2 = operator2;
this.result = result;
this.calculateOperator = calculateOperator;
}
需要注意的是当创建了该类的对象后,this
代表本类当前对象,并指向当前对象的地址,通过this后跟圆点操作符,然后是成员变量或成员方法,就可以使用本类当前对象的变量和 方法。但this不能用于静态上下文中
。
良好的编程习惯是在设计类的属性时,将成员变量的访问控制符声明为private
,而且这些成员变量位于类的相同层次的成员方法之前。成员变量声明为private
,是限制对其的访问。将通过关键词private限制对类的成员变量或成员方法的访问称为封装
。
保持类的成员变量的私有性(private),同时有必要提供public方法用于设置和获取private成员变量的值,这种结构有助于对外隐藏类的实现,减少bug,改进程序的可修改性。
有关访问
private成员变量的public方法
,在接下来的知识点的类的方法节中描述。
方法
是类的功能接口,是内置于一个类中的函数
,因而也是内置于实例化的对象中,是对象可以执行的操作
。方法在类中的声明具有与成员变量相同的层次
,声明格式如下:
[public | protected | private ] [static][final | abstract] [native] [synchronized]
返回类型 方法名([参数列表])[throws 异常列表]{
代码语句;
}
其中,方括号里的内容为可选项,返回类型前面的声明
是方法的修饰符,它们的含义为:
new
实例了某个类的对象,也可以通过对象名调用静态方法。需要注意的是静态方法只能调用静态方法,而且只能处理静态变量。final
与abstract
不能同时使用。C、C++
或汇编语言
等,需要通过JNI
(Java Native Interface) 与程序连接。同样,访问对象中的方法是通过点(.)运算符调用,基本格式为:
对象名.方法名(参数列表);
例如:
Bill firstbill = new Bill();
firstbill.addProductDetail();
firstbill.editProductDetail();
firstbill.deleteProductDetail();
在类的方法中,构造方法
是一种特殊方法,它具有与类相同的名字,而且没有返回类型,也包括void(void是无返回值的类型)。构造方法用于在对象创建时初始化对象,其参数用于给类实例化的对象的成员变量赋初值,如果它的参数列表的类型或数目或顺序不同,可形成构造方法的重载。方法重载是在一个类中,方法名相同,而参数列表不同,从而体现不同功能实现。如果在一个类当中,没有声明构造方法,那末在编译时,编译器自动为其产生一个默认的无参数的空的构造方法,但声明了构造方法时,编译器不再为类自动生成默认的构造方法。因此,通常的做法是在类中声明带参数的构造方法时,也同时声明不带参数的构造方法。当类中声明重载多个构造方法时,在构造方法中可以使用this加圆点操作符调用其它的构造方法,但this语句必须作为第一行语句。以下是【清单4-1】中类Employee的构造方法声明的代码片断:
5: public Employee() { // 构造方法
6: }
7: /**
8: * 构造方法.
9: * @param myname 传递至构造方法的参数
10: * @param mysalary 传递至构造方法的参数
11: * @param address 传递至构造方法的参数
12: */
13: public Employee(String myname,double mysalary,String myaddress){
14: name = myname;
15: salary = mysalary;
16: address = myaddress;
17: }
实现对private
成员变量的访问的public
方法,类提供一个“get”
方法,也称为获取器方法(accessor method), 用于读取private成员变量的值;类也提供一个“set”
方法,也称为设置器方法(mutator method),用于修改private成员变量的值。【清单4-2】中的类Calculate的成员变量operator1
、operator2
、result
和calculateOperator
声明为private
,对应的get与set方法为getOperator1()
、setOperator1()
、getOperator2()
、setOperator2()
、getResult()
、setResult()
、getCalculateOperator()
和setCalculateOperator()
,其中,get和set后的首字母
要大写,get方法
需要返回一个数据类型的值,set方法
需要带数据类型的参数。以下是代码片断:
55: /**
56: * calculateOperator的GET方法
57: * @return calculateOperator 为int型
58: */
59: public int getCalculateOperator() {
60: return calculateOperator;
61: }
62: /**
63: * calculateOperator的SET方法
64: * @param aCalculateOperator 为 int 型
65: */
66: public void setCalculateOperator(int aCalculateOperator){
67: calculateOperator = aCalculateOperator;
68: }
69: /**
70: * operator1的GET方法
71: * @return operator1 为double型
72: */
73: public double getOperator1() {
74: return operator1;
75: }
76: /**
77: * operator1的SET方法
78: * @param aOperator1为double型
79: */
80: public void setOperator1(double aOperator1) {
81: operator1 = aOperator1;
82: }
83: /**
84: * operator2的GET方法
85: * @return operator2 为double型
86: */
87: public double getOperator2() {
88: return operator2;
89: }
90: /**
91: * operator2的SET方法
92: * @param aOperator2为double型
93: */
94: public void setOperator2(double aOperator2) {
95: operator2 = aOperator2;
96: }
97: /**
98: * result的GET方法
99: * @return result为double型
100: */
101: public double getResult() {
102: return result;
103: }
104: /**
105: * result的SET方法
106: * @param aResult为double型
107: */
108: public void setResult(double aResult) {
109: result = aResult;
110: }
在前面介绍了类的属性和类的方法,它们是类的成员。其可见性是由[public | protected | private ] 修饰符进行界定,根据作用域范围的不同,类的成员可以采用对应的修饰符来声明。如果针对类的实例(对象)的成员进行访问,超出了其声明的作用域范围,即实现了信息的隐藏。将隐藏属性、方法或实现细节的过程称为封装。将方法和属性一起包装到一个单元中,单元以类的形式实现。
在情景2和情境3的任务中涉及聊天系统的类有三个,它们分别为ChatMessage
、Client
和Server
,对应的程序代码为[清单2-3
、清单3-1
与清单3-2
]。在该子任务中,分别对其类结构进行分析。
⑴ ChatMessage类设计:
类结构如图所示:
在图所示的ChatMessage
类结构中,其顶层矩形栏内为类名
,第二层矩形栏内为属性
,第三层矩形栏内为方法
。ChatMessage类的实例用于封装聊天系统中不同类型的信息,其中在属性定义中,WHOISIN
、MESSAGE
、LOGOUT
为类定义的常量,它们的修饰限定词为public static final
,数据类型为int
,并且分别赋值为0
(信息类型表示已连接的用户列表)、1
(信息类型表示普通信息)和2
(信息类型表示注销断开连接);在图所示的UML类属性栏中显示为下划线。在属性栏中,还有type
和message
两个成员属性变量,它们的修饰限定词private
,数据类型分别为int
和String
。type
变量用于存储信息类型的具体值,而message
变量用于存储信息字符串。
在图所示的UML类的方法栏中,ChatMessage()
为类的构造方法,用于实例化对象并初始化;getType()
和getMessage()
为类的成员方法,分别用于获取类型值和获取信息内容。在方法栏的方法中,所用限定词为public
。
⑵ Client类设计:
类结构如图所示:顶层矩形为类名栏,第二层矩形为属性栏,第三层矩形为方法栏。
在图所示的Client
类结构中,Client类的实例用于封装客户端的相关信息状态,并具备发送信息和在客户端界面显示信息等行为方法功能。
在类结构的属性栏中,sInput
和sOutput
成员变量定义为ObjectOutputStream
类的对象变量,其数据类型为ObjectOutputStream
类的引用数据类型,用于读写对象流,关于流的知识点的讨论将在后续情境10中介绍;socket
成员变量定义为Socket
类的对象变量,用于网络数据通信,关于网络通信和套接字等知识点的介绍将在情境11中进行;另外,定义了三个成员变量,它们分别是server
、username
和port
,用于存储服务器端地址、用户账号名和服务器端口号的数据信息。
在类结钩的方法栏中,Client()
为类实例化的构造方法;start()
成员方法用于聊天对话的开始,连接到服务器端并创建输入和输出的流对象,开始与服务器进行交互;display()
成员方法用于在界面显示一个信息;sendMessage()
成员方法用于传递一条信息到服务器端;disconnect()
成员方法用于断开连接,关闭输入、输出流。在方法栏最下面的main()
方法为程序入口方法,从此运行客户端。
⑶ Server类设计:
类结构如图所示:顶层矩形为类名栏,第二层矩形为属性栏,第三层矩形为方法栏。
在图所示的Server
类结构中, Server类的实例用于封装服务器端的相关信息状态,并具备侦听客户端的连接请求,并广播信息到所有已连接客户端等行为方法功能。
在类结构的属性栏中,uniqueId
是静态变量,用于为每个连接定义的唯一ID
;clientList
是ArrayLis
t类的对象变量,用于定义一个保持客户端列表的动态数组;sdf
是SimpleDateFormat
类的对象变量,用于定义显示时间日期的格式;port
变量用于存储服务器端提供侦听连接的端口号状态信息;keepGoing
变量是用于定义一个判断服务器端程序停止的布尔变量。
在类结钩的方法栏中,Server()
为类实例化的构造方法;start()
方法用于创建服务器套接字并等待连接请求;display()
方法用于在界面上显示对应一个事件;broadcast()
方法用于广播一条信息到所有的客户端;remove()
方法用于针对一个客户端,使用注销类型的信息完成注销任务;main()
方法为程序入口方法,从此运行服务器端,并判断端口参数。
聊天系统的对象之间的交互,针对Client
和Server
类的实例之间的交互,通过UML时序图能够清晰的表达,如图所示:关于时序图的了解,可参见扩展知识点的介绍。
在图中的对象client
和server
能够很清晰的体现出与类Client
和Server
之间的关系。冒号之前为对象
,冒号之后为对应的类
。当客户端对象与服务器端对象交互时,对象之间通过传递消息
实现相互通信,图中的消息编号1
和2
、3
和4
以及5
和6
分别表示三对同步消息。1
和2
用于请求连接
与响应
;3
和4
用于聊天信息的发送
与广播
;5
和6
用于客户的注销
与响应
。
UML
(Unified Modeling Language)就是一种建模语言,能够在软件工程的不同阶段为一个系统定义不同的模型。
UML是对不同复杂度的软件系统建模的一种标准建模语言,提供了用于任何类型系统的一组表示法和规则,关键是能够创建简单、文档化好的和容易理解的软件模型。UML不但易于使用、表达能力强以及可视化建模,而且独立于过程和编程语言。
UML用于以形象定义的符号可视化软件系统,明确说明软件系统,以帮助构建精确的、无歧义的和完整的模型。还可构建与不同的编程语言直接转换的软件系统模型。通过表达系统的需求,能使软件系统的模型文档化。
UML能够生成表示任何系统的蓝图,这些蓝图以不同的视角观察,表现出不同的可视化视图,如图所示将这些视图列在了一起。
⑴用例视图
用例视图表示以用户的视角观察系统,代表了用户与系统的交互,表示了不同用户的系统需求,有两个概念必须明确:
系统需求是由用例驱动的,用例是由系统内的元素组成的,负责系统的功能和行为。代表所有可能的角色与系统交互时发生的事件。而用例和角色之间的逻辑连接表示一种单向关联关系,如图所示:
一个系统的用例视图由用例图
组成,而一个用例图
包含角色
、用例
和它们之间的关系
。用例图表示了系统对外部实体提供的功能。考察会员管理系统
中会员消费管理
,其中有以下用例:
绘制的用例图
如图所示:
考察会员管理系统的商品项目中的库存管理
,其中有以下角色和用例:
仓库管理员
,商品采购员
,供应商
。发送采购项目申请
:由仓库管理员提出申请,并将其数量发送到采购部。下定单
:采购部识别合适的供应商并将购物定单发送给它们。供给定购商品
:供应商提供所订购的商品项目给采购部。递交采购项目
:定购商品到达时,采购部将其给仓库管理员。绘制用例图如图所示:
⑵ 结构视图
结构视图又称为设计视图
,代表系统的静态方面,包含类图
和对象图
。类图用于描述用例图中的系统功能的类和它们之间的关系。对象图是类图在某一时刻的快照或实例,描述不同的对象和它们之间的链接。有关类和对象的概念还会在本情景后续内容中详细讨论。类用于描述客观现实的实体,实体的具体实例就是对象。类具有不同的类型,分别有下列三种:
考察会员管理系统中会员消费管理的“输入会员消费单信息
”用例,可以映射的类为“收银员
(Clerk)”、“帐单控制器
(BillControler)”、“帐单
(Bill)”和“商品
(Product)”。用类图
展示它们及其关系,如图所示:
⑶ 行为视图
行为视图表现系统模型的动态方面。包括时序图、协作图、状态图和活动图。以下是它们的动态方面:
考察“输入会员消费单信息
”用例、“结帐
”用例和“打印帐单
”用例的对象,其时序图如图所示:对象有收银员
(Clerk)、帐单控制器
(BillControler)、总数计算器
(SumCounter)、帐单打印机
(BillPrinter)。
时序图
可以通过工具转换为协作图
,如图所示:
状态图
体现的是对象的状态与转换,对象从一种状态到另一种状态的转换,需要满足某些条件、执行某一活动或等待某一事件的出现,是由外部实体驱动的。考察会员消费的消费单对象
,从会员消费开始到结束,消费单的状态有“开始”、“消费项目”、“消费总数”、“打印”、“结束”。如图所示:
活动图
表示在一个进程中的步骤或任务,类似于流程图
,描述对象对内部事件流
,而不是对外部事件的响应。如图所示:
⑷ 实现视图与环境视图
实现视图
又称组件视图
,描述系统实现的不同,根据编程语言的不同而不同,涉及源代码结构、运行时的实现结构和软件发布的配置管理等。实现视图通过组件图
来体现,组件图中的组件是可以跨越用例使用的类的映射,表现的是一种软件结构。考察用Java语言实现帐单
(Bill)类和商品
(Product)类的组件图如图所示:
环境视图
又称部署视图
,描述系统中的组件的物理分发,通过部署图
来体现,包含网络设计的节点。如图所示:
UML基于面向对象的思想,采用面向对象的概念和规范。为实现可视化建模,其定义了每一个模型元素的特定图形符号,称为UML图素
。分为四种类型,分别为基本图素、关系图素、模型型图素和行为模型图素。以下分别介绍:
⑴ 基本图素
基本图素包括角色、用例、包、类、对象、接口、协作、组件、节点等。
角色(参与者):有业务角色、业务工人、和角色三种类型。业务角色和业务工人用于业务机构中的业务模型中,业务角色(业务参与者)代表了业务机构之外与之交互的事物,它可以是客户或供应商等,用图(a)所示的图标表示;业务工人代表了业务机构内的业务中的角色及其交互方式,用图(b) 所示的图标表示。角色(参与者)用于系统模型中描述与系统功能有关的外部实体,它可以是用户,也可以是外部系统,用图(c) 所示的图标表示。
用例:有业务用例和系统用例两种类型。业务用例是业务机构中的一组相关业务工作流,也就是业务机构中的某个部门的相关业务中的一个或几个业务工作流程,用图(a)所示的图标表示;系统用例用于表示所建模(开发)的系统的一项外部功能需求,即从用户的角度分析所得的功能需求,用图(b)所示的图标表示。
包:当UML描述一个大型的复杂业务或复杂系统时,面对处理大量的模型元素,这时需要把相同类型的模型元素放在一起。包就是对具有共性的模型元素进行组织的通用机制,把语义相近的模型元素放在同一个包里,表示业务机构中的一个部门或系统中的一个系统框架、一个系统模型、一个子系统等。用图所示的图标表示。
类:在面向对象技术中,类是一个基本的概念,是指一类或一组具有类似属性和相同行为的事物。类将信息和影响信息的行为包装在一起,信息是类的属性,行为是类的方法,用图(a)所示的图标表示,矩形图标被分为水平的3个区域,最上面的区域是类名
,包括版型,版型表示类的不同类型;中间区域是类的属性
;最下面的区域是类的操作方法
。有时为了简化类的表示,可以隐藏类的属性和方法。提到类的版型的概念,它可以描述类的各种类型,有助于分析系统。通常根据系统的功能,把类分成三种基本版型类,即边界类、控制类和实体类,用图(b)、(c)、(d)所示的图标表示。 在类的属性
前面的“”和操作
方法
前面的“”的含义是代表UML 元素的可见性图标,对应于私有属性(
Private
)和公共方法(Public
), 其它可见性图标参见前面相关知识点中的类的属性
和类的方法
介绍。
对象:对象是类的实例,是具有具体属性值和行为的一个具体的事物,用来构造实际运行系统的个体。对象可以是它的类的直接实例,也是那个类的父类的间接实例。对象用图所示的图标表示,与类不同的是名称部分下面要带下划线,名称部分由对象名:所属类名构成。
接口:接口是一种特殊的抽象类,它只有对操作方法的定义说明,而没有操作方法的实现,并且不包含属性。接口的作用是将定义操作方法的接口与实现该操作方法的类分开,如果外部访问该操作方法,是通过接口,而不是引用实现操作方法的类,因此,改变实现类时,并不会影响外部的访问。接口可以用类的图标来表示,此时的版型为<<接口>>,没有属性,在操作方法区列出抽象的公共操作方法
,用图所示的图标表示。
协作:一个系统用例声明了外部可见的功能,那么如何实现一个系统用例,可以用协作来描述对系统用例的实现。在协作表示的模型中,一个特定的行为,是由一组对象以及对象之间的消息传递来实现的。协作描述了在一定的语境中一组对象以及用以实现特定行为的这些对象之间的相互作用,图(a)所示的图标表示协作。协作体现在结构和行为两个方面,结构方面描述为对象的集合及其之间的关系,而行为方面描述为消息的集合及其在对象之间的传递。协作是UML中采用设计模式来对重复问题建模的基础。对设计模式而言,模式就是一种特殊的协作,它实质上描述的就是对象的结构以及对象之间的交互,并应用这样的一种协作来解决某一类问题。在UML中 ,模式是用参数化的协作来表示的,如图(b)所示。
组件:组件是编码的实现模块,具有规范接口的物理实现单元,只要接口相同,组件之间可以相互替换。根据程序设计语言的不同,对逻辑设计中的类、接口、协作的打包成的组件也有不同。要是用Java实现编程,则每个类映射.java文件,形成一个组件。组件可以用不同版型的图标来表示,有两种主要的组件类型,即源代码组件和运行组件。如图所示图标的组件表示具有定义接口的软件模块;
节点:代表计算机资源或网络资源的物理单元,有两种基本类型的节点,一种是具有处理器(CPU)功能的节点,用图(a)所示的图标表示,代表了客户机或服务器;另一种是不具有处理器功能的节点,用图(b)所示的图标表示,代表了打印机或者网络设备等。组件必须配置在具有处理器功能的节点上运行。
⑵ 关系图素:
关系图素包括关联关系、依赖关系、聚集关系、实现关系和泛化关系等。分别介绍如下。
关联关系:关联关系表示类的实例(对象)之间的连接,使得一个类的对象知道另一个类的对象的公共属性和操作。有双向关联和单向关联,图(a)所示代表双向关联,用一条实线连接两个类,表示类1的对象知道类2的对象的公共属性和操作,类2的对象也知道类1的对象的公共属性和操作,同时也表明两个类的对象之间可以相互发送消息。图(b)所示代表单向关联,用一条带箭头的实线连接两个类,表示类3的对象知道类4 的对象的公共属性和操作,但类4 的对象不知道类3的对象的公共属性和操作,这时,类3的对象可向类4的对象发送消息,反之不行。单项关联在系统实现时更容易创建和维护,有助于标识可复用的类。另外,有一种特殊的单向关联,就是一个类的对象与同一个类的其它对象相联系,称之为反身关联,如图(c)所示。
依赖关系:依赖关系表示模型元素间的语义关系,一个模型元素影响到另一个模型元素,涉及到模型元素本身连接而不需要其实例。依赖关系只能是单向的,用一条带箭头的虚线表示,如图所示。
聚集关系:聚集关系表示整体与部分的关系。整体类需要引入表示部分类的属性,这里需要区分的是按值方式聚集还是按引用方式聚集。按值聚集属性时,同时创建和部署整体和部分对象,一旦删除整体对象,部分对象也随之删除,即整体和部分具有一致的生命周期,按值聚集关系也称为组成关系,用带实心菱形实线表示,实心菱形连接整体类一端,如图(a)所示。按引用聚集的整体和部分对象不是同时创建和部署,部分的对象可以为多个整体聚集对象共享,它们是一般的聚集关系,用带空心菱形实线表示,空心菱形连接整体聚集类一端,如图(b) 所示。
实现关系:实现关系是两个模型元素之间的连接关系,其中一个模型元素提供面向外部行为的规格说明,另一个模型元素用来实现前者的行为的规格说明。通常有比较典型的两种类型,一种类型是提供外部功能行为的规格说明的系统用例与实现这种系统用例行为的协作之间的关系,显然,系统用例与协作之间的关系是实现关系,用一条带封闭三角形空箭头的虚线来表示,封闭三角形空箭头在提供规格说明的系统用例一边,而末端在提供实现的协作一边,如图(a)所示;另一种类型是提供外部操作行为的规格说明的接口与实现接口操作行为的类之间的关系,同样,接口与之连接的类的关系也是实现关系,用一条带封闭三角形空箭头的虚线来表示,如图(b)所示。
泛化关系:泛化关系表示两个模型元素间的继承关系,涉及的模型元素有角色、用例、 类和包等。针对类的模型元素,表示一般性描述的类称为父类,而表示特殊性具体描述的类称为子类,当然,子类除了继承了父类的特性外,还具有本身的特性。继承关系用一条带封闭三角形空箭头的实线来表示,封闭三角形空箭头在父类一边,末端在子类一边。其它模型元素的父与子的描述基本相同。如图所示。
博文最后更新时间: