1
 软件工程
1.9.2.3 7.2.3 类设计

7.2.3 类设计

类设计针对某个子系统中某个类的作用、类的内部构成及类之间的关系进行清晰、具体的描述,使得实现阶段的程序员能够根据描述很容易地转换为程序代码。类设计主要完成问题域中的对象及其相互交互的设计。在类设计期间需要完成如下工作:

(1)创建属性数据结构和所有操作过程的详细规约;

(2)定义所有属性的可见性(公共的、私有的或保护的),精化对象间的接口,以便定义完整的消息模型的细节。

面向对象设计的对象设计是对面向对象分析的结果进行改进和增补。根据实现条件,对面向对象分析产生模型的类与对象、结构、属性、操作进行组合与分解,增加必要的类、属性和关系。

1.类设计的目标

面向对象的类设计的目标是代码重用、良好设计的类和方法及数据的完整性。

1)代码重用

代码重用在面向对象的系统开发中,始终都是关注的目标。在系统需求分析中最初主要考虑的是类及类之间的继承层次。在类设计阶段,系统分析的结果可以作为代码重用的基础,协作图是模式提取的一种应用。所谓模式提取就是研究类图、交互图和对象图,识别交互中的模式,评估这些模式以确定相似协作中这些交互机制是否可以重用,而不再去重新开发。应根据类的共性对类进行评估,确定是否可以抽象出一个公共的基类来共享方法。最后随着设计的展开要对该方法的特征进行标记,对类似的可重用的方法要进行评估。

在教务信息管理系统中,无论是学生基本信息的管理,还是教师基本信息的管理,抑或是课程相关信息的管理都需要对数据库中的数据信息进行访问和使用,这里都需要对相应的数据库进行操作。例如,与数据库建立连接、与指定表建立连接、关闭连接等操作。因此,可以提取出一个公共使用的类(公共类)以用于对数据库进行相关的操作。

2)良好设计的类和方法

良好设计的类和方法关系到软件的可扩充性、可靠性、可维护性和灵活性。下面给出保证良好设计的类和方法的设计原则。

(1)始终保持数据的私有化,即尽量将所有的字段(又称为域、成员变量)声明为私有的。

(2)始终在构造函数内初始化数据,可以定义不同重载版本的构造函数,用于对不同需要的数据进行初始化工作。

(3)不要使用太多的相关联的原始数据类型。如果多个相关联的属性是原始数据类型,则尽可能把它们聚集到自己的类中。例如,年级类中与其相关联的专业属性就可以在年级类中加入专业编号来表示。

(4)不是所有的属性都需要单独的存取程序、更换程序方法,或者称为获取和设置方法。存取程序方法就是某个对象中返回一个特定的属性值的方法,而设置方法就是修改某个对象的单个属性值。换句话说,不是所有的属性都需要用set和get访问器来获取和设置。

(5)一致的排列构成每个类的元素。例如,首先是定义常量,然后是构造函数,再是静态方法,接着是其他方法、实例变量,最后是静态变量。

(6)对类、方法、属性给出有意义的名字,以便见名知义。

3)数据的完整性

一个结构良好的、模块化的健壮性软件,依然有数据完整性的问题,即使所有的算法和计算都经过了测试。在独立的方法调用中,更新相互关联的数据时会产生潜在的问题。相互关联的数据是指为维护数据的完整性,必须同时更新的多重数据。例如,如果在班级类中存放专业的相关信息,如果专业的信息在专业类中被修改了,而年级类中没有修改的话,那么专业类中的信息与班级类中的信息就会出现不一致的现象,这样会造成按照专业查找班级信息或按照专业查找学生信息时,导致查找失败的现象。因此,在进行类设计时,必须充分考虑数据的完整性和一致性问题。

还有可能因为恶意用户或不合法用户存取数据造成数据的完整性问题。在绝大多数系统中,并不是所有的用户都有同样的权限来存取数据和使用系统功能,所以系统设计有安全性设计。当然,安全性问题有很多是操作系统或系统管理员应该处理的问题,不应该是应用层次负责解决的。

2.类设计的内容

在类设计阶段需要对类的名称、属性、方法进行详细的描述,其中包括属性描述、方法描述、接口描述等。

1)类信息描述

(1)类的命名。

类的命名原则采用“className”的形式,需要注意的是,在为类进行命名时一定要为类起一个见名知义的名字。

(2)类的修饰符。

比较常用的类的修饰符有public、private、protected、internal、abstract、sealed等。在定义类时,需要根据系统的需要来确定类的修饰符。下面对这几个修饰符进行说明。

①pubic:公共类。

它可以被任何类访问,包括同一包中的类和其他包中的类。该修饰符只能用于顶层类和成员类(如某类的子类)。

②private:私有类。

这样的类只能由自身进行访问,它对外部类没有任何的贡献,但是它可以用于对嵌套类的声明进行定义。

③protected:受保护类。

这样的类只能由同一类或该类的派生类中的代码才可以访问该类型或其成员。

④internal:内部类。

这样的类只能由同一程序集中的任何代码才可以访问该类型或成员,其他程序集中的代码不可以访问。

⑤abstract:抽象类。

该类不能被实例化,只能用于继承。

⑥sealed:密封类。

该类不能由其他的类来继承,只能用于实例化。

(3)类的成员。

类的成员可以有常量、成员变量、属性、方法、构造函数、析构函数、其他数据类型等。在排列构成类的成员时,需要注意排列的次序,这样能够让类的层次结构更加清晰。

2)属性描述

类的属性描述首先要命名和设置可见性。

(1)属性的命名。

属性的命名使用“attributeName”的形式,字段的命名采用“fieldName”的形式。例如,定义学号字段可以定义为studentID。

(2)属性的数据类型。

在为类定义和属性描述时,非常重要的一个部分就是为属性指定数据类型,在设计阶段这个数据结构必须能够满足系统对属性的使用要求,并且尽量地为操作提供方便。例如,在教务信息管理系统中,定义学生信息类的属性时,需要将学生的学号studentID字段设置为private,将数据类型设置为字符串,也可以将此字段设置为整型或长整型的。这需要根据系统的需要来决定,一般情况下,将学生的学号设置为字符串类型,其扩展性和通用性会更好,这是因为有的学号除了数字还可能有用于表达专业信息的字符等信息,另外使用字符串可以固定学号的位数,这样便于更好地表达和操作。如果希望外部类对学号这个字段进行读/写,就需要定义一个与学号字段对应的属性(如studentID属性),通过get和set方法来控制字段的读/写。

(3)属性建模的原则。

一般情况下,为了降低类之间的耦合度,要遵循属性建模的以下原则。

①可以将所有域(也称为字段、成员变量)的可见性设置为private。

②仅通过set方法更新属性。在set方法中,实现简单的有效性验证,而在独立的验证方法中实现复杂的逻辑验证。

③仅通过get方法访问属性。在get方法中,实现域的访问。

④不是所有的属性都需要设置set和get方法,除非需要对这个属性进行读/写。

3)方法描述

方法描述需要确定方法的可见性、名称、参数、返回值等。其中,方法也称为操作、服务或成员函数。方法的可见性是指外部对象对该方法的访问级别。

(1)方法的命名。

方法要使用完整的描述进行命名,使用“方法名+()”形式。例如,在教务信息管理系统中需要查询学生信息,可以用findStudent()来命名。

(2)方法的可见性。

一个方法如何被其他方法访问是由方法的可见性定义的,在UML中,方法的可见性有3种级别:

①private只能在定义它的类中可以使用,使用“-”表示;

②protected只能在类的内部或其派生类中可以使用,使用“#”表示;

③public在程序的任何地方都可以使用,使用“+”表示。

为了降低系统的耦合,建立方法的可见性应遵循“让方法按照需要提供可见性,不要超过限度”的原则。例如,如果一个方法的可见性只需protected,就将方法设置为protected,而不必设置为public。