【软件构造】ADT

什么是抽象

抽象数据类型是软件工程中一般原理的一个实例,它有许多名称,其意义略有不同:
抽象。用更简单,更高层次的概念来省略或隐藏底层细节。
模块化。将系统划分为组件或模块,每个组件可以与系统的其他部分分开设计,实施,测试,推理和重复使用。
封装。围绕模块构建墙壁(硬壳或胶囊),使模块对其自身的内部行为负责,并且系统其他部分的错误不会损害其完整性。
信息隐藏。从系统的其他部分隐藏模块实现的细节,以便稍后可以更改这些细节,而无需更改系统的其余部分。

用户定义类型
在软件开发方面一个重大的进展就是抽象数据类型的提出。数据抽象的关键思想是:一个类型被可以对其进行的操作特征化。比如说,一个数字是你可以进行加法或者乘法运算的类型,一个字符串是你可以连接或者获得其子字符串的类型等等。从某种意义上说,用户可以在早期的编程语言中定义自己的类型,但是抽象类型的新颖性和不同之处在于它对操作的关注,用户不需要担心它的值是如何存储,只需要知道如何对这些值进行操作。

类型分类与操作
无论是用户自己定义的还是内置的类型,都可以被分为可变的和不可变的。可变类型的对象可以被改变,也就是说,他们提供的操作在执行时会导致在同一个对象上的其他操作给出不同的结果。如Date是可变的,我们可以调用setMonth改变一个对象,并使用getMonth观察变化。String是不可变的,因为他的操作创建了一个新的String对象而不是对现有的String对象进行改变。有时一种类型将以两种形式提供,一种是可变的一种是不可变的。如StringBuild就是String的一个可变版本。

一个抽象的类型的操作分类如下:
Creator   创建该类型的新对象。一个创造器可以将对象作为参数,但不是正在创建的类型的对象。
Producer  从该类型的旧对象产生一个新对象。例如String的concat方法就是一个生产者,他连接两个字符串,产生一个新的字符串。
Observer  获取抽象类型的对象并返回不同类型的对象。例如List的size方法返回一个int
Mutator  改变对象。例如List的add方法通过在List最后添加一个元素改变List。
我们可以将这些区别总结如下

每个T都是抽象类型本身; 每个t都是其他类型。+标记表示该类型可能在该签名的该部分中出现一次或多次,并且*标记指示它出现零次或多次。| 表示或。

一个创建器操作通常作为构造函数实现,如new ArrayList()。但是创建器也可以作为一个简单的静态方法,如Arrays.asList()。实现为静态方法的创建器一般称为静态工厂,Java中的各种String.valueOf方法都是作为静态工厂实现的。

变异器通常由无效的返回类型来设置。返回void的方法在调用时会产生一些副作用,因为它不返回任何东西。但不是所有的变异器都没有返回类型,如Set.add返回一个布尔型的值,只是该集合是否加入了新的元素。


抽象数据类型实例
下边是一些抽象数据类型的例子以及他们的一些操作

int是Java的基本数据类型,它是不变的,因此他没有变异器。
创建器:数字1,2……
生产器:算术运算符+,-,*,/
观察器:比较运算符==,!=,<,>
变异器:无
List是Java的列表类型。 列表是可变的。
创造器:ArrayList和LinkedList的构造函数,Collections.singletonList (静态工厂)
生产器:Collections.unmodifiableList
观察器:size,get
变异器:add,remove,addAll,Collections.sort

设计抽象数据类型
设计一个抽象的数据类型涉及选择一个好的操作并确定他们的表现方式。下边是一些经验法则。

最好有几个可以以强大的方式进行组合的简单的操作,而不是进行大量复杂的操作。

每个操作都应该有一个明确的目标,应该有一个连贯的行为,而不是一堆特殊情况。例如,我们可能不应该向List添加一个sum操作。虽然它可以帮助客户处理全书整数的List,但对一个全是字符串的List或嵌套的List就不适用了。所有这些特殊情况都会导致sum操作难以理解和使用。

这套操作在一定程度上应该足够的,因为必须有足够的操作来完成客户端可能想要做的各种计算。一个好的测试是检查是否可以提取该类型对象的每个属性。例如,如果没有get操作,我们将无法找出List中的元素。基本信息应该很容易获得。例如,List中的size方法并不是必须的,因为我们可以应用get方法来增加索引,直到失败,但这样做效率不高并且极其不方便。

该类型可能是通用的,例如列表或集合或图表。或者它可能是特定领域的:街道地图,员工数据库,电话簿等,但它不应该混合通用和特定于域的功能。用于表示纸牌序列的Deck类型不应该有一个通用的add方法来接受像整数或字符串这样的任意对象。相反,将诸如dealCards这样的特定于域的方法放入泛型类型List中是没有意义的。


代表独立
一个好的抽象数据类型(ADT)的关键应该是独立于表示的。这意味着抽象类型的使用与其表示形式(用于实现它的实际数据结构或数据字段)无关,因此表示形式的变化对抽象类型本身之外的代码没有影响。例如,List提供的操作与列表是以链表还是以数组表示无关。除非通过前置条件和后置条件充分指定了操作,否则您将无法更改ADT的表示形式,以便客户知道要依赖哪些内容,并且知道可以安全更改的内容。


下边是一个MyString的抽象数据类型的例子,先看他的规约

这些公共操作及其规约是允许此数据类型的客户端知道的唯一信息。事实上,在测试优先编程范例之后,我们应该创建的第一个客户端是一个测试套件,它根据它们的规范来执行这些操作。我们可以用MyStrings执行的唯一操作就是我们上面定义的操作:valueOf,length,charAt和substring。我们的测试必须将自己限制在这些操作上。下边是对valueof操作的一个测试。

现在我们来看一个MyString的简单表示法:只是一个字符数组,它恰好是字符串的长度,最后没有额外的空间。

下面是如何声明内部表示的方法,作为类中的一个实例变量

根据这种表示形式,我们的操作可以以下列方式实现

这种实现方式的一个问题是,它放弃了提高性能的机会。由于这种数据类型是不可变的,因此子字符串操作并不需要将字符复制到新数组中。它可以指向原始的MyString对象的字符数组,并跟踪新的子字符串对象所代表的开始和结束。

为了实现这个优化,我们可以将这个类的内部表示改为

具体实现方式为

由于MyString的现有客户端仅依赖于其公共方法的规约,而不依赖其私有字段,因此我们可以在不检查和更改所有客户端代码的情况下进行更改。这是代表独立的好处。


总结
抽象数据类型的特点是其操作。

操作可以分为创建器,生产器,观察器和变异器。

ADT的规约是其一套操作和这些操作的规约。

一个好的ADT是简单的,连贯的,充分的,和表示无关的。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值