我们引入了UML类图的概念比较了在Java编程语言和UML类图中表示类属性操作和关联关系的不同之处下面我们来看看如何在UML中表示两个重要的Java概念——继承接口
继承
在Java中我们可以声明一个类扩展(extends)另一个类还可以声明一个类实现(implements)一个或者多个接口下面我们来看看如何在UML中表达这些概念
下面是三个Java类的基本骨架第一个类是代表某种支付方式的Payment抽象类另外两个类分别扩展Payment类描述两种不同的支付方式
/** 描述支付方式的抽象类 */
abstract public class Payment {
public Payment() { }
public Payment(BigDecimal amount) {
thisamount = amount;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
thisamount = amount;
}
private BigDecimal amount;
}
/** 一个扩展了Payment类的子类描述信用卡支付方式 */
public class CreditCardPayment extends Payment {
public CreditCardPayment() {
}
public CreditCardPayment(BigDecimal amount) {
super(amount);
}
public String getCardNumber() {
return cardNumber;
}
public void setCardNumber(String cardNumber) {
thiscardNumber = cardNumber;
}
public boolean authorize() {
return false; //暂不实现
}
private String cardNumber;
}
/** 一个扩展了Payment类的子类描述现金支付方式 */
public class CashPayment extends Payment {
public CashPayment() {
super();
}
public CashPayment(BigDecimal amount) {
super(amount);
}
public BigDecimal getAmountTendered() {
return amountTendered;
}
public void setAmountTendered(BigDecimal amountTendered) {
thisamountTendered = amountTendered;
}
private BigDecimal amountTendered;
public BigDecimal calcChange() {
return amountTenderedsubtract(supergetAmount());
}
}
图一用UML显示了同样的三个类在操作和属性声明中类型和参数之类的细节都没有显示出来这是为了更清楚地显示出类的整体结构以及各个类之间的关系
图一UML一般化关系
Java中的extends关键词声明了继承关系相当于UML中的一般化(Generalization也译为泛化)关系在UML图形中用子类向超类的实线空心封闭箭头表示图一额外增加了一个Sale类这是为了更清楚地说明UML一般化关系与UML定向关联关系所用箭头的不同关联关系与一般化关系的另一个不同之处在于一般化关系的两端不需要说明多重性或角色名称
显然UML类图比三个Java源代码文件更清楚直观地显示出了三个类之间的继承关系如果你要与别人探讨设计思路绘制UML草图也要比直接使用代码简单快捷得多
也许有人会说系统的类结构图就在他们的头脑中他们只需要直接使用Java代码实际上对于规模较大的系统这种说法显然是不成立的即使对于规模较小的系统如果一定的时间之后要由其他程序员修改没有UML图也会寸步难行——很难保证每一个人都了解你头脑中的类结构图
在UML中抽象类的标志是类的名字以斜体显示在白板或纸张上手工画UML草图时很难区分字体是否是斜体为此一些人建议这些场合可以在类名称的右下角加上{abstract}标记以示区别
另一些人认为在白板上写{abstrac t}显得太罗嗦他们倾向于打破UML常规在类名称的右下角加上一个表示零个实例如果在该位置写上则表示该类是一个singleton类(永远只有一个实例的类)如果在该位置写上N则表示它是一个枚举类(拥有固定实例数量的类如一星期中的天数彩虹的颜色等等)不过这一切都不是标准的UML只能用于手工绘制UML图的场合看来也不可能得到UML建模工具的支持
历史知识UML首先由Rational公司的一个工作组发明Ration公司是UML建模工具Rose的生产者UML于年的OOPSLA会议上被公诸于世随后OMG(对象管理组织)于年采用了UML规范不难理解继续负责发展UML规范的OMG任务组包含了来自几乎所有主流UML工具厂商的代表因此除了严格遵从规范的UML软件工具在一些书籍或网页上发现不规范的UML符号也不足为怪
继承使得一个类能够使用另一个类的属性和方法就象使用自己的属性和方法一样当这类继承机制第一次出现时人们普遍把它视为重用现有代码的理想方法令人遗憾的是规模过于庞大的继承树变得很脆弱修改继承树的一部分就会在整棵继承树中引起一系列的连带反映在面向对象的编程中如果要实现有效的封装就应该让改动局部化即一个地方的改动不至于引起其他地方的变化而修改继承树一个地方引起其他地方的变化恰恰违背了上述设计思想UML图使得我们能够方便地掌握继承关系图从而为应用继承关系带来了方便那么什么时候适合运用继承关系呢?按照《Java Design》一书对于超类A和子类B执行如下检查
命题B是一个由A扮演的角色不成立
B永远不需要变形成为其他某些类别中的对象
B扩展而不是覆盖或废弃A的行为
A不仅仅是一个工具类(一些可以重用的实用功能)
对于一个问题域(特定的业务对象环境)A和B定义了同一类型的对象或者是用户事务角色实体(团体位置或其他东西)或其他物体的相似类别
如果上述任意一个判断不成立那么把A和B定义成继承关系可能是不合适的改用关联关系可能更加稳固正确例如图二违背上面的第一个判断因为雇员是一个由人扮演的角色成立另外它还违背了第二个判断因为雇员确实可能改变其类别(身份)例如某个时候它可能是顾客这样一个既是顾客又是雇员的人就要有两个独立的对象来描述从而使保存在Person类里面的信息重复出现带来了两个数据副本之间数据不一致的风险
图二不恰当的extends用法
图三显示了改用关联关系后的UML图现在一个人可以在同一时刻(或不同时刻)既是顾客又是雇员(或任意一种)
图三改用关联关系
接口
Java编程语言中接口(Interface)的概念也能够与UML概念匹配UML中的接口是一种实现继承的形式但这种继承形式与Java中通过关键词extends实现的继承有所不同
在Java中extends关键词描述了一种继承形式它既继承接口也继承行为这种类型的继承有时被称为Subclassing与其他的面象对象编程语言不同Java类只能从一个类继承许多时候设计UML图的人熟悉多种编程语言常常会引入多重继承的思想例如C++的多重继承思想从已有的Java代码生成UML图(这个过程称为反向工程)不会带来多重继承的问题但如果要求一个Java程序员去实现一个带有多重继承的UML类图就会出现问题如果多重继承中的超类是纯抽象类这部分类可以用Java的接口来描述但是如果只做这种转换不足以把UML类图中的多重继承全部转换成单重继承这时就必须修改UML类图重新建模了
虽然Java不支持C++之类语言那样的多重继承但它支持实现多重接口这种由Java关键词implements声明的继承只继承接口这种继承有时被称作Subtyping在UML中实现接口的类与接口定义之间的关系叫做Realization关系用一个虚线封闭箭头表示从实现接口的类指向接口接口本身的UML图与普通类一样但它的名字上面要加上<>图四由图一修改而成Payment类被一个接口取代(关于Realization名称的说明Realization最常见的中文译名是实现但是Java的implements也叫做实现为避免混淆本文中凡是出现Realization的地方一律直接使用英文) 图四接口与实现接口的类之间的Realization关系
接口可以从一个或者多个其他接口扩展UML一般化关系(实线封闭箭头)可用来描述这种关系如图五所示
图五UML一般化关系用来表示一个接口扩展了一个或者多个其他接口
UML还支持另一种接口符号即用圆圈表示接口(加上连线之后就成了棒棒糖的样子)但这种表示法多用于UML组件图在UML类图中比较少见
如果UML图规模较大有大量的类实现一个常用接口整个UML图可能乱成一团糟《Java Design》一书提出了一种简化方法后来又被《Streamlined Object Modeling》一书的作者采用这就是在实现接口的类中用接口的名字替代从接口继承的方法不过这不属于标准方法遗憾的是目前似乎还没有工具支持这种转换图六是用Together ControlCenter工具加工出来的简化类图