天天看点

《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包

本节书摘来华章计算机《数据结构与抽象:java语言描述(原书第4版)》一书中的第1章 ,第1.2节,[美]弗兰克m.卡拉诺(frank m. carrano) 蒂莫西m.亨利(timothy m. henry) 著 罗得岛大学  新英格兰理工学院 辛运帏 饶一梅 译 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

1.2 说明一个包

在用java实现包之前,需要描述它的数据,并详细说明对应于包行为的方法。我们将命名方法,选择它们的参数,确定它们的返回值类型,并写出注释来充分描述它们对包数据的影响。当然,我们最终的目的是写出每个方法的java头和注释,但首先我们用伪代码来描述方法,然后用统一建模语言(uml)进行表示。

crc卡的第一个行为引出一个方法,该方法返回包中当前的项数。对应的方法没有参数,它返回一个整数。使用伪代码,我们有下列的描述:

《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包

可以使用uml将方法表示为:

《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包

且将这行添加到类图中。

可以使用一个布尔值方法来测试包是否为空,同样该方法没有参数。用伪代码及uml描述这个方法的规格说明

《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包

《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包

将这行添加到类图中。

注:因为通过查看getcurrentsize是否返回0就能检测包何时为空,所以并不真的需要操作isempty。但是它是所谓的便利方法(convenience method),所以很多集合都提供这样一个操作。 现在想向包中添加给定的对象。可以将这个方法命名为add,并有一个表示新项的参数。可以写出下列伪代码:
《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包

我们可能试图让add作为void方法,但是会有一些情况,比如如果包满则不能将新项添加到包中。这些情况下,我们该如何办呢?

设计决策:当不能添加新项时,方法add将如何处理?

当add不能完成任务时,我们可采取下面两种选择:

什么也不做。不能添加其他的项,所以忽略这个项并且不改变包。

不改变包,但告诉客户添加是不可能的。

第一个选择简单,但会让客户疑惑到底发生了什么。当然,我们可以规定add的前置条件,即包必须不满。这样客户要负责避免将新项添加到满包中。

第二个选择更好一些,且它也不难说明或实现。我们如何告诉客户添加是否成功?标准java接口collection规定,如果添加没有成功则发生异常。稍后我们再完成这个方法,并且使用另一种方式。显示一条错误信息并不是好的选择,因为你应该让客户决定所有的书面输出。因为添加操作或者成功或者不成功,所以我们可以让方法add返回一个布尔值。

因此,可以用uml规范add方法:

其中t表

《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包

示newentry的数据类型。

自测题1 假定abag表示一个有有限容量的空包。写伪代码,将用户提供的字符串添加到包中,直到操作失败。

有3个动作涉及从包中删除项:删除所有的项;删除任意一项;删除某个项。假定我们用伪代码为这些方法命名并说明其参数,如下所示:

《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包

这些方法的返回类型是什么?

方法clear可以是一个void方法:我们只想要一个空包,不获取它的任何内容。所以,在uml中方法写为:

《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包

如果第一个remove方法从包中删除一项,则该方法可以简单地返回被删除的对象。它的返回类型为t,这是包中项的数据类型。在uml中,我们有

《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包

现在,我们可以处理从返回null的空包中删除对象了。

如果包中不含有某项,则第二个remove方法不能从包中删除该项。可以让方法返回一个布尔值,类似于add那样,用它来表示成功与否。或者,方法可以返回被删对象,或者,如果不能删除这个对象则返回null。下面是用uml表示的规格说明的两种可能版本——我们必须二选一:

《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包

或者

《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包

如果anentry等于包中的某项,则这个方法的第一个版本将删除该项并返回真(true)。即使方法没有返回被删除的项,客户也能有方法的参数anentry,它等于被删除的项。故我们选择这个版本,它与接口collection是一致的。

自测题2 在一个类内同时具有上面描述的remove(anentry)的两个版本合法吗?

解释。

自测题3 在一个类内同时具有remove的两个版本,一个不带参数而另一个带一个参数,这样合法吗?解释。

自测题4 给出自测题1中创建的满包abag,写伪代码语句,删除并显示包中的所有字符串。

其他的动作并不改变包的内容。其中一个动作是计数包中给定对象的出现次数。我们先用伪代码后用uml说明它,如下所示。

《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包

另一个方法测试包是否含有给定对象。使用伪代码和uml给出的规格说明如下所示。

《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包
自测题5 给定自测题1中创建的满包abag,写伪代码语句,找出abag中字符串"hello"出现的次数,如果有的话。

最后,我们想看看包的内容。不是提供显示包中项的方法,而是定义一个方法来返回保存这些项的数组。这样,客户可以按照自己的意愿显示部分或全部的项。下面是最后这个方法的规格说明:

《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包

当方法返回一个数组时,它通常应该定义一个新的数组来返回。我们还将说明这个方法的细节。

当我们为包中的方法提供前面那些规格说明时,使用uml符号来表示它们。图1-2显示了这些结果。

《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包

注意,crc卡和uml并不反映所有的细节,例如我们在前面的讨论中提到过的假定和特殊情形。但是,在确定了这样的条件后,你应该在每个方法的下面说明该方法应有的动作。

应该写下你的决策,想让方法如何动作,就像我们写在下表中的那样。然后,可以将这些非形式化的描述放在说明方法的java注释中。

《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包
《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包
设计决策:当特殊条件出现时会怎样?

作为类的设计者,必须要做出决定如何处理特殊条件,并将这些决策包含在规格说明中。adt包的文档应该反映这些决策和前面讨论的细节。

一般地,可以用几种方式声明特殊情形。你的方法可能

假定无效的情形不能发生。这个假定并不像听起来那么幼稚。方法可以声明一种假设(即前置条件),这是客户必须遵守的限制。然后由客户检查在方法调用前这个前置条件是否满足。例如,方法remove的前置条件可能是包为非空的。注意,客户可以使用adt包的其他方法,例如isempty和getcurrentsize,来辅助完成这个任务。只要客户遵守这个限制,无效的情形就不会发生。

忽略无效情形。当给出无效数据时方法可能简单到什么也不做。但是什么都不做会让客户不知道发生了什么。

猜测客户的意图。与前一个选择一样,这个选择可能为客户带来麻烦。

返回一个表示问题的值。例如,如果客户试图从空包中remove一项时,remove方法应该返回null。返回的值必须是不在包中的值。

返回一个布尔值,表示操作的成功或失败。

抛出一个异常。

注:抛出异常经常是java方法运行期间处理遇到的特殊事件的理想方法。方法可以简单地报告问题而不决定要做什么。异常能让每个客户根据自己的特殊情形按需处理。java插曲2将介绍异常的基本机制。

一个接口

随着规格说明越来越详细,也越发地影响到你对程序设计语言的选择。最终,你可能为包的方法写下java的方法头并将它们组织为一个java接口,用它们来实现adt的类。程序清单1-1中的java接口含有adt包的方法及描述它们行为的详细注释。回想一下,类接口不含有数据域、构造方法、私有方法或保护方法。

现在,包中的项将是同一个类的对象。例如,我们可以有字符串的包。为了容纳类类型的项,包的方法中使用泛型数据类型(generic data type)t>来表示每个项。必须在接口名的后面写,来说明标识符t的含义。一旦客户选择了具体的数据类型,编译程序将在t出现的所有地方使用那个数据类型。接在本章后面的java插曲1中,将讨论如何使用泛型为adt中的数据提供类型的灵活性。

当检查接口时,注意前一段中提到的处理特殊情形时所做的决策。具体来说,对于add、remove及contains方法,它们每一个都返回一个值。因为我们的程序设计语言是java,所以要注意,有一个remove方法返回一个指向项的引用,而不是项本身。

虽然不一定要在实现类之前写接口,但这样做能让你以简洁的方式记录你的规格说明。然后可以将接口中的代码用在具体类的框架中。有了接口还能为包提供数据类型,它不依赖于具体的类定义。接下来的两章将开发包类的两种不同的实现。针对接口所写的代码,能让我们更易于将包的一种实现替换为另一种。

程序清单1-1 包类的java接口

《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包
《数据结构与抽象:Java语言描述(原书第4版)》一1.2 说明一个包

说明一个adt并为它的操作写了java接口后,应该写几个使用adt的java语句。虽然还不能执行这些语句(毕竟我们没写实现baginterface的类),但我们可以用它们来确认或者修改方法的设计决策及相关文档。这样,可以检查规格说明的适应性及对它的理解。最好现在来修改adt的设计或文档,而不是等到写完实现后再进行。认真做这件事的额外好处是,后面可以使用这些相同的java语句来测试你的实现。

自测题6 给定自测题1创建的包abag,写java语句,显示abag中所有的字符串。不要改变abag的内容。

写java语句来测试一个类的方法,将有助于你完全理解方法的规格说明。很明显,在能正确实现方法之前必须理解它。如果你也是类的设计者,那么使用这个类可能有助于你对设计或对文档进行理想的修改。如果在实现类之前做这些修改,将会节省时间。因为早晚都要写一个程序来测试你的实现,所以为什么不现在写而获益,而非要放到以后再写呢?

则abag中可以包含类c的对象及c的任何子类的对象。

下一节看看使用包的两个例子。后面,可以用这些例子来测试你的实现。