软件构造复习(5)
(1)表示不变量和抽象函数
几个概念
表示域(space of representation values)里面包含的是值具体的实现实体。
抽象域里面包含的则是类型设计时支持使用的值。这些值是由表示域“抽象/想象”出来的,也是使用者关注的。
每一个抽象值都是由表示值映射而来:我们之前说过实现抽象类型的意义在于支持对于抽象值的操作,即我们需要能够创建和管理所有的抽象值,因此它们也必须是可表示的。
一些抽象值是被多个表示值映射而来的:这是因为表示方法并不是固定的,我们可以灵活的表示一个抽象值。
不是所有的表示值都能映射到抽象域中:在上面这个例子中,“abbc”就没有被映射。因为我们已经确定了表示值的字符串中不能含有重复的字符——这样我们的 remove 方法就能在遇到第一个对应字符的时候停止,因为我们知道没有重复的字符。
总之,一个ADT的实现不仅是选择表示域(规格说明)和抽象域(具体实现),同时也要决定哪一些表示值是合法的(表示不变量),合法表示会被怎么解释/映射(抽象函数)。
检查表示不变量
表示不变量不仅是一个简洁的数学概念,你还可以通过断言检查它的不变属性来动态捕捉bug。
这里举出一种检查的方法:
// Check that the rep invariant is true
// *** Warning: this does nothing unless you turn on assertion checking
// by passing -enableassertions to Java
private void checkRep() {
assert denominator > 0;
assert gcd(Math.abs(numerator), denominator) == 1;
}
应该在每一个创建或者改变表示数据的操作后调用 checkRep() 检查不变量,换句话说,就是在使用创建者、生产者以及改造者之后。
AF, RI以及表示暴露安全性的注解
应该在抽象类型(私有的)表示声明后写上对于抽象函数和表示不变量的注解,这是一个好的实践要求。
当在描述抽象函数和表示不变量的时候,注意要清晰明确:
对于RI(表示不变量),仅仅宽泛的说什么区域是合法的并不够,你还应该说明是什么使得它合法/不合法。
对于AF(抽象函数)来说,仅仅宽泛的说抽象域表示了什么并不够。抽象函数的作用是规定合法的表示值会如何被解释到抽象域。作为一个函数,我们应该清晰的知道从一个输入到一个输入是怎么对应的。
将表示暴露的安全性注释出来,这种注释应该说明表示的每一部分,它们为什么不会发生表示暴露,特别是处理的表示的参数输入和返回部分(这也是表示暴露发生的位置)。
下面是一个Tweet类的例子,它将表示不变量和抽象函数以及表示暴露的安全性注释了出来:
// Immutable type representing a tweet.
public class Tweet {
private final String author;
private final String text;
private final Date timestamp;
// Rep invariant:
// author is a Twitter username (a nonempty string of letters, digits, underscores)
// text.length <= 140
// Abstraction function:
// AF(author, text, timestamp) = a tweet posted by author, with content text,
// at time timestamp
// Safety from rep exposure:
// All fields are private;
// author and text are Strings, so are guaranteed immutable;
// timestamp is a mutable Date, so Tweet() constructor and getTimestamp()
// make defensive copies to avoid sharing the rep's Date object with clients.
// Operations (specs and method bodies omitted to save space)
public Tweet(String author, String text, Date timestamp) { ... }
public String getAuthor() { ... }
public String getText() { ... }
public Date getTimestamp() { ... }
}
可以看到,对于不可变类型的表示,表示暴露的安全性说明会简单很多。
(2)adt的四种操作
抽象类型:强调"作用于数据上的操作",程序员和client无需关心数据如何具体存储,只需设计/使用操作即可。
ADT由操作定义,与其内部实现无关。
可变数据类型:提供了可改变其内部数据值的操作;
不可变数据类型:其操作不改变内部值,而构造新的对象。(没有mutators)
ADT操作分类
Creators 构造器:
不利用该类型对象产生一个新的对象
可能实现为构造函数或静态函数(factory method)
Producers 生产器:
用已有该类型对象产生新对象
如string.concat()(连接两个字符串,产生一个新的字符串)
Observers 观察器
如list.size()返回int(不同于原类型)
Mutators 变值器(改变对象属性的方法)
通常范围void,如果返回void,则必然意味着它改变了某些对象的内部状态
也可能范围非空类型(如容器类的put、add方法)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理