抽象数据类型(ADT)

抽象数据类型 (ADT,Abstract Data Type)是指一个 数学模型 以及定义在此数学模型上的一组操作。 它通常是对数据的某种抽象,定义了数据的 取值范围 及其结构形式,以及对 数据操作 的集合 。抽象数据类型是描述数据结构的一种理论工具,其目的是使人们能够独立于程序的实现细节来理解数据结构的特性。

 

java中抽象数据类型的描述:1.抽象类(abstraction class),抽象类型的实现用继承该抽象类的子类表示, 2.接口(interface) ,抽象类型的实现用实现该接口的类表示。

 

定义数据类型的作用一个是隐藏计算机硬件及其特性和差别,使硬件对于用户而言是透明的,即用户可以不关心数据类型是怎么实现的而可以使用它。定义数据类型的另一个作用是,用户能够使用数据类型定义的操作,方便的实现问题的求解。例如,用户可以使用Java定义在int 型的加法操作完成两个整数的加法运算,而不用关心两个整数的加法在计算机中到底是如何实现的。这样不但加快了用户解决问题的速度,也使得用户可以在更高的层面上考虑问题。

 

用面向对象的语言和语法,更容易理解抽象数据类型,就像上面写的那样。“抽象、封装、继承、多态”是面向对象的4个特点,抽象就是把同一数据类型的共有特征概括出来。一个数据对象有四个属性:V(值),A(地址),N(名称),T(类型)。在程序编译完成后,可执行程序里面产生了信息缺失:程序中的数据对象只有地址和值,没有数据类型、数据名称、数据意义等信息了。所以抽象完成在编写程序的时候。抽象得出的数据类型的特点包含两个方面:属性和方法。封装就是通过一定的语法形式,把抽象出来的属性和方法“捆绑在一起”,在形式上写成一个整体,使人从形式上就能看出两者的紧密关系,这个过程叫封装。通过封装,还可以将部分属性和方法隐藏起来,对外只留一定的接口(函数),实现“信息隐藏”。封装可以理解为抽象的具体表现形式。

 

抽象数据类型 = 逻辑结构 + 抽象运算


ADT的特性

抽象数据类型ADT的特性:表示泄漏、抽象函数AF、表示不变量RI。若RI推给用户实现,就不为RI了。

 

抽象类型

强调“作用于数据上的操作”,程序员和 client无需关心数据如何具体存储的,只需设计/使用操作即可。ADT是由操作定义的,与其内部 如何实现无关!

 

可变数据类型

提供了可改变其内部数据的值的操作。

可变类型的例子:

List:
creators:ArrayList 和LinkedList的constructor,Collections.singletonList。
producer:Collections.unmodifiable.List
observers:size,get
mutators:add,remove,Collections.sort

 

不可变数据类型

其操作不改变内部值,而是构造新的对象。

不可变类型的例子:

String:
creators:String constructor。
producer:concat,substring,toUpperCase。
observers:length。
mutators:没有。因为是不可变数据类型。

 

ADT中四种操作方法

Creator构造器(无到有)

Producer生产器(老到新,与构造器类似)

Observers观察器

Mutator变值器(不可改变类型不能有变值器 )

一个作为静态方法实现的构造器通常被称为工厂方法变值器通常返void,如果返回值为void,则必然意味着它改变了对象的某些内部状态,变值器也可能返回非空类型。

 

表示独立性

client使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不应影响外部spec和客户端。除非ADT的操作指明了具体的pre和post-condition,否则不能改变ADT的内部表示——spec规定了 client和implementer之间的契约。

用户使用ADT时无需考虑内部是如何实现的,且ADT内部表示的变化不应影响外部用户的使用。

 

测试creators, producers, and mutators:

调用observers来观察这些operations的结果是否满足spec;

测试observers:调用creators, producers, and mutators等方法产生或改变对象,来看结果是否正确。

测试creators,producers,mutators:调用observers来观察这些operetions的结果是否满足spec。

测试observers:调用creators,producers,mutators等方法来产生或改变对象,来看结果是否正确。

风险:如果被依赖的其他方法有错误,可能导致被测试方法的测试结果失效。

 

表示泄露

表示泄露不仅影响不变性,也影响了表示独立性:无法在不影响客户端的情况下改变其内部表示。

Final修饰immutable有助于保证不会在对象被构造后被重新分配。

但final修饰mutable仍无法保证其值不变。

除非迫不得已(当复制代价很高时,不得不这么做,但是由此引发的潜在bug也将很多),否则不要把希望寄托于客户端上,ADT有责任保证自己的invariants,并避免“表示泄露”。

最好的办法就是使用immutable(除非重新创建,否则无法改变)的类型,彻底避免表示泄露。

一旦泄露,ADT内部表示可能会在程序的任何位置发生改变(而不是限制在ADT内部),从而无法确保ADT的不变量是否能够始终保持为true。

可以用ADT不变量取代复杂的 Precondition,相当于将复杂的precondition封装到了ADT内部。

 

AF和RI

ADT开 发者关注表示空间R,client关注抽象空间A。

AF:R->A满射,未必单射,未必双射。

表示不变性RI:某个具体的“表示”是否是“合法的” 。

也可将RI看作:所有表示值的一个子集,包含了所有合法的表示值 。

也可将RI看作:一个条件,描述了什么是“合法”的表示值。

客户端知道的有:Abstract value space,Creators,Observers.定义RI要时刻检验,规约推给用户。

AF和RI的注释:要精确的记录RI:rep中的所有fields何为有效,要精确记录AF:如何解释每一个R值。

rep exposure safety argument 表示泄漏的安全声明,给出理由,证明代码并未对外泄露其内部表示——自证清白。

ADT的规约里只能使用client可见的内容来撰写,包括参数、返 回值、异常等。

如果规约里需要提及“值”,只能使用A空间中的“值”。

ADT的规约里也不应谈及任何内部表示的细节,以及R空间中的任何值,ADT的内部表示(私有属性)对外部都应严格不可见,故在代码中以注释的形式写出AF和RI而不能在Javadoc文档中,防止被外部看到而破坏表示独立性/信息隐藏。