软件构造--Chapter6总结

抽象数据类型(ADT)

Abstraction and User-Defined Types

编程语言提供的基本数据类型和对象数据类型有限,程序员可以根据自己的需求,定义属于自己的数据类型。
数据抽象是由一组操作所刻画的数据类型,例如一个number类型,是满足可以进行加操作和乘操作的。
传统类型定义更关注数据的具体表示,并非操作,而抽象类型强调数据上的操作,无需关心数据的存储,是需要完成设计、使用操作即可。
在这里插入图片描述

Classifying Types and Operations

可变类型的对象,提供了可改变其内部数据的值的操作,例如Java中的Date、StringBuilder类就是可变类型;不可变类型的对象,不提供可改变内部值的操作,而是构造新的对象,例如Java中的String类就是不可变类型。
ADT中操作被分为如下几种:
1.构造器Creators,创造该类型新的对象,可能实现为构造函数或静态函数;
2.生产器Producer,从该类型旧对象中创建新对象,如String的concat()方法;
3.观察器Observers,获取抽象类型对象并返回不同类型的对象,如List类的size()方法;
4.变值器Mutators,改变对象属性的方法,如List类的add()方法。变值器通常返回void;

Abstract Data Type Examples

int类型是不可变的,其操作如下:
1.Creators,数字文字,如0,1,2,…
2.Producers,算术操作符,如+,-,*,/
3.Observers,比较运算符,如==,!=,<,>
4.Mutators,无
String是Java的字符串类型,是不可变的,其操作如下:
1.Creators,字符串构造器
2.Producers,concat,substring,toUpperCase
3.Observers,length,charAt
4.Mutators,无
List是Java的列表类型,是可变的,同时也是一个接口,为其他类提供了实现的数据类型,如ArrayList、LinkedList,其操作如下:
1.Creators,ArrayList、LinkedList构造器,Collections.singletonList
2.Producers,Collections.unmodifiableList
3.Observers,size,get
4.Mutators,add,remove,addAll,Collections.sort
具体例子如下:

操作操作类型
Integer.valueOf()Creator
BigInteger.mod()Producer
List.addAll()Mutator
String.toUpperCase()Producer
Set.contains()Observer
Map.keySet()Observer
Collections.unmodifiableList()Producer
BufferedReader.readLine()Mutator

Designing an Abstract Type

良好ADT的设计靠“经验法则”,提供一组操作,设计其行为规约spec。
1.设计简洁、一致的操作。最好使用简单的操作实现,而非复杂的操作;每个操作都应该有明确的目的,且有连贯的行为,而不是一大堆特殊情况,操作行为应该是内聚的。
2.足以支持client对数据所做的所有操作需要,操作满足client需要的难度要低。对象的每个需要被访问的属性应该都能被访问到;基本信息应该易于获取。
3.要么抽象,要么具体,不能混合。面向具体应用的类型不应该包含通用方法,而面向通用的类型不应包含面向具体应用的方法。

Representation Independence

表示独立性是client使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不应影响外部spec和客户端。
通过前提条件和后置条件充分刻画ADT的操作,spec规定client和实现者的契约,明确了client知道可以依赖哪些内容,实现者知道可以安全更改的内容。

Testing an Abstract Data Type

测试ADT方法:
测试Creators、Producers和Mutators通过调用Observers来观察这些操作结果是否满足规约;测试Observers通过调用Creators、Producers和Mutators等方法产生或改变对象,来看结果是否正确。
例如:
在这里插入图片描述
但是这样存在风险,如果被依赖的其他方法有错误,可能导致被测试方法的测试结果失效。

Invariants

不变量,程序在任何时候总是true的性质,一个好的ADT的属性是需要始终保持其不变量。不变量是由ADT负责,与client端的行为无关。
不变量可以保持程序的“正确性”,易于发现错误。在设计时要假设client可能存在“恶意”毁坏ADT不变量行为。
可变类型一种危险是客户端可以直接访问它的域,例如:

/**
 * This immutable data type represents a twwet from Twitter.
 */
public class Tweet{
	public String author;
	public String text;
	public Date timestamp;
	/**
	 * Make a Tweet.
	 * @param author       Twitter user who wrote the tweet
	 * @param text         text of the tweet
	 * @param timestampe   data/time when the tweet was sent
	 */
	public Tweet(String author,String text,Date timestamp){
	this.author = author;
	this.text = text;
	this.timestamp = timestamp;
	}
}
Tweet t = new Tweet("abc","Hello,world!",new Date());
t.author = "ABU";

这种情况是表示泄露,不仅影响不变量,也影响了表示独立性,无法在不影响客户端的情况下改变其内部表示。
关键字private可以表示fields和methods仅可在类内部访问,而关键字final则说明fields在第一次赋值后不能修改,否则会编译报错。用private和final修饰则可以维持它的不变性。
当可变对象复制代价过高时候,在签名中声明客户端不能改变。但是,不要把希望过多给客户端,ADT有责任保证不变性、避免表示泄露,最好就是使用不可变类型,例如用java.time.ZonedDateTime代替java.util.Date。
总结一下,不要将可变参数合并到对象中,要使用防御性的副本;可变字段返回时返回防御副本;返回新的实例而不是去修改原来的实例,或者返回可变字段的不可修改视图;使用不可变成分来消除防御性副本的需要。

Rep Invariant abd Abstraciton Function

R为表示值构成的空间,即表示空间,实现者看到和使用的值;A为抽象值构成的空间,即抽象空间,client看到和使用的值。
在这里插入图片描述
AF是抽象函数,是R和A之间映射关系的函数,即如何将R中的每一个值解释为A中的每一个值。
AF是满射,不是单射(R中部分值可能不合法,缺少A中对应的映射值),因此不一定是双射。
RI是values值到booleans的映射,对于每一个值r,RI®是true如果r可以被AF映射,换言之RI说明哪些值是合法的,它是所有表示值的一个子集,包含了所有合法的表示值。
例如:

public class CharSet{
	private String s;
	// Rep invariant:
	//   s contains no repeated characters
	// Abstraction function:
	//   AF(s) = {s[i] | 0<= i <s.length()}
	...
}

同一个ADT可以有多种表示,对于不同的表示需要设计不同的AF和RI。确定某种特定的表示方式R,进一步指定某个子集是合法的(设计RI),并未该子集内的每个值做出解释(设计AF)。
同样的表示空间R,可以有不同的RI;同样的R、同样的RI,也可以有不同的AF。
设计ADT,首先要选择空间R和空间A,再设计RI完成合法的表示值,最后再对合法的表示值进行解释,完成AF的设计。
在每一个操作的最后都要进行不变性检查,可以通过实现private void checkRep(),重复调用此方法进行检查,避免表示泄露。

Beneficent mutation

对于不可变的ADT,它在A空间的抽象值是应该保持不变的,但是内部表示的R空间的值是可以变化的。这种是有益的可变性。
例如:

@Override
public String toString(){
	int g = gcd(numerator,denomitnator);
	numerator /= g;
	denominator /= g;
	if(denominator < 0){
		numerator = -numerator;
		denominator = -denominator;
	}
	checkRep();
	return (denominator > 1) ? (numerator + "/" + denominator) : (numerator + "");

弱化RI,参与运算时,可以有公约数,而显示输出的时候,需要化简至没有公约数。
重写的toString重新分配了私有字段分子和分母,改变表示,即使它是不可变类型上的Observers,但是它不改变抽象值。这种改变只改变了R值未改变A值,在client视角是没有改变的,这种mutation是无害甚至是有益的。
但是这种不变类型中仍然不可以随意出现mutator。

Documenting the AF,RI,and Safety from Rep Exposure

在代码中用注释形式记录AF和RI,分别解释每一个R值、rep中的fields的值的有效性。另外,也要给出表示泄露的安全声明Rep exposure safety argument,给出证明代码未对外泄露内部表示的理由。
这里区分一下规约,规约里只能使用client课间的内容撰写,如参数、返回值、异常等,值只能为A空间的值。
在这里插入图片描述
规约中不应该有任何内部的细节以及空间的任何值,私有属性对外部应该严格不可见。
在代码中注释形式写的AF和RI不会在Javadoc文档中,防止被外部看到破坏表示独立性。
为了保持不变性,对象初始状态不变量为true,在对象发生变化时,不变量也要为true;Creators和Producers在创建对象时也要保持不变量为true;Mutators和Observers在执行时也要保持不变性;在每个方法返回前,使用实现的checkRep()检查不变量是否保持。
另外,一旦表示泄露,ADT内部表示可能在程序任何位置改变(不一定是ADT内部),无法确保ADT的不变量是否始终为true。

ADT invariants replace preconditions

ADT不变量可以取代复杂的前置条件(相当于把其封装到ADT内部)。
所需条件可在一个位置强制执行,而Java静态检查发挥作用,如果值不符合条件则编译时报错。
这更容易理解,传达了程序要需要知道的内容;这更容易改变,无需更改client。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深海质粒ABCC9

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值