UML建模: 高级类设计中的常见错误及其纠正方法

建模高级类设计中的常见错误及其纠正方法

常见错误

纠正错误

举例

未完成设计中的属性详细信息

确保使用其类型,可见性和初始值正式定义每个属性。

见代码例11.1。 -DoctorID:INT = 0;

没有完整的操作签名

确保操作具有参数列表,可见性和返回值。

见图11.16

直接从基本类设计中设计解决方案级别的类图

在转向系统设计之前,确保将序列和状态机设计作为开发模型过程的一部分。

重温关于过程的第4章,关于序列图的第12章和关于SMD的第14章

过度使用多重继承

尽可能避免多重继承,至少在问题空间(实体)中

图11.12仅作为理解多重继承的示例提供; 不推荐。

在没有良好继承设计的情况下探索多态性

多态性需要仔细创建具有适当运算符重载的继承层次结构。

研究图11.10

混淆引用和值

通过引用是指多个源指向(引用)相同数据的位置; by value是在处理之前创建数据副本的位置。

 

UML建模: 高级类设计中的常见错误及其纠正方法

UML建模: 高级类设计中的常见错误及其纠正方法

设计中的接口和实现关系类或组件(组件在第17章中讨论)可能很复杂,具有许多属性和操作。当一个类很复杂,并且当它被另一个类使用时,复杂性只会增加。在设计过程中,复杂类将其公共操作的一小部分作为“子集”提供给其他类使用。这个操作子集可以被认为是“接口”。这样的接口(或一组接口)代表了类的一组特定且有凝聚力的功能。然后,消费者类可以调用此功能来寻找提供接口的类的服务。

UML建模: 高级类设计中的常见错误及其纠正方法

例如,考虑图11.4,它通过界面显示两个类,PatientForm和Patient之间的关系。 Patient类是一个实质性的类,在设计层面,包含许多操作。然而,在PatientForm方面,并非所有这些患者手术都很重要。因此,当PatientForm必须使用Patient时,它不会直接使用Patient类,而是使用其中一个接口。

为实现这一目标,Patient提供了一个名为PatientRegistrationInterface的界面,可供PatientForm使用,而不是整个Patient类。如图11.4中的示例所示,接口有助于将类的公共操作的子集提供给系统中的其他类。

接口也是类,虽然没有操作实现的细节,也没有所有属性。因此,接口可以由类表示。

PatientRegistrationInterface提供属于Patient类的操作子集,这些操作提供操作的模板,以及Patient类中接口的实际实现或实现。

顾名思义,接口只是类的“前端”,没有底层的实现细节。因此,每个接口都需要通过相应的实现来实现。这种情况要求两个类通过实现关系相关联。设计中的实现关系表明哪个特定类实现了接口。设计中的聚合关系聚合关系是一种特殊的关联形式。在聚合中,类在关联关系中彼此更紧密地相关。聚合可以被描述为整体部分关系,其中实体由“其他实体”或“类别”组成。因此,聚合也称为部分关系或包含关系。聚合的UML标准符号是一条线(类似于关联),在关系中的“高级”(或容器或聚合器)类旁边有一个菱形。

图9.6(第9章)显示了医院和部门类之间的聚合或“有”关系。该图显示了“医院有部门”,其中包含医院一侧的聚合钻石。这意味着医院由部门组成。

从技术上讲,在解决方案空间中,此聚合意味着Hospital对象由Department对象组成。代码示例11.4显示了如何在Java中实现聚合。

部门对象是医院对象的一部分,序列图中的部门对象的生命周期(在第12章中讨论)取决于医院对象的生命周期。实现关系:按引用和按值实现关联和聚合关系的两种方式:“按引用”和“按值”。图11.5和11.6分别显示了这两种方法。

UML建模: 高级类设计中的常见错误及其纠正方法

空心菱形(图11.5)表明虽然“主”(或更高)类由“其他”类中的对象组成,但它在执行期间仍可独立存在。这是因为它有自己的属性,其值足以存在。

实心或实心菱形(图11.6)表示构图,这意味着主类完全由另一类中的对象组成,不能独立存在。

UML建模: 高级类设计中的常见错误及其纠正方法

 

 

 

 

因此,如果Hospital对象必须与相应的Department对象关联,则Hospital对象可以“引用”Department对象。这是使用指向Department对象的“指针”。或者,Hospital对象可以使用Department对象的副本。如果创建了Department对象的副本,则聚合关系是“按值”。由于已创建Department对象的副本,原始对象将继续存在。可以由系统中的其他对象创建和使用Department的其他副本。由于尝试使用部门的各种对象尝试并更新自己的部门副本,因此部门的多个“按价值”副本可能会产生混淆。在执行期间,可以从多个源更新Department对象,从而导致同步问题。

 

 

当使用“by reference”来实现两个类之间的关系时,Hospital对象不会复制Department,而是指向或引用Department。因此,Department对象在系统中是唯一的。需要Department对象的其他对象引用它而不是复制它。

两种实施方法之间的比较可归纳如下:

◾在“按价值计算”中,医院和部门对象“严密”卡住。这意味着在创建Hospital对象时,会在其中创建Departments;同样,当Hospital对象被销毁(删除)时,它必须删除Department对象。此外,当Hospital对象从内存位置A移动到B时,Department对象也应从相关位置A移动到B.

 

◾“参考”,医院和部门仅通过参考相关。

这意味着当Hospital从内存位置A移动到B时,不一定要移动Department对象,并且Hospital对象可以继续引用它。

参数可见性对象可以通过操作签名作为参数传递给其他对象。这种对象对系统中其他对象可见的程度与关系的设计有关,对象在设计中可以具有以下可见性:

◾全局可见性使对象在运行时对系统中的所有对象可见;这可能会转化为一种联想。

 

◾类可见性 - 使对象仅对类或其他对象和类中的操作可见;这也使得两个类代表通过关联相关的两个对象。

 

◾功能可见性 - 保持对象可见,仅对给定的功能或操作可用;这转化为阶级到阶级关系中的聚合。

 

◾字段(或参数)可见性 - 使对象仅作为参数列表中的字段可见。

根据实现语言,程序员可以选择使参数对象对系统的部分(或全部)可见的选项。

多重性和对象图设计中的多重性多重性指示将参与关联或聚合关系的类的实例(即,对象)的数量。多重性在关联关系的任一侧显示为数字,表示在运行时将涉及此关系的对象范围。

最初在分析期间指定多重性。这些多重性源自用例中指定的业务规则。在设计过程中,进一步更新和改进了分析过程中产生的多重性估计。对于良好的设计,建议在所有关联和聚合关系上指定多重性。这是因为,除了指定业务规则之外,稍后还会在数据库设计期间使用多重性来开发<< table >>类及其关系。由于多重性是对数据库设计的重要影响,因此在第13章中再次对它们进行了单独讨论。

第9章(图9.7)讨论了类图中的多重性规范。多重性可以解释如下:

◾一个医院对象必须有一个部门,但它可以有许多部门。

 

 

CODE EXAMPLE 11.4: FOR AGGREGATION RELATIONSHIP IN JAVA

class Department

{

int DeptID;

String DeptName;

int[] AllDoctors;

int HospitalID;

public String getDeptName()

{

//code to get DeptName

}

public int getHospitalID()

{}

public String getHospitalName()

{}

public int getDoctors(Doctor DoctorArray[])

{}

}class Hospital

{

int HospitalID;

String HospitalName;

Department[] AllDepartments;

Address HospitalAddress;

public String getHospitalName()

{}

public int getDepts(Department DepartmentArray[])

{

//get list of departments in the hospital

}

}

 

 

 

 

 

UML建模: 高级类设计中的常见错误及其纠正方法

 

 

对象图解释多重性请注意,在指定多重性时有许多“可选项”。例如,当指定多重性0.1时,它表示在运行时,在该关联的那一端可能没有或一个对象关联。因此,通过查看多重性,人们不能总是确定将与另一个类的对象链接的对象的确切数量。对象类Model-3

可以绘制181图表,以便在视觉上显示在运行时可以链接到另一个对象的对象数量。

UML建模: 高级类设计中的常见错误及其纠正方法

图11.7显示了对应于同一图中的简单类图的对象图。该类图上的Doctor-Department关联中的多重性通过物理对象及其链接转换为对象图。图11.7显示了属于DOCTOR类的aDoctor对象和属于其对应的DEPARMENT类(或DEPARTMENT类的派生类)的其他四个部门对象。此图描绘了Doctor-Department关系的运行时方案。

请注意,每当乘法指定可选性(或范围,如本例1.4),则一个对象图不足以显示其整个范围。例如,在系统的某些其他执行中,只有两个或三个Department对象将链接到aDoctor。无需为每个可能的多重性绘制对象图。相反,只有在关系复杂且重要且需要可视化表示时才绘制对象图。此外,在运行时,需要进行检查以确定对象的数量和存在的链接。这些检查可以通过这些类中的特殊操作来实现,以验证对应于多重性的链接的存在。最后,当显示对象之间的链接时,它们的可见性(如前所述)也会得到改进。

集合类和多重性如前所述,多于一的多重性表示为该类实例化的众多对象的范围。例如,如果类Patient具有多于一的多重性,那么从该类实例化有许多Patient对象。分析描述Patient类行为的用例揭示了功能 - 技术操作 - 也有两类:

◾一组操作处理单个Patient对象。这些是与单个患者的行为相关的所有操作,例如createPatient(),calculateAge(),getDiagnosis()或changeDetails()。

 

◾另一组操作涉及一组或一组Patient对象。这些操作包括能够对一组患者进行存储,检索,分类和操作的操作。

此类操作的示例是listPatientByName()和totalPatients()。

这些操作不适用于单个Patient对象,因此不应将它们放在Patient类中。

适用于对象集合的操作指向需要“容器”或Collection类。这样的Collection类在图11.8中显示为PatientCollection类。容器或Collection类是其实例仅指向属于另一个类的列表或对象数组的类。集合类以开发环境中可用的容器类(实现语言)为模型。此类容器类的示例包括集合,数组,列表,字典,堆栈和队列。

容器类通常在UML中被建模为参数化类。如图11.9所示,其中list类具有表示集合的Item。在使用UML对收集方案建模时,此项目由Patient替换。

设计继承中的继承和多态(在第9章的图9.3中讨论)关系意味着一个类共享另一个类的结构和行为。子类从超类继承三个特定元素:属性,操作和关系。实际的继承层次结构通常是三层深。

代码示例11.5反映了“外科扩展部门。”此代码示例中显示的继承关系也允许类重用。

在设计中融入多态性多态性是软件工程的基础之一(第1章)。多态行为意味着,在运行时,相同的消息具有不同的行为效果。调用对象将相同的消息发送到被调用对象。但是,接收消息的被调用对象可以属于许多继承类之一。因此,接收的消息以不同的方式执行 - 取决于已经实例化的接收对象。

UML建模: 高级类设计中的常见错误及其纠正方法

在设计中加入错误和例外结合错误检测和错误处理是良好设计中的一项重要活动。错误处理是在运行时检测对象中的错误的机制。错误处理还指定发生错误时对象要采取的操作。对象中的错误示例包括错误的参数列表,参数中的错误值或不适当的返回值。

虽然错误通常是由于调用对象的错误调用而发生的,但在良好的设计中,接收对象以优雅的方式处理错误仍然很重要。

在没有这种错误处理设施的情况下,系统“崩溃”并且用户没有控制或补救。

 

设计中的良好错误处理包括由发生错误的对象以外的对象处理错误。这提高了设计质量,因为通常在整个系统中并不总是知道运行时对象中的错误的影响。大多数实现语言都提供内置的错误处理功能,支持强大的设计。

 

单独的错误监视对象可以继续发送验证和验证对象完整性的测试操作。这些“测试对象”继续通过发送测试消息并在消息被执行后检查其状态来监视主对象的状态。

简单代码示例11.9显示了在HMS的Patient类中包含错误处理(请注意,这是一个不是特定于编程语言的伪代码示例)。此示例显示了患者对象获取计划时发生的情况。此计划基于索引(从1到10,因为在此示例中仅允许10个计划)。当索引超出范围时,会发生错误。此错误处理机制不会“崩溃”系统,而是显示错误“索引超出范围。

 

例外仅用于未预期的“特殊”情况。因此,异常不是普通的错误情况,可以通过类中的“if-then-else”逻辑来处理。类别设计中包含例外情况,以处理错误未被类别计划和不被类别理解的情况。程序本身的算法或逻辑中的错误不是例外。但是,这些错误的算法在执行时“抛出”异常,并且错误处理是“捕获”或处理意外错误的机制。

属性标识,命名和定义属性是类的属性的名称。每个类都有表达其某些属性的属性。属性在很多方面与类相似,但不相同。例如,识别属性的过程与类名词分析的过程相同。然而,与类不同,属性没有自己的行为。事实上,这种缺乏行为是确定属性为属性而非类的原因之一。图11.13显示了在解决方案空间中设计期间如何定义和显示属性的详细信息。

Person类的属性示例是LastName,FirstName和DateOfBirth在UML中,每个属性都有一个名称和一个类型。类型可以是模型中的任何数据类型,类或接口。

命名属性与类一样,属性也称为单数常用名词。建议使用样式指南来指定属性的命名约定。例如,命名属性的通用标准是用大写字母开始它们。此外,如果必须为属性名称连接两个或多个单词,则应将它们与以大写字母开头的每个子单词放在一起。不使用下划线发现属性通过分析用例文档中的事件流,可以发现许多属性。

作为第一个剪辑,不适合成为一个班级的名词可以成为一个属性。

定义类时会发现其他属性。领域专家也提供了良好的属性。在分析和设计阶段,类中属性的发现和细化是一个迭代和增量过程。另外,在设计期间,为属性提供更多细节以使得能够实现相应的类。

属性(数据)类型通过声明数据类型在设计中进一步定义属性。数据类型列在属性名称后跟冒号(:)后面,如图11.14所示。有三种类型的数据类型:

◾开发语言提供的数据类型,例如INT(对于整数)和CHAR(对于字符)。

 

◾用户定义的数据类型,例如Color,可以描述汽车的颜色。

 

◾用户定义的类,例如Patient对象将具有一个名为Address的属性,它本身就是一个类(图11.15)。

“用户定义”类型提供“伪”语言,允许设计者扩展语言以用于编码系统。一些开发语言比其他语言提供更多数据类型;例如,某些语言中可能未预定义Date数据类型,这需要在编码期间创建它。开发语言的选择不应该影响类的设计,因为如果数据类型尚未在特定语言中预定义,则数据类型在编码期间始终可以由用户定义。

属性值虽然属性是类中数据项的“定义”,但属性值是数据项的“实际”值。例如:

◾属性是lastName

◾属性值是“波特”

◾另一个属性值是“Sharma”等等......属性值也有助于提供状态和对象;例如,在Account类中,如果属性dateClosed的值为0,则关闭相应Account对象的状态。

此外,如果dateClosed属性包含实际日期,则关闭Account对象的状态(有关状态的更多详细信息,请参阅第14章中有关状态机图的讨论)。

设计属性中的常见错误属性与类类似。这种相似性是设计属性时出现一些常见错误的原因。

 

属性设计中的常见错误包括:

◾将类命名为属性,例如,将Address设置为Patient的属性,而Address更适合作为一个类本身

◾将操作命名为属性,例如,getName是属性的错误名称,因为它意味着操作而不是类的特征

◾将属性值命名为属性,例如,Sam是名为Name的属性的值,但不是属性本身

◾在需要时不初始化属性,例如,DateOfBirth是应该初始化为“00”的患者类的属性

◾不提供适当的可见性 - 属性默认情况下应具有私有可见性,尽管偶尔全局属性可能具有公共可见性。不提供可见性或提供不适当的可见性是设计错误。

◾为属性提供错误的属性类型,例如AccountBalance:CHAR;帐户余额的属性类型应为CURRENCY或DOUBLE。

 

when在需要时不提供属性构造型。 Stereotype是UML中的分组机制,如果一个类中有大量属性,那么按照它们的原型对它们进行分组总是一个好主意。

属性的常见构造型示例有<< entity >>,<< business >>,<< date >>,<< counter >>等。

操作标识,命名和签名图11.16显示了解决方案空间中的操作的详细信息。

了解类中的操作类中的操作使类能够执行其职责。它们是可以由从该类实例化的对象执行的操作。

操作示例包括:

◾检查账户,客户的有效性

◾从数据库创建,读取,更新和删除(CRUD)操作类中的操作还提供实现封装的机制。操作提供可由其他对象调用的内部和外部服务。换句话说,类具有向需要该信息的其他对象提供信息所需的所有操作。例如,如果一个对象向Person对象询问该人的年龄,那么Person对象将从dateOfBirth属性和todaysDate计算年龄本身以提供答案。通过应用封装的基础,Person对象不允许系统中的任何其他对象访问其属性(在此示例中为dateOfBirth)。

命名操作操作是系统中其他对象的类的接口。出于这个原因,应该命名操作以指示其结果,而不是操作背后的步骤。这是因为操作中的步骤可以随着类本身的进一步开发和改进而改变。这需要重命名操作并修改它与之接口的任何其他类 - 如果操作是以它的功能而不是它提供的命名。

以下示例说明了命名操作的原理:

◾get​​Balance() - 这是一个命名良好的操作,因为它表示来自“调用类”视点的结果。

 

◾calculateBalance() - 命名不佳,因为它表明必须计算余额,而可以有很多方法来达到平衡。此名称代表实施决策。但请注意,如果在类中的另一个操作使用calculateBalance(),那么操作的这个名称是完全可以接受的。

了解操作签名操作提供系统的行为。最初在用例中识别并记录这些行为。然而,在设计期间,操作“签名”包括更多细节,从而可以实现操作。因此,在设计期间,操作定义将扩展为包括参数列表(也称为参数),它们的数据类型以及操作的返回值数据类型。

例如,Patient类中的操作可以完全定义为getAge(int PatientID):int,这意味着操作被发送患者ID(这是一个整数),并返回一个整数,即患者的年龄。 (请注意,Patient类本身将包含必要的属性DateOfBirth,用于计算年龄 - 这是封装。)在分析过程中,包含签名是可选的;但是,在设计过程中,签名是强制性的。

操作的签名也可以表示关系。如果操作参数中的类(或从操作返回)是基类(例如,字符串),则该关系不会在图中显示。对于其他类,该关系通常显示在一个或多个类图上。可以将操作组合或拆分为类,并将其他操作添加到类中以实现它。

UML建模: 高级类设计中的常见错误及其纠正方法

UML建模: 高级类设计中的常见错误及其纠正方法

图11.10通过一个简单的例子显示了多态性。 在这个例子中,Car类继承了两种类型的汽车:FourWheel和FamilyCar。 Car是包含方法(操作)moveCar()的超类。 此操作是由FourWheel和FamilyCar类中的moveCar操作“重载”的虚拟操作。 在代码示例11.6中可以看到这种设计的效果。

 

UML建模: 高级类设计中的常见错误及其纠正方法

 

UML建模: 高级类设计中的常见错误及其纠正方法

 

UML建模: 高级类设计中的常见错误及其纠正方法

 

 

UML建模: 高级类设计中的常见错误及其纠正方法

 

 

UML建模: 高级类设计中的常见错误及其纠正方法

 

 

UML建模: 高级类设计中的常见错误及其纠正方法