[UML笔记] 理解UML类关系

本文 翻译 自 :Understanding UML Class Relationships

前段时间为一个项目面试了数十名未来的开发人员时,我发现90%以上的候选人都宣称非常了解UML,但是却无法区分类图中使用的一些常见的UML关系元素:

Figure 1: 类关系

你可以使用UML命名法来命名上述各种关系吗?你可以描述每个关系如何用目标编程语言(如Java)实现吗?

UML不仅仅是关于漂亮的图片。如果正确使用,UML会精确地传达代码如何从图中实现。如果精确解释,实现的代码将正确反映设计者的意图。你可能会说,“这并不重要,因为我知道我的意思,我知道如何实现它。”的确,在一个项目中,架构师,分析师,设计师和开发人员的角色都由一个人(例如你),在设计图和实现之间断开 - 至少直到有人被卷入。尽管如此,这是一个小项目的心态,更不用说这是一种短视的做法。

那些开发大型系统和应用程序的大型组织呢?当不同的人甚至不同的团队充满上述角色时,所有涉及的人都会更好地了解在类图上绘制的每个UML关系代表什么。否则,UML的虚假陈述和/或误解将导致错误的代码实现。由于项目时间紧张,没时间也没必要重新实现。本文旨在帮助您和您的团队首次正确解释UML类关系。所以,让我们开始吧!

1 依赖

UML依赖关系是他们中最不正式的。这意味着在关系源端的类与在关系的目标(箭头)端的类存在某种依赖性。例如,下面的列子说明,A类以某种方式依赖于B类:

Figure 2: 依赖

虽然依赖可能具有广泛的意义,但最好不要过度使用依赖关系。在分析模型类图中,如域模型图,您可能会倾向于表示所有类只是相互依赖。然而,有趣的是,Rational UnifiedProcess(RUP)规定了在分析模型中应该使用的一般类关系是关联的(下一个),而不是依赖关系。因此,即使您对较高层次的概念进行建模,最好不要松散地使用依赖关系。这太模糊了。

此外,除非您以有限的方式使用依赖关系,否则您的模型消费者(您自己或其他开发人员)将对其含义的解释过于宽泛。一般来说,在项目中架构师和设计人员角色的人员,可以为经验不足的开发人员提供指导。因此,依赖关系应该用于向开发人员传达架构师和设计师的具体指导。

那么依赖关系应该代表什么呢?在上面的UML示例中,依赖关系意味着类A使用类B,但是类A不包含B类作为其自身状态的一部分的实例。这也意味着如果B类的接口发生改变,它可能会影响A类并要求它改变。我建议您限制您对非状态相关问题的依赖关系的使用。您将使用依赖关系来指示,例如,类A接收到类B的实例作为至少其中一个方法的参数。您还将使用依赖关系来指示类A为其一个方法(在堆栈上)创建B类的本地实例。然而,您不会使用依赖关系来指示类A声明类B的实例变量,因为这将指示与状态相关的关注。再次,使用关联来做到这一点(接下来)。

在Java中,以下是受限依赖关系的正确解释:

import B;
public class A
{

public void method1(B b)
{

// . . .
}

public void method2()
{

B tempB = new B();
// . . .
}
}

实际上,B类用法中的任何一种,作为方法的参数,或作为方法中的本地实例引用,都将适当地反映UML依赖关系。基本上对Java的约束依赖关系意味着您必须将类B导入到类A中,以便类A可以某种方式在方法中引用它。但是,以下是Java中约束依赖关系的错误实现:

import B;

public class A {

private B b; // wrong!

public B getB() {
return b;
}
}

在上面的例子中,B类用于通过声明具有实例范围的B类实例来定义A类实例的状态。 然而,这是对依赖关系的误解。但这确实引导了我们使用下一个UML类关系类型,关联。

2 关联

现在,依赖关系理解了 -也就是说,我们知道依赖关系是什么意思,它不是什么意思 - 它更容易理解称为关联的UML类关系。 这是一个与另一个类有关联的类的例子:

Figure 3: 关联关系

关联定义了依赖关系,但管理关系远远强于上述的依赖关系。箭头意味着存在单向关系。在这个例子中,这意味着A1类与B1类相关联。换句话说,A1类使用并包含B1类的一个实例,但B1不知道或包含A1类的任何实例。此示例显示为以下Java代码:

import B1;
public class A1 {
private B1 b1;
public B1 getB1() {
return b1;
}
}

因此,从某种意义上说,关联关系规定了约束的依赖关系不是什么。关联关系确定了依赖类的实例的状态。因此,依赖类(A1)必须在其类范围内定义关联类(B1)的实例。

还有更多的关联关系。因为我们正在讨论状态,所以可能需要定义构成依赖对象状态的实例的生命周期,以及有多少关联的类实例。这些建模技术分别是指聚合/组合和多重性。例如,下面显示了两个类之间的聚合关联:

Figure 4: 聚合

关系的源头已经添加了一个清晰的钻石装饰。这意味着A1聚合B1。聚合描述了A1的实例包含对B1的实例的引用作为A1的状态的一部分的关联,但是B1的特定实例的使用是或者可以在其他聚合器之间共享。共享关联意味着聚合对象的生命周期(在这种情况下为B1的实例)超出了引用对象的范围。因此,当A1的特定实例超出引用对象的作用域(例如垃圾回收)时,B1的实例(如果必要)不是超出作用域。

另一方面,组合定义了包含对象(A1)和包含对象(B1)的作用域相关的关系。当包含对象超出作用域时,包含的对象也将超出。组合装饰看起来像聚集装饰,除了组合装饰是黑色的:

Figure 5: 组合

关联箭头旁边的数字装饰指示关联中涉及的实例数。此示例说,A1类的一个实例将始终包含(状态)对许多B1类实例的引用。可以使用一系列可用的多样化装饰,例如0,1,0.1,0.. *,1..3,1 .. *等等。当关联关系显示聚合或组合时,也可以使用多重性。

关于多重性建模的指导是适当的。在上面的例子中,该图表明A1的一个实例包含许多B1的实例。这意味着数组关联。但是,在目标编程语言中,您可能不想将这种关系实现为B1实例的文字数组:

import B1;

public class A1 {
private B1[] b1;
// . . .
public B1 getB1(int anIndex) {
return b1[anIndex];
}
}

正如你可能经历过的,以这种方式处理数组是复杂的。每次添加新项目时,必须重新分配数组,并且在访问之前必须检查索引范围。显然上面的方法getB1(…)并没有写得很好,因为在某个时间点几乎保证了一个未处理的运行时异常。

在Java中,您可能会决定使用某些形式的 java.util.Collection ,例如 java.lang.ArrayList 实现的 java.lang.List ,而不是一个文字数组。应该编写一个组织标准,说明如何在UML中建模这些关联。当然,在分析模型中,上述多重关联是适当的。它清楚地表明了这种关系的意图。但是我建议在设计模型中,如果您使用以下标准解释图表,开发人员将会更清楚这些关系:

Figure 6: 另一种组合关系

不要在设计模型中使用多重性,除非您绝对打算使用数组,否则我相信上述可以更好地使用UML。此关联关系表明,A1的一个实例将声明类 java.util.List 的实例变量,实例变量的名称将为b1List,表示为UML角色名称。以下是相应代码的代码片段:

import java.util.ArrayList; import java.util.List;
import B1;

public class A1 {

private List b1List;

public A1() {

super();

b1List = new ArrayList();
}

// . . .
}

我们使用Java数组的许多疑虑已经消失了,因为使用了预先编写的和经过良好测试的类。此外,UML将设计师的意图完美地传达给将要解释图的程序员。

上述经验法则可能会有例外。如果您的UML工具允许您为各种多样性类型(例如有序和合格的)指定一个重写结合类,您可以确定使用多重性是合理的。该工具根据您的首选集合类规范生成的代码将清楚地表明架构师或设计师的意图。

3 泛化

UML泛化是更好理解的关系之一,象征着所谓的面向对象编程世界中的继承。它有时也被称为特殊化,因为子类是更通用的超类的特殊化:

Figure 7: 泛化

更具体地说,UML泛化对应于Java语言中的类扩展。上面的图片片段将在Java中实现,如下所示:

import B2;
public class A2 extends B2
{

// . . .
}

B2是超级类,A2是关系中的子类。只要记住,泛化符号从子类到其超类形成一条线,其中明确的三角形箭头指向超类。

4 实现

我最后要说的的UML类关系类型是实现。这种关系与泛化有些相关,但有点不同。在面向对象编程中,语法实现代表了一个类的接口的实现。所以它代表了一个类的一些特性如何定义,但对实现细节没有说明:

Figure 8: 实现

该图片段表明,A3类实现了由B3定义的接口。在Java语言中,上述实现关系将被编程如下:

import B3;

public class A3 implements B3 {
// . . .
}

设计面向对象的子系统和框架时,实现非常重要。在类图中实现的接口表示子系统或框架与其消费者之间的契约。接口发布者保证任何正确执行其一个或多个公共接口的消费者将具有与接口定义子系统或框架的一定程度的一致性。

5 结论

正如我一开始所说,UML可以比绘制图片发挥更大的作用。UML具有必要的属性,为传达软件架构和设计的实现提供巨大的价值。如果所有技术项目利益相关者都了解本文中讨论的类图中使用的四种UML关系类型,您可以确定UML建模的质量和实现的质量将会增加。

我邀请您联系我以进行进一步的澄清和反馈。你可以在这里找到我:vvernon at shiftmethod dot com