软件构造课程随笔——3-3【抽象数据类型-1】

本文深入探讨了抽象数据类型的概念,包括类型分类、操作分类、设计原则及表示独立性,阐述了抽象数据类型如何通过其操作集和规格说明来表征,以及如何设计一个有效的抽象类型。

一、抽象和用户定义类型

什么是抽象

抽象数据类型是软件工程中一个普遍原则的实例,从它衍生出很多意思相近的名词。这里列出了几个能够表达其中思想的词:
抽象: 忽略底层的细节而在高层思考
模块化:将系统分为一个模块,每个模块可以单独的进行设计、实现、测试、推倒,并且在剩下的开发中进行复用。
封装:在模块的外部建立起一道“围墙”,使它只对自己内部的行为负责,并且系统别处的bug不会影响到它内部的正确性。
信息隐藏:将模块的实现细节隐藏,使未来更改模块内部时不必改变外部代码。
功能分离:一个模块仅仅负责一个特性/功能,而不是将一个特性运用在很多模块上或一个模块拥有很多特性。

用户定义类型

在早期的编程语言中,用户只能自己定义方法,而所有的类型都是规定好的(例如整型、布尔型、字符串等等)。而现代编程语言允许用户自己定义类型对数据进行抽象,这是软件开发中的一个巨大进步。
对数据进行抽象的核心思想就是类型是通过其对应的操作来区分的:一个整型就是你能对它进行加法和乘法的东西;一个布尔型就是你能对它进行取反的东西;一个字符串就是你能对它进行链接或者取子字符串的东西,等等。例如定义一个名叫Date的结构体,里面用int表示天数和年份。但是真正使得抽象类型变得新颖不同的是对操作的强调:用户不用管这个类型里面的数据是怎么保存表示的,就好像是程序员不用管编译器是怎么存储整数一样。起作用的只是类型对应的操作。

二、类型和操作的分类

类型的分类

对于类型,不管是内置的还是用户定义的,都可以被分为可改变 和 不可变两种。其中可改变类型的对象能够被改变:它们提供了改变对象内容的操作,这样的操作执行后可以改变其他对该对象操作的返回值。所以 Date 就是可改变的,因为你可以通过调用setMonth操作改变 getMonth 操作的返回值。但 String 就是不可改变的,因为它的操作符都是创建一个新的 String 对象而不是改变现有的这个。有时候一个类型会提供两种形式,一种是可改变的一种是不可改变的。例如 StringBuilder就是一种可改变的字符串类型。
而抽象类型的操作符大致分类:

创建者creator

创建一个该类型的新对象。一个创建者可能会接受一个对象作为参数,但是这个对象的类型不能是它创建对象对应的类型。

生产者producer

通过接受同类型的对象创建新的对象。例如, String类里面的 concat 方法就是一个生产者,它接受两个字符串然后据此产生一个新的字符串。

观察者observer

接受一个同类型的对象然后返回一个不同类型的对象/值。例如List的 size 方法,它返回一个 int。

改造者mutator

改变对象的内容,例如 List的 add 方法,它会在列表中添加一个元素。
在这里插入图片描述

三、抽象数据类型举例

举例
int:不可变数据类型,没有mutator函数
List:可变数据类型,接口,有mutator函数
String:不可变数据类型,没有mutator函数

在这里插入图片描述
在这里插入图片描述
抽象类型是用操作定义的
类型是由其操作集以及规格说明所表征的。
在这里插入图片描述
抽象类型的值是不透明的,因为客户端无法检查存储在其中的数据,除非操作允许。 隐藏了数据和实现。

四、设计抽象类型

设计一个抽象类型包括选择合适的操作以及它们对应的行为,这里列出了几个重要的规则。设计少量,简单,可以组合实现强大功能的操作而非设计很多复杂的操作。

每个操作都应该有一个被明确定义的目的,并且应该设计为对不同的数据结构有一致的行为,而不是针对某些特殊情况。例如,或许我们不应该为List类型添加一个sum操作。因为这虽然可能对想要操作一个整数列表的用户有帮助,但是如果用户想要操作一个字符串列表呢?或者一个嵌套的列表? 所有这些特殊情况都将会使得sum成为一个难以理解和使用的操作。

操作集合应该充分地考虑到用户的需求,也就是说,用户可以用这个操作集合做他们可能想做的计算。一个较好测试方法是检查抽象类型的每个属性是否都能被操作集提取出来。例如,如果没有get操作,我们就不能提取列表中的元素。抽象类型的基本信息的提取也不应该特别困难。例如,size方法对于List并不是必须的,因为我们可以用get增序遍历整个列表,直到get执行失败,但是这既不高效,也不方便。

抽象类型可以是通用的:例如,列表、集合,或者图。或者它可以是适用于特定领域的:一个街道的地图,一个员工数据库,一个电话簿等等。但是一个抽象类型不能兼有上述二者的特性。被设计用来代表一个纸牌序列的Deck类型不应该有一个通用的add方法来向类型实例中添加任意对象,比如整型和字符串类型。反过来对于一些特定类型的操作将他加入通用的类型中也是没有意义的

五、表示独立

一个好的抽象数据类型应该是表示独立的。这意味着它的使用和它的内部表示(实际的数据结构和实现)无关,所以内部表示的改变将对外部的代码没有影响。例如,List就是表示独立的——它的使用与它是用数组还是连接链表实现无关。

如果一个操作完全在规格说明中定义了前置条件和后置条件,使用者就知道他应该依赖什么,而你也可以安全的对内部实现进行更改(遵循规格说明)。
例子: 字符串的不同表示
先来看看一个表示独立的例子,下面的 MyString抽象类型是我们举出的例子,虽然它远远没有Java中的String操作多,规格说明也有些不同,但是还是有解释力的。下面是规格说明:
在这里插入图片描述
使用者只需要/只能知道这个类型的公共方法和规格说明。

private char[] a;

如果使用这种表示方法,我们对操作的实现可能就是这样的:
在这里插入图片描述
这种实现有一个性能上的问题,因为这个数据类型是不可变的,那么 substring 实际上没有必要真正去复制子字符串到一个新的数组中。它可以仅仅指向原来的 MyString 字符数组,并且记录当前的起始位置和终止位置。
为了实现这种优化,我们可以将内部表示改为:

private char[] a;
private int start;
private int end;

通过这种新的表示方法,我们可以这样实现操作:
在这里插入图片描述
什么是表示独立
MyString的现有调用者仅依赖其public方法,而不依赖其private字段,因此我们可以在不检查和更改所有调用者代码的情况下进行更改。

六、测试抽象数据类型

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值