软件构造第三章3

第3章:抽象数据类型(ADT)和面向对象编程(OOP)3.3抽象数据类型(ADT)
3.3抽象数据类型(ADT)
大纲
1.抽象和用户定义的类型2. ADT中的操作分类3.抽象数据类型示例4. ADT的设计原则5.表示独立性(RI)6。在Java中实现ADT概念7.测试ADT
3.3抽象数据类型(ADT)
大纲
1.不变量2. Rep不变量和抽象函数3.有益突变4.记录AF,RI和Rep暴露的安全性5. ADT不变量取代前提条件6.总结
3.3抽象数据类型(ADT)
本讲座的目的
抽象数据类型和表示独立性:使我们能够将程序中数据结构的使用方式与数据结构本身的特定形式分开。 - 抽象数据类型解决了一个特别危险的问题:客户对类型的内部表示做出假设。 - 我们会明白为什么这是危险的以及如何避免它。 - 我们还将讨论操作的分类,以及抽象数据类型的良好设计的一些原则。
3.3抽象数据类型(ADT)
本讲座的目的
不变量,表示暴露,抽象函数(AF)和表示不变量(RI) - 通过抽象函数和rep不变量的概念,对类实现ADT意味着什么的更正式的数学概念。 - 这些数学概念在软件设计中非常实用。 - 抽象函数将为我们提供一种在抽象数据类型上清晰地定义相等操作的方法。 - rep不变量将更容易捕获由损坏的数据结构导致的错误。
软件构建
1抽象和用户定义的类型
3.3抽象数据类型(ADT)
什么抽象意味着什么
抽象数据类型是软件工程中一般原则的一个实例,它有许多名称: - 抽象:用更简单,更高级的想法省略或隐藏低级细节。 - 模块化。将系统划分为组件或模块,每个组件或模块可以与系统的其余部分分开设计,实现,测试,推理和重用。 - 封装。在模块(硬壳或胶囊)周围构建墙壁,以便模块负责其自身的内部行为,并且系统其他部分中的错误不会损害其完整性。 - 信息隐藏。从系统的其余部分隐藏模块实现的详细信息,以便稍后可以更改这些详细信息,而无需更改系统的其余部分。 - 关注点分离。使功能(或“关注”)成为单个模块的责任,而不是将其分布在多个模块中。
3.3抽象数据类型(ADT)
用户定义的类型
编程语言带有内置类型(例如整数,布尔值,字符串等)和内置过程,例如用于输入和输出。 用户可以定义自己的数据类型和过程 - 用户定义的类型。
3.3摘要数据类型(ADT)SE研究人员做出了贡献
Ole-JohanDahl和KristenNygaard Dahl(Simula语言的发明者)AntonyHoare(他开发了许多我们现在用来推理抽象类型的技术)DavidParnas(他创造了信息隐藏这个术语并首先表达了围绕他们封装的秘密组织程序模块)BarbaraLiskov和John Guttag(抽象类型的规范,以及对它们的编程语言支持)
3.3抽象数据类型(ADT)
数据抽象
数据抽象:类型的特征在于您可以对其执行的操作。 - 数字是你可以添加和增加的东西; - 字符串是你可以连接并采用子串的东西; - 布尔值是你可以否定的东西,依此类推。 从某种意义上说,用户已经可以在早期编程语言中定义自己的类型:您可以创建一个记录类型日期,例如,使用日,月和年的整数字段。 但是,使抽象类型变得新颖和不同的是对操作的关注:该类型的用户不需要担心其值是如何实际存储的,就像程序员可以忽略编译器实际存储整数的方式一样。重要的是操作。2分类和操作
3.3抽象数据类型(ADT)
可变和不可变类型
无论是内置类型还是用户定义类型,都可以归类为可变类或不可变类。 - 可变类型的对象可以更改:也就是说,它们提供的操作在执行时会导致同一对象上的其他操作的结果给出不同的结果。 - 所以Date是可变的,因为你可以调用setMonth()并使用getMonth()操作观察更改。 - 但String是不可变的,因为它的操作创建新的String对象而不是改变现有的对象。 - 有时会以两种形式提供类型,即可变形式和不可变形式。例如,StringBuilder是String的可变版本(尽管两者肯定不是相同的Java类型,并且不可互换)。
3.3抽象数据类型(ADT)对抽象类型的操作进行分类
Creators创建该类型的新对象。 - 创建者可以将对象作为参数,但不是正在构造的类型的对象。 生产者从该类型的旧对象创建新对象。 - 例如,String的concat()方法是一个生成器:它接受两个字符串并生成一个表示其串联的新字符串。 观察者采用抽象类型的对象并返回不同类型的对象。 - 例如,List的size()方法返回一个int。 Mutator更改对象。 - 例如,List的add()方法通过向末尾添加元素来改变列表。
3.3抽象数据类型(ADT)对抽象类型的操作进行分类
创建者:t *→T生产者:T +,t *→T观察者:T +,t →tmutator:T +,t →void | t | Ť
每个T都是抽象类型本身; 每个t都是其他类型。 +标记表示该类型可能在签名的该部分中出现一次或多次。 
标记表示它出现零次或多次。 |表示或。
3.3抽象数据类型(ADT)
签名的操作
String.concat()作为生产者 - concat:String×String→StringList.size()作为观察者 - 大小:List→intString.regionMatches作为观察者 - regionMatches:String×boolean×int×String× int×int→布尔值
3.3抽象数据类型(ADT)
创作者的签名
创建者可以实现为构造函数,如新的ArrayList(),也可以只是静态方法,如Arrays.asList()。 - 作为静态方法实现的创建者通常称为工厂方法。 - Java中的各种String.valueOf()方法是作为工厂方法实现的创建者的其他示例。
3.3抽象数据类型(ADT)
签名的变异者
突变体通常由无效返回类型发出信号。 必须为某种副作用调用返回void的方法,否则它不会返回任何内容。 但并非所有的mutator都返回void。 - 例如,Set.add()返回一个布尔值,指示该集是否实际更改。 - 在Java的图形用户界面工具包中,Component.add()返回对象本身,以便可以将多个add()调用链接在一起。
软件构建
3抽象数据类型示例
3.3抽象数据类型(ADT)
INT
int是Java的原始整数类型。 int是不可变的,所以它没有mutators。 - 创建者:数字文字0,1,2,… - 生产者:算术运算符+, - ,
,/ - 观察者:比较运算符==,!=,<,> - mutators:none(它是不可变的)
3.3抽象数据类型(ADT)
名单
列表是Java的列表类型,并且是可变的。 List也是一个接口,这意味着其他类提供了数据类型的实际实现。这些类包括ArrayList和LinkedList。 - creators:ArrayList和LinkedList构造函数,Collections.singletonList - producer:Collections.unmodifiableList - observers:size,get - mutators:add,remove,addAll,Collections.sort串
String是Java的字符串类型。字符串是不可变的。 - creators:String constructors - producer:concat,substring,toUpperCase - observers:length,charAt - mutators:none(it is immutable)
3.3抽象数据类型(ADT)
在Java中实现ADT概念
软件构建
4设计抽象类型
3.3抽象数据类型(ADT)
设计抽象类型
设计抽象类型涉及选择良好的操作并确定它们的行为方式。 经验法则1 - 最好有一些简单的操作可以以强大的方式组合,而不是许多复杂的操作。 - 每项操作都应有明确的目的,并且应该具有连贯的行为,而不是一整套特殊情况。

  • 例如,我们可能不应该向List添加求和操作。它可能会帮助使用整数列表的客户端,但是字符串列表呢?还是嵌套列表?所有这些特殊情况都会使得难以理解和使用。
    3.3抽象数据类型(ADT)
    设计抽象类型
    经验法则2操作集应该足够,因为必须有足够的数量来完成客户可能想要做的计算。
  • 一个好的测试是检查可以提取该类型的对象的每个属性。 - 例如,如果没有get操作,我们将无法找出列表的元素是什么。
  • 基本信息不应该非常难以获得。 - 例如,对于List来说,size方法并不是绝对必要的,因为我们可以在增加索引之前应用get,直到我们出现故障,但这样做效率低且不方便。
    3.3抽象数据类型(ADT)
    设计抽象类型
    经验法则3
    类型可以是通用的:例如列表,集合或图形。或者它可能是特定于域的:街道地图,员工数据库,电话簿等。但它不应混合通用和特定于域的功能。 - 用于表示扑克牌序列的Deck类型不应该具有接受任意对象(如整数或字符串)的通用add方法。 - 相反,将特定于域的方法(如dealCards)放入泛型类型列表中是没有意义的。
    软件构建
    5代表独立
    3.3抽象数据类型(ADT)
    代表独立
    重要的是,一个好的抽象数据类型应该是独立的表示。 - 这意味着抽象类型的使用与其表示(用于实现它的实际数据结构或数据字段)无关,因此表示的更改对抽象类型本身之外的代码没有影响。 例如,List提供的操作与列表是表示为链表还是数组无关。 除非使用前提条件和后置条件完全指定操作,否则您将无法更改ADT的表示形式,以便客户知道要依赖的内容,并且您知道可以安全地更改的内容。
    3.3抽象数据类型(ADT)示例:字符串的不同表示形式
    3.3抽象数据类型(ADT)MyString的简单表示
    现在,让我们看一下MyString的简单表示:只是一个字符数组,正好是字符串的长度,最后没有额外的空间。这是内部表示的声明方式,作为类中的实例变量:
    private char [] a;
    通过选择表示,操作将以直接的方式实施:MyString的相应实现
    3.3抽象数据类型(ADT)另一种表示更好的性能
    由于此数据类型是不可变的,因此子字符串操作实际上不必将字符复制到新数组中。 它可以指向原始的MyString对象的字符数组,并跟踪新的子字符串对象所代表的开始和结束。 要实现此优化,我们可以将此类的内部表示更改为:private char [] a; private int start;私人int end;
    3.3抽象数据类型(ADT)
    现在实施是…
    3.3抽象数据类型(ADT)什么是表示独立性
    由于MyString的现有客户端仅依赖于其公共方法的规范,而不依赖于其私有字段,因此我们可以进行此更改,而无需检查和更改所有客户端代码。 这是代表独立的力量。
    软件构建
    6测试抽象数据类型
    3.3抽象数据类型(ADT)
    如何测试ADT
    我们通过为每个操作创建测试,为抽象数据类型构建测试套件。 这些测试不可避免地相互影响。 测试创建者,生成器和mutator的唯一方法是通过调用结果对象的观察者,同样,测试观察者的唯一方法是创建对象供他们观察。
    3.3抽象数据类型(ADT)划分ADT操作的输入空间
    3.3抽象数据类型(ADT)
    覆盖所有分区的测试套件
    软件构建
    7不变量
    3.3抽象数据类型(ADT)
    ADT的不变量
    良好的抽象数据类型最重要的属性是它保留自己的不变量。 对于程序的每个可能的运行时状态,invariant是程序的属性,它始终为true。 - 不变性是一个至关重要的不变量:一旦创建,不可变对象在其整个生命周期中应始终表示相同的值。 说ADT保留了自己的不变量,即ADT负责确保其自己的不变量成立。 - 它不依赖于客户的良好行为。 - 正确性不依赖于其他模块。 不变量由构造函数建立 - 由公共方法调用维护 - 可能在方法执行期间暂时失效
    3.3抽象数据类型(ADT)
    为什么需要不变量?
    当ADT保留其自己的不变量时,对代码的推理变得更加容易。 如果您可以依赖Strings永远不会改变的事实,那么当您调试使用字符串的代码时 - 或者当您尝试为使用字符串的另一个ADT建立不变量时,可以排除这种可能性。 与字符串类型进行对比,该字符串类型保证仅当客户承诺不更改它时它才是不可变的。然后,您必须检查代码中可能使用该字符串的所有位置。
    …假设客户会试图破坏不变量(恶意黑客或诚实错误)
    软件构建
    8 Rep不变量和抽象函数
    3.3抽象数据类型(ADT)
    两个值的空间
    R:表示值空间(rep值)由实际实现实体的值组成。 - 在简单的情况下,抽象类型将被实现为单个对象,但更常见的是需要一个小的对象网络,因此这个值实际上通常是相当复杂的。 答:抽象值的空间由类型旨在支持的值组成。 - 它们是如所描述的那样不存在的柏拉图式实体,但它们是我们想要查看抽象类型的元素的方式,作为该类型的客户端。 - 例如,无界整数的抽象类型可能将数学整数作为其抽象值空间;例如,它可能被实现为一个原始(有界)整数数组的事实与该类型的用户无关。两个空格的示例
    抽象类型的实现者必须对表示值感兴趣,因为使用rep值空间实现抽象值空间的错觉是实现者的工作。
    例如,假设我们选择使用字符串来表示一组字符:
    然后,rep空间R包含字符串,抽象空间A是数学字符集。
    3.3抽象数据类型(ADT)
    R和A之间的映射
    每个抽象值都映射到一些重复值(满射,满射)。 - 实现抽象类型的目的是支持对抽象值的操作。因此,据推测,我们需要能够创建和操纵所有可能的抽象值,因此它们必须是可表示的。 一些抽象值通过多个重复值(非内射,未必单射)映射到。 - 这是因为表示不是严格的编码。将一组无序字符表示为字符串的方法不止一种。 并非所有的rep值都被映射(不是双射的,未必双射)。 - 在这种情况下,字符串“abbc”未映射。在这种情况下,我们决定字符串不应包含重复项。
    3.3抽象数据类型(ADT)
    抽象功能
    一个抽象函数,它将rep值映射到它们代表的抽象值:
    AF:R→A图中的弧显示抽象函数。 在函数的术语中,属性可以通过说函数是满射的(也称为函数)来表示,不一定是单射的(一对一),因此不一定是双射的,而且通常是部分的。
    3.3抽象数据类型(ADT)
    Rep Invariant
    将rep值映射到布尔值的rep不变量:
    RI:R→布尔对于rep值r,当且仅当r由AF映射时,RI(r)为真。 换句话说,RI告诉我们给定的rep值是否合格。 或者,您可以将RI视为一组:它是定义AF的rep值的子集。
    3.3抽象数据类型(ADT)
    记录RI和AF
    应该在代码中记录rep不变量和抽象函数,就在rep本身的声明旁边:
    3.3抽象数据类型(ADT)
    什么决定AF和RI?
    仅抽象值空间不能确定AF或RI: - 对于相同的抽象类型,可以有多个表示。 - 一组字符同样可以表示为字符串,如上所述,或者作为位向量,每个可能的字符有一位。显然,我们需要两个不同的抽象函数来映射这两个不同的rep值空间。 为代表定义类型,从而选择代表值空间的值,不会确定哪些代表值是合法的,哪些是合法的,它们将如何解释。
    3.3抽象数据类型(ADT)
    什么决定AF和RI?
    例如,如果我们允许字符串中的重复项,但同时要求对字符进行排序,以非递减顺序出现,那么将存在相同的rep值空间但不同的rep不变量。
    3.3抽象数据类型(ADT)
    什么决定AF和RI?
    即使对于rep值空间和相同的rep不变RI具有相同的类型,我们仍然可以使用不同的抽象函数AF来不同地解释rep。 假设RI允许任何字符串。然后我们可以定义AF,如上所述,将数组的元素解释为集合的元素。但是没有先验的理由让代表决定解释。 也许我们将连续的字符对解释为子范围,因此字符串rep“acgg”被解释为两个范围对,[ac]和[gg],因此表示集合{a,b,c,g} 。 这就是AF和RI对于该表示的看法。
    3.3抽象数据类型(ADT)
    什么决定AF和RI?
    3.3抽象数据类型(ADT)RI和AF如何影响ADT设计
    关键点在于,设计抽象类型不仅意味着选择两个空间 - 规范的抽象值空间和实现的rep值空间 - 还要决定使用哪些rep值以及如何解释它们。 正如我们上面所做的那样,在代码中写下这些假设至关重要,这样未来的程序员(以及你未来的自己)就会意识到表示实际意味着什么。为什么?如果不同的实施者不同意代表的意思,会发生什么?示例:Rational Numbers的ADT
    3.3抽象数据类型(ADT)
    这个例子的RI和AF
    RI要求分子/分母对采用缩减形式(即最低项),因此上述(2,4)和(18,12)之类的对应在RI之外绘制。
    3.3抽象数据类型(ADT)
    检查Rep不变量
    rev不变量不仅仅是一个简洁的数学概念。如果您的实现在运行时声明了rep不变量,那么您可以尽早捕获错误。
    在创建或改变rep(创建者,生成者和mutator)的每个操作结束时,你当然应该调用checkRep()来声明rep不变量。回顾上面的RatNum代码,你会看到它在两个构造函数的末尾调用checkRep()。 观察者方法通常不需要调用checkRep(),但无论如何这样做都是很好的防御措施。 - 在每个方法(包括观察者)中调用checkRep()意味着您更有可能捕获由代表暴露引起的代表不变违规。
    3.3抽象数据类型(ADT)
    代表中没有空值
    空值很麻烦且不安全,因此我们尝试将它们完全从编程中删除。 我们方法的前提条件和后置条件隐式要求对象和数组不为空。 我们将该禁令扩展到抽象数据类型的代表。默认情况下,rep不变量隐式包含x!= null,用于具有对象类型的rep中的每个引用x(包括数组或列表中的引用)。
    虽然您不需要在rep不变注释中声明它,但您仍然必须实现x!= null检查,并确保当x为null时,checkRep()正确失败。
    软件构建
    9有益的突变
    3.3抽象数据类型(ADT)
    有益的突变
    当且仅当类型的值在创建后永远不会更改时,回想一下类型是不可变的。 随着我们对抽象空间A和rep空间R的新理解,我们可以改进这个定义:抽象值永远不会改变。 但是实现可以自由地改变rep值,只要它继续映射到相同的抽象值,以便客户端看不到该更改。 这种变化称为慈善突变。
    3.3抽象数据类型(ADT)
    慈善突变的一个例子
    RatNum:此代表具有较弱的rep不变量,不需要以最低的条件存储分子和分母:
    3.3抽象数据类型(ADT)
    慈善突变的一个例子
    这种较弱的rep不变量允许一系列RatNum算术运算简单地省略将结果减少到最低项。但是当它向人类显示结果时,我们首先简化它:
    3.3抽象数据类型(ADT)
    慈善突变的一个例子
    请注意,此toString实现重新分配私有字段分子和分母,改变表示形式 - 即使它是不可变类型的观察者方法! 但是,至关重要的是,突变不会改变抽象值。 将分子和分母除以相同的公因子,或者将它们乘以-1,对抽象函数的结果没有影响,AF(分子,分母)=分子/分母。 另一种思考方式是AF是一对多的功能,并且rep值已经改变为另一个仍然映射到相同抽象值的值。 所以突变是无害的,或者是有益的。
    3.3抽象数据类型(ADT)为什么需要有益的突变?
    这种实现者自由通常允许性能改进,如: - 缓存 - 数据结构重新平衡 - 懒惰计算 - 懒惰清理
    软件构建
    10记录来自Rep曝光的AF,RI和安全性记录AF和RI
    优良作法是在类中声明抽象函数和rep不变量,使用注释的私有字段声明。 另一份文件是代表性安全论点。这是一个检查代表的每个部分的注释,查看处理代理部分的代码(特别是关于参数和客户端的返回值,因为这是代理暴露发生的地方),并提供了为什么代码不公开代表。
    3.3抽象数据类型(ADT)
    记录AF和RI:示例1
    3.3抽象数据类型(ADT)
    记录AF和RI:示例2
    3.3抽象数据类型(ADT)
    如何建立不变量
    不变量是一个对整个程序都适用的属性 - 在对象不变的情况下,它会缩短到对象的整个生命周期。 要进行不变的保持,我们需要: - 在对象的初始状态下使不变量成立; - 确保对象的所有更改都保持不变。 根据ADT操作的类型进行翻译,这意味着: - 创建者和生产者必须为新的对象实例建立不变量; - 变异者和观察者必须保留不变量。
    3.3抽象数据类型(ADT)
    如何建立不变量
    重复曝光的风险使情况更加复杂。如果代理被暴露,那么对象可能会在程序中的任何地方被更改,而不仅仅是在ADT的操作中,并且我们不能保证在这些任意更改之后仍然保持不变量。 因此,证明不变量的完整规则是:结构归纳—如果抽象数据类型的不变量是 - 由创建者和生产者建立的; - 由变异者和观察者保存; - 没有表示暴露发生,那么抽象数据类型的所有实例的不变量都是正确的。
    软件构建
    11 ADT不变量取代了先决条件
    3.3抽象数据类型(ADT)ADT不变量替换前置条件
    精心设计的抽象数据类型的一个巨大优势是它封装并强制执行我们原本必须在前提条件中规定的属性。
    软件构建
    摘要
    3.3抽象数据类型(ADT)
    摘要
    抽象数据类型以其操作为特征。 操作可分为创建者,生产者,观察者和变异者。 ADT的规范是它的一组操作和规范。 良好的ADT是简单,连贯,充分和独立的表现。 通过为每个操作生成测试来测试ADT,但在相同的测试中一起使用创建者,生产者,变异者和观察者。
    3.3抽象数据类型(ADT)
    摘要
    不变量是对象的生命周期中始终为ADT对象实例的属性。 良好的ADT保留其自己的不变量。不变量必须由创作者和制作者建立,并由观察者和变异者保存。 recinvariant指定表示的合法值,并应在运行时使用checkRep()进行检查。 抽象函数将具体表示映射到它表示的抽象值。 代表性暴露威胁到代表独立性和不变保存。
    3.3抽象数据类型(ADT)
    摘要
    免受错误的影响。良好的ADT保留其自己的不变量,因此这些不变量不易受到ADT客户端中的错误的影响,并且在ADT本身的实现中可以更容易地隔离违反不变量的情况。明确说明rep不变量,并在运行时使用checkRep()检查它,更早地捕获误解和错误,而不是继续使用损坏的数据结构。 易于理解。 Rep不变量和抽象函数阐明了数据类型表示的含义,以及它与抽象的关系。 准备好改变。抽象数据类型将抽象与具体表示分开,这使得可以在不必更改客户端代码的情况下更改表示。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值