软件构造第六讲:ADT
第六讲抽象数据类型(ADT)
ADT和表示独立性
信息隐藏——表示泄露
1.抽象和用户自定义类
抽象类型:强调“作用在数据上的操作”,程序员和client无需关心数据如何具体存储的,只需设计/使用操作即可。
2.类中类型和操作
考:区分操作是哪种类型操作,根据输入输出类型
可变和不可变数据类型
可变数据类型的对象:提供了可改变其内部数据的值的操作
不可变数据类型:其操作不改变内部值,而是构造新的对象
抽象类型的操作类别:
- 构造器creators:——无中生有
其他类型生成当前类型
可能实现为构造函数或静态函数
工厂方法(设计模式)
若包含了静态方法会暴露了接口的实现,造成信息泄露 - 生产器producers:——旧—>新
当前类型生成当前类型 - 观察器observes:
当前类型生成其他类型 - 变值器mutatos——改变对象属性的方法
直接改变类中属性,通常返回void,若返回值为void,则必然意味着它改变了对象的某些内部状态,变值器也可能返回非空类型
mutable类型的对象包含四种操作,immutable只包含前三种
区别有无变值器
3.抽象数据类型的例子
immutable:int,string
mutable:List
4.设计抽象类型
设计好的ADT,靠“经验法则”,提供一组操作,设计其行为规约spec
设计规则
- 设计简洁、一致的操作
- 足够多的操作来满足client的需要,而且用操作满足client需要的难度要低
5.表示独立性RI
表示独立性:client使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不应影响外部spec和客户端
只能用类提供的方法来改变类的属性,client不能通过引用直接改变属性
所有属性不能都是public
不能直接返回mutable类型的数据,解决方法:defensive copy或者用claction将其变为immutable
表示暴露 public->private mutable不可直接返回 immutable可以直接返回 若想改变immutable只能另外开辟地址
6.测试ADT
测试creators,producers,mutators:调用observers来观察这些operations的结果是否满足spec;
测试observers:调用creators,producers,mutators等方法产生或改变对象,来看结果是否正确。
风险:如果被依赖的其他方法有错误,可能导致被测试方法的测试结果失效。
针对creators:构造对象之后,用observer去观察是否正确
针对observer:用其他三类方法构造对象,然后调用被测observer,判断观察结果是否正确
针对producer:produce新对象之后,用observer判断结果是否正确
7.Invariants不变性
保持不变量:在任何时候不变量都是true
immutability就是一个典型的不变量
由ADT负责不变量,与client端的任何行为无关
==不变量:
1.immutable;
2.表示独立性 :只能通过类操作改变;
3.属性大小、前后关系保持不变 ==
表示泄露RE:客户能够直接访问到属性
不仅影响不变性,也影响了表示独立性,无法在不影响客户端输入的情况下改变其内部表示。
final:指向的位置不变,但值可变,不能阻止表示暴露
除非迫不得已,否则不要寄希望于客户端(在规约中限制客户端的输入),ADT有责任保证自己的不变性,并避免表示泄露。
最好的方法是使用immutable类型
保持不变性和保持表现 独立性,是ADT最重要的一个Invariant!
8.Rep Invariant and Abstraction Function
R:表示空间,程序员能看到
A:抽象空间,抽象值构成的空间,client看到和使用的值,程序员也能看到
R到A空间满足:
满射:每一个A中的值在R中都能找到原象
未必单射:一些A中的值可能被不止一个R中的值映射到
未必双射:并非R中的值都参与了映射
AF:R->A 抽象函数,R和A之间映射关系的函数,即如何去解释R中的每一个值为A中的每一个值。
RI: R->boolean RI(r)=true<=>r被AF映射到
表示不变性RI:某个具体的“表示”是否是“合法的”
也可将RI看作:所有表示值的一个子集,包含了所有合法的表示值
也可将RI看作:一个条件,描述了什么是“合法”的表示值
rep invarian和abstraction function 应写在rep声明下
什么决定AF和RI
即使是同样的R、同样的RI,也有可能有不同的AF,即“解释不同”
RI和AF如何影响ADT设计
设计ADT:
(1)选择R和A 选择属性的数据结构
(2)RI——合法的表示值
(3)如何解释合法的表示值——映射AF
2和3取决于数据类型的设计
做出具体的解释:每个rep value如何映射到abstract value
而且要把这种选择和解释明确写到代码当中
对于一个ADT:
用户应该知道:Abstract value space,Creators,Observers
方法的声明可以看到,但看不到具体实现
开发者应该知道:Abstract value space,Creators,Observers,Abtraction function,Rep,Rep invariant
随时检查RI是否满足:
checkRep()插入到每一个产生或者改变rep的方法(creators,producers,mutators)末尾
(在所有可能改变rep的方法内部都要检查)
Observer方法可以不用,但建议也要检查,以防万一。
9.Beneficent mutation有益的可变性
immutable类型<=>在之后的产生中这种类型的值不会改变
(immutable可以出现改变属性的方法,要求改变后的值对应A空间的值不变)
抽象空间里的值不应该改变
可以自由的改变rep value,只要它映射到相同的abstract value,这种改变用户不可知
这种改变称为有益的可变性
10.Documenting the AF,RI,and Safety from Rep Exposure
在代码中用注释形式记录AF和RI
1.要精确的记录RI:
rep中的所有fields何为有效。针对Rep的每一个field以及多个fields之间的关系,进行条件限定,要精确
2.要精确记录AF:
如何解释每一个R值。给出client看到的A值是什么,是对每一个Rep值的“数学运算”
3.表示泄露的安全声明:
给出理由,证明代码并未对外泄露其内部表示——自证清白
ADT的规约
只能使用client可见的内容来撰写,包括参数、返回值、异常等
如果规约里需要提及值,只能使用A空间中的“值”。
ADT的规约里也不应该谈及任何内部表示的细节,以及R空间中的任何值
ADT的内部表示(私有属性)对外部都应该严格不可见
在代码中以注释形式写出AF和RI而不能在Javadoc文档中,防止被外部看到而破坏表示独立性/信息隐藏
如何确定不变性
在对象的初始状态不变量为true,而在对象发生变化时,不变量也要为true。
构造器和产生器在 创建对象时要确保不变量为true
变值器和观察器在执行时必须保持不变性
在每个方法return之前,用checkRep()检查不变量是否得以保持。
表示暴露的风险:一旦泄露,ADT内部表示可能会在程序的任何位置发生改变(而不是限制在ADT内部),从而无法确保ADT的不变量是否能够始终保持为true。
11.ADT invariants replace preconditions
用ADT不变量取代复杂的Precondition,相当于将复杂的precondition封装到了ADT内部。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理