HIT-SC-Chapter Six
- HIT-SC-Chapter Six
- 1 Abstract Data Type (ADT)
- 2 Classifying Types and Operations
- 3 Abstract Data Type Examples
- 4 Designing an Abstract Type
- 5 Representation Independence
- 6 Testing an Abstract Data Type
- 7 Invariants
- 8 Rep Invariant and Abstraction Function
- 9 Beneficent mutation
- 10 Documenting the AF, RI, and Safety from Rep Exposure
- Summary: What an ADT spec may talk about
- 如何建立不变量
- 11 ADT invariants replace preconditions
- SUMMARY
HIT-SC-Chapter Six
Abstract Data Type (ADT)
1 Abstract Data Type (ADT)
- User-Defined Types
- 除了编程语言自带的基本数据类型和对象数据类型
- Data Abstraction
- 由一组操作所刻画的数据类型
- number 可以add and multiply
- string 可以 concatenate and substrings
- 而传统的类型定义:关注数据的具体实现
- 程序员无需关心数据如何具体存储,只需设计/使用操作即可
- 由一组操作所刻画的数据类型
- An abstract type is defined by its operations
- 类型T的操作集及其说明完全说明了我们所说的T。
- ADT是由操作定义的,与其内部如何实现无关
2 Classifying Types and Operations
(1) Mutable and immutable types
- mutable:
- 提供了可改变其内部数据的值的操作
- 日期,因为您可以调用setMonth()并通过getMonth()操作观察更改。
- 提供了可改变其内部数据的值的操作
- immutable:
- 其操作不改变内部值,而是构造新的对象
- 有时会以可变和不可变的形式提供类型。例如,StringBuilder是String的可变版本(但两者肯定不是同一种Java类型,而且不可互换)。
(2) Classifying the operations
-
creators 构造器
- create new objects of the type.
- 创建者可以接受一个对象作为参数,但不能接受正在构造的类型的对象。
-
producer 生产器
- create new objects from old objects of the type.
- String的concat()方法是一个生成器:它接受两个字符串,并生成一个表示它们连接的新字符串。
-
Observers 观察器
- take objects of the abstract type and return objects of a different type.
- The size() method of List , for example, returns an int .
-
Mutators 变值器
- 改变对象属性的方法
- The add() method of List , for example, mutates a list by adding an element to the end.
-
(3)操作的明显特征
- Signature of a creator:
- 构造函数/静态函数
- 作为静态方法实现的创建者通常称为factory method 工厂方法
- Signature of a mutator:
- 通常返回void
- 返回void,则必然改变了对象的某些内部状态
- 也可能返回非空类型
- 如,set .add()返回一个布尔值,指示是否实际更改了集合
- 在Java的图形用户界面工具包中,Component.add()返回对象本身,因此可以将多个add()调用链接在一起。
- immutable has no mutators
- 通常返回void
3 Abstract Data Type Examples
- int and String
- List
4 Designing an Abstract Type
- 一组操作+行为规约spec
- 经验法则
- 设计简洁、一致的操作
- 每个操作都应该有一个明确的目的,并且应该有一个连贯的行为,而不是一大堆特殊情况。
- 例如,我们可能不应该向List添加求和操作。它可能会帮助处理整数列表的客户端,但是字符串列表呢?或者嵌套列表?所有这些特殊的情况会使求和成为一个难以理解和使用的操作。
- 通用性
- 足以支持client的所有需求,难度要低
- 操作集应该是足够的,因为必须有足够的操作来完成客户机可能想要执行的计算类型。
- 一个好的测试是检查该类型对象的每个属性都能被提取出来。
- 例如,如果没有get操作,我们将无法找出列表的元素是什么。
- 基本信息不应难以获得
- 例如,size方法对于List来说并不是严格必要的,因为我们可以对递增的索引应用get,直到我们得到一个失败,但是这样做效率低且不方便。
- 要么抽象、要么具体,不要混合---要么针对抽象设计,要么针对具体应用的设计
- 相反,将特定于领域的方法(如dealCards)放入泛型类型List中是没有意义的。
- 设计简洁、一致的操作
5 Representation Independence
- 内部成员变量和方法都发生了变化,但是Client的调用方法是一致的。也就是说:内部变化不影响外部的调用。是独立的。
6 Testing an Abstract Data Type
- 我们通过为ADT的每个操作创建测试来为其构建测试套件。
- 这些测试不可避免地相互影响。
- 测试创建器、生成器和变异器的唯一方法是在产生结果的对象上调用观察器,同样,测试观察器的唯一方法是创建它们要观察的对象。
- 测试creators, producers, and mutators:调用observers来观察这些 operations的结果是否满足spec;
- 测试observers:调用creators, producers, and mutators等方法产生或 改变对象,来看结果是否正确。
- 划分ADT操作的输入空间
- 覆盖所有分区的测试套件
7 Invariants
- ADT的不变量
- 好的抽象数据类型最重要的属性是它保留自己的不变量。
- 在任何时候都是true
- 由ADT负责其不变量,与client端的任何行为无关
- 当一个ADT能够确保它内部的不变量恒定不变(不受使用者/外部影响),我们就说这个ADT保护/保留自己的不变量.
- 为什么需要不变量?
- why
- 总是假设client有意破坏ADT的不变量,防御性编程
- Immutability as a type of Invariants
(1)exposure
- 直接访问它的字段
- 不仅影响不变性,也影响独立性
- 我们无法在不影响所有直接访问这些字段的客户端情况下更改Tweet的实现。
- 不仅影响不变性,也影响独立性
- private和public关键字
- 指出哪些字段和方法只能在类内部访问,哪些字段和方法可以从类外部访问。
- final关键字
- 有助于确保这个不可变类型的字段在对象构造后不会被重新赋值。
-
直接访问引用
-
防御性复制
- 假定用户去毁坏不变性(可能是有意的,也可能是无意的)
- 确保类不变量在任何输入下都有效,以最小化可变性
-
Copy() and Clone()
-
如果任何类型是可变的,请确保您的实现不会返回对其表示形式的直接引用。
-
转移矛盾
- 当复制代价很高时
- 但引发的潜在bug也很多
-
最好的方法是使用immutable的类型,彻底避免表示泄露
-
SUM
8 Rep Invariant and Abstraction Function
- ADT的表示空间和抽象空间
- 表示空间:ADT的表示
- 抽象空间:client看到和使用的值
- 抽象类型的实现者必须对表示值感兴趣,因为实现者的工作是使用表示值空间来实现抽象值空间的假象。
- Mapping between two spaces
- 满射,未必单射,未必双射
- 一些抽象值被多个代表值映射
- 每个抽象值都被一些代表值映射
- 并不是所有代表值都被映射
(1) AF
抽象函数
- R和A之间映射关系的函数,即如何去解释R种的每一个值为A中的每一个值
- 图中的弧线显示了抽象函数。
(2) RI :another important ADT invariants
- A rep invariant that maps rep values to booleans:
- RI : R → boolean
- For a rep value r , RI(r) is true if and only if r is mapped by AF .
- 换句话说,RI告诉我们给定的代表值是否格式良好。
(3) Documenting RI and AF
-
不变量和抽象函数都应该记录在代码中,就在rep本身声明的旁边
-
A集合单独无法决定AF和RI
-
同一抽象类型可以有几种表示形式。
- 一组字符可以同样地表示为一个字符串(如上所示),也可以表示为一个位向量(每个位对应一个可能的字符)。
- 显然,我们需要两个不同的抽象函数来映射这两个不同的代表值空间。
- 不同的内部表示,需要设计不同的AF和RI
- 先确定R,进而指定RI,再利用AF解释。
- 一组字符可以同样地表示为一个字符串(如上所示),也可以表示为一个位向量(每个位对应一个可能的字符)。
-
相同的表示空间RI亦会不同
- 例如,如果我们允许字符串中存在重复项,或要求对字符进行排序,以非减量顺序出现,那么将有相同的rep值空间,但不同的rep不变量。
-
同样的R和RI,也可能会有不同的AF,即解释不同
- 也许我们将连续的字符对解释为子区域,这样字符串代表“acgg”被解释为两个范围对[a-c]和[g-g],因此代表集合{a,b,c,g}。
-
How RI and AF influence ADT design
- 设计ADT
- 选择R和A
- 选择RI--合法的表示值
- 如何解释合法的表示值--映射AF
- 做出具体的解释:每个rep value如何映射到abstract value
- 而且要把这种选择和解释明确写到代码当中
- 设计ADT
-
-
(4) Checking the Rep Invariant 随时检查RI是否满足
- 在所有改变rep的方法内都要检查,调用checkRep()
- Observer方法可以不用,但建议也要检查
- 在每个方法(包括观察器)中执行checkRep()意味着您将更有可能捕获由再现暴露引起的再现不变量违反
9 Beneficent mutation
-
回想一下,当且仅当类型的值在创建后从未更改时,类型才是不可变的。
-
通过对抽象空间A和代表空间R的新理解,我们可以完善这个定义:抽象值永远不应该改变。(A)
- With our new understanding of the abstract space A and rep space R, we can refine this definition: the A 从不改变
-
但是,只要rep值继续映射到相同的抽象值,实现就可以自由地修改rep值,这样客户机就看不到更改。
-
This kind of change is called beneficent mutation (有益的可变性).
-
-
-
- 除以公因子
- 分子分母同时除以相同的公因数,或者同时乘以-1,对抽象函数的结果没有影响
- 但rep变了
- 另一种思考方式是:AF是一个多对一函数,代表值改变为另一个仍然映射到相同的抽象值。
-
-
这种实现者自由通常允许诸如此类的性能改进:
-
-
通过牺牲immutability的部分原则来换取“效率”和“性能”
-
10 Documenting the AF, RI, and Safety from Rep Exposure
(1) Documenting AF and RI
- 在代码中用注释形式记录AF和RI
- 不能简单地写为rep中所有的fields为有效
- RI的工作是精确解释字段值如何有效
- 精确地记录AF:如何解释每一个R值
- 抽象函数的工作是精确定义具体字段值的解释方式。
- 作为一种函数,如果我们将记录的AF替换为实际(合法)字段值,我们应该得到它们所代表的单个抽象值的完整描述。
- As a function, if we take the documented AF and substitute in actual (legal)
field values, we should obtain out a complete description of the single
abstract value they represent. - 作为一种功能,如果我们将记录的AF替换为实际(合法)字段值,我们应该得到它们所代表的单个抽象值的完整描述。
- As a function, if we take the documented AF and substitute in actual (legal)
(2) Documenting rep exposure safety argument
- Another piece of documentation is a rep exposure safety argument
- 表示泄露的安全说明
- 这是一个注释,它检查了代表的每个部分,查看了处理这部分代表的代码(特别是关于客户端的参数和返回值,因为这是代表暴露发生的地方),并给出了代码不暴露代表的原因。
- 给出理由,证明代码并未对外泄露其内部表示
Summary: What an ADT spec may talk about
- ADT的spec里面只能用client可见的内容、返回值和异常
- This includes parameters, return values, and exceptions thrown by its operations.
- Whenever the spec needs to refer to a value of type T, it should describe the value as an abstract value, i.e. mathematical values in the abstract space A.
- 只能用A空间内的值,不能谈论R空间中的任何值和代码内部的细节
- ADT的私有属性,对外严格不可见
- 在代码中以注释的形式写出AF和RI,而不能在Javadoc文档中,防止被外部看到而破坏独立性/信息隐藏。
- 将它们编写为Javadoc注释将作为类型规范的公共部分提交给它们,这将干扰代表独立性和信息隐藏。
- 在代码中以注释的形式写出AF和RI,而不能在Javadoc文档中,防止被外部看到而破坏独立性/信息隐藏。
如何建立不变量
-
不变量是一个对整个程序都成立的属性——在对象的不变量的情况下,它减少到对象的整个生命周期。
-
对象的初始状态不变量为true,在对象发生变化时,不变量也要为true。
-
Translating this in terms of the types of ADT operations:
- 构造器和生产器在创建对象时要确保不变量为true
- 变值器和观察器在执行时必须保持不变性
- 在每个方法return之前,用checkRep()检查不变量是否得以保持。
-
表示泄露的风险:
- 如果rep被暴露,那么对象可能在程序的任何地方被更改,而不仅仅是在ADT的操作中,而且我们不能保证在这些任意更改之后不变式仍然保持不变。
-
So the full rule for proving invariants
-
-
11 ADT invariants replace preconditions
用ADT不变量取代复杂的 Precondition,相当于将复杂的precondition封装到了ADT内部
-
-
It’s safer from bugs
-
It’s easier to understand
-
It’s more ready for change
-
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理