ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

软件构造复习第六章 抽象数据类型ADT

2021-06-29 10:33:56  阅读:297  来源: 互联网

标签:表示 ADT 复习 AF 数据类型 client 抽象数据类型 RI


复习目录

Abstraction and User-Defined Types

除了编程语言所提供的基本数据类型和对象数据类型,程序员可定义自己的数据类型。
Data abstraction 数据抽象。由一组操作所刻画的数据类型。
抽象类型:强调作用于数据上的操作,程序员和client无需关心数据如何具体存储的,只需设计/使用操作即可。
一个数据抽象上的操作和操作的规约(spec)完全地定义了一个这个数据抽象,不去关系数据结构的细节实现。ADT是由操作定义的,与其内部如何实现无关
在这里插入图片描述

Classifying Types and Operations

在前几章提到过,数据类型可分为mutable or immutable,也即可变和不可变数据类型。
可变类型的对象:提供了可改变其内部数据的值的操作
不变数据类型: 其操作不改变内部值,而是构造新的对象
作用于数据类型上的方法可以分为

  1. Creators 构造器 即 t* → \rightarrow → T
  2. Producers 生产器 即 T+,t* → \rightarrow → T
  3. Observers 观察器 即 T+,t* → \rightarrow → t
  4. Mutators 变值器,改变对象属性的方法 即 T+,t* → \rightarrow → void | t | T immutable一般没有mutators方法

t:其他数据类型
T:抽象数据类型
+:出现一次或多次
*: 出现0次或者一次或者多次

接下来详细介绍四种方法:
构造器可能实现为构造函数或静态函数,静态函数的实现方法也被叫做factory method 工厂方法
变值器变值器通常返回void,如果返回值为void,则必然意味着它改变了对象的某些内部状态。变值器也可能返回非空类型,如返回boolean类型,来标识是否修改成功。
生产器 从旧的对象创建一个新的对象。如String类中的concat()方法。
观察器 依据该对象,生成一些其它类型的对象。如List类的size()方法。

Design principles of ADT

设计好的ADT,靠“经验法则”,提供一组操作,设计其行为规约 spec

  • 设计简洁、一致的操作
  • 要足以支持client对数据所做的所有操作需要,且用操作满足client需要的难度要低

Representation Independence (RI)

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

class Family {
	public List<Person> people;
	public List<Person> getMembers(){
		return people;
	}
}

void client1(Family f){
	Person baby = f.people.get(f.people.size()-1);
	//...
}
//上面的对f的调用完全违反了RI原则
//client1方法直接使用了people作为list的方法
//如果Family属性改变,则程序出错
class Family {
	public Set<Person> p;
	public List<Person> getMembers(){
		return new Arraylist<>(p);
	}
}
//要保持RI,应该如下编写
void client1(Family f){
	Person anybody = f.getMembers().get(0);
	//...
}

Testing an Abstract Data Type

测试ADT的方法:

  1. 测试creators, producers, and mutators:调用observers来观察这些operations的结果是否满足spec;
  2. 测试observers:调用creators, producers, and mutators等方法产生或改变对象,来看结果是否正确。

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

Invariants

一个好的ADT需要时刻保持不变量。
不变量:在任何时候总是true。例如:immutability就是一个典型的不变量
由ADT来负责其不变量,与client端的任何行为无关
不变量:保持程序的“正确性”,容易发现错误
假设client可能恶意破坏ADT的不变量—defensive programming

representation exposure 表示泄露 ADT的属性直接暴露给了client,比如声明了public类型的属性。不仅影响不变性,也影响了表示独立性:无法在不影响客户端的情况下改变其内部表示
改变的方法:属性定义为private final。private意味着仅类的内部方法可以访问该属性。final意味着如果属性是immutable,则这个属性不会再被改变。
其他的情况比如某个方法返回了一个类中的mutable类型的属性。如果client对这个属性进行修改,就会改变ADT中的属性。
处理方法一般是复制该属性的副本返回给client。
或者在spec中告知client不要进行修改。当复制代价很高时,不得不这么做,但是由此引发的潜在bug也将很多。除非迫不得已,否则不要把希望寄托于客户端上,ADT有责任保证自己的invariants,并避免“表示泄露”。
最好的办法就是使用immutable的类型,彻底避免表示泄露

保持不变性和避免表示泄漏,是ADT最重要的一个Invariant

建立不变性的方法:

  • 在对象的初始状态不变量为true,在对象发生变化时,不变量也要为true
  • 构造器和生产器在创建对象时要确保不变量为true
  • 变值器和观察器在执行时必须保持不变性
  • 在每个方法return之前,用checkRep()检查不变量是否得以保持

Rep Invariant and Abstraction Function

表示值rep values的空间 R 一般情况下ADT的表示比较简单,有些时候需要复杂表示
抽象值构成的空间A:client看到和使用的值
ADT开发者关注表示空间R,client关注抽象空间A
R与A中的映射:

  • Every abstract value is mapped to by some rep value (surjective, 满射).
  • Some abstract values are mapped to by more than one rep value (not injective, 未必单射).
  • Not all rep values are mapped (not bijective, 未必双射).

Abstraction Function AF抽象函数:R和A之间映射关系的函数,即如何去解释R中的每一个值为A中的每一个值。AF : R → \rightarrow → A
AF: 满射、非单射、未必双射。R中的部分值并非合法的,在A中无映射值
Rep Invariant RI

  • 表示不变性RI:某个具体的“表示”是否是“合法的”
  • 可将RI看作:所有表示值的一个子集,包含了所有合法的表示值
  • 可将RI看作:一个条件,描述了什么是“合法”的表示值

不同的内部表示,需要设计不同的AF和RI
选择某种特定的表示方式R,进而指定某个子集是“合法”的(RI),并为该子集中的每个值做出“解释”(AF)——即如何映射到抽象空间中的值。
即使是同样的R、同样的RI,也可能有不同的AF,即“解释不同”。

设计ADT
(1) 选择R和A;
(2) RI — 合法的表示值;
(3) 如何解释合法的表示值 —映射AF。做出具体的解释:每个rep value如何映射到abstract value
(4)要把这种选择和解释明确写到代码当中

Checking the Rep Invariant 随时检查RI是否满足

  • 在所有可能改变rep的方法内都要检查
  • Observer方法可以不用,但建议也要检查,以防止你的“万一”

Beneficent mutation

对immutable的ADT来说,它在A空间的abstract value应是不变的。但其内部表示的R空间中的取值则可以是变化的。这种改变称为beneficent mutation (有益的可变性)
这种mutation只是改变了R值,并未改变A值,对client来说是immutable的。
利用“AF并非单射”,从一个R值变成了另一个R值

Documenting the AF, RI, and Safety from Rep Exposure

在代码中用注释形式记录AF和RI
要精确的记录RI:rep中的所有fields何为有效
要精确记录AF:如何解释每一个R值
记录表示泄漏的安全声明:给出理由,证明代码并未对外泄露其内部表示——自证清白

总结一下ADT中的spec应该写些什么:

  • ADT的规约里只能使用client可见的内容来撰写,包括参数、返
    回值、异常等
  • 如果规约里需要提及“值”,只能使用A空间中的“值”
  • ADT的规约里也不应谈及任何内部表示的细节,以及R空间中的任何值
  • ADT的内部表示(私有属性)对外部都应严格不可见
  • 故在代码中以注释的形式写出AF和RI而不能在Javadoc文档中,防止被外部看到而破坏表示独立性/信息隐藏

ADT invariants replace preconditions

用ADT不变量取代复杂的Precondition,相当于将复杂的precondition封装到了ADT内部。示例
在这里插入图片描述

标签:表示,ADT,复习,AF,数据类型,client,抽象数据类型,RI
来源: https://blog.csdn.net/weixin_45633456/article/details/118323997

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有