[软件构造]04-抽象数据类型和类对象

本文主要包含以下内容:

1.数据抽象ADT的特点

2.RI的作用和撰写

3.ADT的内部关系

4.类与对象的基本概念

5.类域ADT的关联

一、数据抽象

由一组操作所刻画的数据类型,它强调作用于数据上的操作,与其内部实现方法无关。可变抽象数据提供可以改变其内部数据值的操作,不可变抽象数据则所有操作均不可改变内部值,而是构造新的对象,有一些抽象两种兼得。

二、ADT操作类型

ADT操作分为四种类型:构造器(可能实现为构造函数或静态函数[工厂方法])、生产器、观察器和变值器(通常返回void)。

一些ADT操作的具体例子:

int没有变值器,构造器是数字,生产器是运算符,观察器是比较运算。

字符串无变值器,构造器是新建串,生产器有concat,substring,观察器是length,charAt等。

List构造器是Arraylist/Linkedlist,生产器是Collection.unmodifiableList,观察器size, get,变值器包括add,remove,addAll,Collection.sort。

三、ADT的特点和设计原则

ADT的特性是不变量、表示泄露、抽象函数AF、表示不变量RI。其中:

        不变量(invariant)——程序在任何时候总是true的性质,例如不可变类型。ADT需要保持其不变量以保持程序的正确性,而与客户端的任何行为无关。当用户可以直接访问类域时发生表示泄露(representation exposure),不仅影响不变量,也影响RI,因为无法在不影响客户端的情况下改变其内部表示,所以类域需要使用private final或不可变类型。

        表示独立性(RI)——用户使用ADT时无需考虑内部如何实现,ADT内部表示的变化不应影响外部规约和客户端。(一般就是返回一个new类型)

        抽象函数(AF)——抽象函数就是R(表示)和A(抽象)间的映射关系,即如何将R解释为A。A是R的满射但未必单射,所以未必双射。这说明R中的部分值是非法的,在A中无映射值。

因此,良好的设计ADT的原则就是根据经验认识提供一组操作设计其行为规约。这些操作的设计要简洁一致,足以支持用户对数据所做的所有操作需要,且用操作满足用户需要时实现难度较低等。

四、表示独立性(RI)的撰写

1)RI的作用:它通过前提条件和后置条件来充分刻画ADT的操作,规约规定了用户和实现者之间的某种契约,明确了用户能够依赖哪些内容,使用者能够安全更改哪些内容。

2)方法RI撰写的例子:



/**@param b a boolean value
 * @return string representation of b,either "true" or "false" */
public static MyString valueOf(boolean b){ ... }


/**Calculate the time-length of the Interval
 * @param start starting index
 * @param end ending index.Requires 0<=start<=end<=maxtime
 * @return end's subtraction of start */
public static int differ(int start,int end){ ... }

3)ADT撰写RI的例子:

//Rep invariant:
//    author is a Twitter username(一个非空字符串)
//    text.length<=20
//Abstraction function:
//    AF(author,text,timestamp)=a tweet posted by athtor ,with text at timestamp
//Safety from rep exposure:
//    所有域都是private
//    anthor and text are String,so are guaranteed immutable
//    timestamp is a mutable date,so Tweet() constructor and getTimestamp() make defensive //    copies to avoid sharing rep's Date object.

五、测试ADT

测试构造器、生产器和变值器,就是调用观察器来检查操作是否符合规约。而测试观察器就要反过来。但这样如果被依赖的方法有错误则测试结果失效。

六、表示独立性和抽象函数

这个方面包含以下基本定义:

1)表示(R):表示值是程序员看到和使用的值。

2)抽象(A):抽象值是用户看到和使用的值。

3)RI:将R映射成布尔值,指示具体某个表示是否是合法的。可被看做所有标示值的一个子集,包含了所有合法的表示值;或者说若干条件,描述什么情况下表示值合法。同样的R,可以有不同的RI,而且即使是同样的R、RI也可能有不同的AF,即”解释不同”。

4)checkRep:对任何可能改变R的方法都要检查,观察器的检查是可选的。

5)不可变类型的ADT,它在A中的值应不变,而其内部表示的R的值则是可以变化的。一些变值器只是改变了R而未改变A,这说明AF并非单射,这种变化通常有益。

6)记录RI、AF、RE安全性:在代码中注释形式记录AF(每个R是如何解释的)和RI(R中哪些域是有效的),还要有表示泄露的安全声明(关于域是否是private、方法是否不可变等等)。如果规约里提及值,只能用A,包括参数、返回值、异常等。

7)ADT不变量代替前置条件:相当于将复杂的前置条件封装到了ADT内部,这种方法不易出错,而且易于理解、易于改动。

七、类的基本概念

1)对象:一个对象是一系列状态(对象中的数据)和行为(对象支持的操作,即方法)的集合。

2)类:每个对象都有一个类来定义方法和域作为成员。简单地说,类是应用程序接口(API)。

3)方法:类成员变量和类方法与一个类连接起来并且每个类出现一次。使用它们并不需要创建对象;而实例方法和实例成员变量对于类的每个实例出现一次。

4)接口(interface):接口中只有方法的定义,没有实现函数体。接口之间可以继承与扩展,一个类可以实现多个接口,一个接口也可以有多重实现类。接口是用来确定ADT规约的,也可以无需接口直接使用类作为ADT,但更倾向于用接口定义变量。接口不能有构造器,转而用静态工厂生成具体实现类。

5)默认接口(default):接口中的每个方法在所有类中都要事先,缺点是会导致部分方法的重复实现。所以使用default方法,在接口中同一实现某些功能,无需在各个类中重复实现,这是增量式的为接口增加额外功能而不破坏已实现类。

6)信息掩盖:使用接口类型声明变量,客户端仅使用接口中定义的方法,客户端代码无法直接访问属性。通常声明private、protected和public。

编程时,最好只给客户端功能实现,其他所有成员都声明为private。private之后还能public。

7)继承:class A extends B。继承是代码复用,满足严格继承(子类只能添加新方法,无法重写超类中的方法[final])。

8)重写(override):重写的函数前后同名,具体执行时,运行会根据谁调用的来判断调用哪个。父类中被重写函数不空意味着对其大多数子类该方法可直接复用,但某些子类具有特殊性,所以需要重写父类型的函数,实现自己的特殊要求。如果父类某个函数的实现体为空,则所有子类都要实现这个功能,但没有共性,需要在每个子类中重写。重写后,可以借助super.function来在子类中复用父类的功能。如果super的是构造器,则必须在子类构造器的第一句调用。重写时不得改变原方法的本意,保证签名一致(加上@Override交由编译器检查)。

9)抽象类:抽象方法由abstract声明,只有定义没有实现。至少包含一个抽象方法的类就是抽象类。抽象类不能实例化(不能用new),继承某个抽象类的子类在实例化时,所有父类的抽象方法必须已经实现。接口就是一个只有抽象方法的抽象类。如果某些操作是所有子类型共有但彼此有差别,可以在父类中设计抽象方法,在各子类型中重写。

八、ADT与类的关系

在自己的编写(堆砌)代码的历程中,设计ADT往往就是要实现一个或抽象或具体的类,只不过当撰写ADT时,注重对内部操作的编写和封装,而类既可以做接口也可以做实现类,所以不仅着眼于内部操作,还会考察外部方法调用的情况。这就是后面我们将要学到的设计模式了。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值