Reading 12:Abstract Data Types

Reading 12: Abstract Data Types
阅读12 抽象数据类型
Software in 6.031
软件6.031
Safe from bugs
安全漏洞 Easy to understand
容易理解 Ready for change
随时可以改变
Correct today and correct in the unknown future.
今天是正确的并且在未知的未来也是正确的。 Communicating clearly with future programmers, including future you.
与未来的程序员能清晰地交流,包括未来的你。 Designed to accommodate change without rewriting.
设计以适应更改而无需重新编写。
Objectives 目标
Today’s class introduces two ideas:
• Abstract data types
• Representation independence
In this reading, we look at a powerful idea, abstract data types, which enable us to separate how we use a data structure in a program from the particular form of the data structure itself.
Abstract data types address a particularly dangerous problem: clients making assumptions about the type’s internal representation. We’ll see why this is dangerous and how it can be avoided. We’ll also discuss the classification of operations, and some principles of good design for abstract data types.
今天的课程介绍了两种概念
抽象数据类型
表示独立
在今天这篇阅读中,我们可以看到一个强大的思想,抽象数据类型,他能够使我们在程序中如何使用数据结构与数据结构本身的特定形式区分开来。
抽象数据类型解决一个特别的危险问题:客户会关于类型内部的表示做出假设。我们可以知道为什么他是危险的并且如何如何能够避免。我们会讨论操作的分类,以及为抽象数据类型有好的设计的一些原则。
Access control in java
You should already have read: Controlling Access to Members of a Class in the Java Tutorials.
READING EXERCISES
The following questions use the code below. Study it first, then answer the questions.
在java中的访问控制
你应该已经读过了:控制对Java教程中类成员的访问。
阅读练习
下面的问题使用下面的代码。开始学习他,然后回答问题。
class Wallet { private int amount; public void loanTo(Wallet that) { // put all of this wallet’s money into that wallet/A/ that.amount += this.amount;/B/ amount = 0; } public static void main(String[] args) {/C/ Wallet w = new Wallet();/D/ w.amount = 100;/E/ w.loanTo(w); } } class Person { private Wallet w; public int getNetWorth() {/F/ return w.amount; } public boolean isBroke() {/G/ return Wallet.amount == 0; } }
Access control A (访问控制A)
Access control B (访问控制B)
Access control C (访问控制C)
Access control D (访问控制D)
Access control E (访问控制E)
Access control F (访问控制F)
Access control G (访问控制G)
What abstraction means
Abstract data types are an instance of a general principle in software engineering, which goes by many names with slightly different shades of meaning. Here are some of the names that are used for this idea:
• Abstraction. Omitting or hiding low-level details with a simpler, higher-level idea.
• Modularity. Dividing a system into components or modules, each of which can be designed, implemented, tested, reasoned about, and reused separately from the rest of the system.
• Encapsulation. Building a wall around a module (a hard shell or capsule) so that the module is responsible for its own internal behavior, and bugs in other parts of the system can’t damage its integrity.
• Information hiding. Hiding details of a module’s implementation from the rest of the system, so that those details can be changed later without changing the rest of the system.
• Separation of concerns. Making a feature (or “concern”) the responsibility of a single module, rather than spreading it across multiple modules.
抽象意味着什么
抽象数据类型是软件工程中一般原则的实例。他有许多名字,但含义略有不同。这有一些用于表达这个想法的名字。
抽象。用简单高层次的想法来省略或隐藏低层次的细节。
模块。将系统分为组件或模块,每一个组件或模块都可以独立于系统的其他部分进行设计,实现,测试,解释和重用。
封装。在模块的周围组建一个墙(坚硬的外壳或胶囊)以至于模块会对它自己内部的行为负责,但系统中一部分的漏洞不能破坏他的整体性
信息隐藏。向系统的其他部分隐藏模块实现的细节,以便于以后在没有改变系统其他部分这些细节可以被改变。
关注点分类。让一个特性(或“关注点”)由单个模块负责,而不是将其分散到多个模块。
As a software engineer, you should know these terms, because you will run into them frequently. The fundamental purpose of all of these ideas is to help achieve the three important properties that we care about in 6.031: safety from bugs, ease of understanding, and readiness for change.
作为一名软件工程师,你应该了解这些术语,因为你会经常遇到它们。所有这些想法的根本目的是帮助实现我们在6.031中关心的三个重要属性:bug的安全性、易于理解性和变更的准备性。
We have in fact already encountered some of these ideas in previous classes, in the context of writing methodsthat take inputs and produce outputs:
• Abstraction: A spec is an abstraction in that the client only has to understand its preconditions and postconditions to use it, not the full internal behavior of the implementation.
• Modularity: Unit testing and specs help make methods into modules.
• Encapsulation: The local variables of a method are encapsulated, since only the method itself can use or modify them. Contrast with global variables, which are quite the opposite, or local variables pointing to mutable objects that have aliases, which also threaten encapsulation.
• Information hiding: A spec uses information-hiding to leave the implementer some freedom in how the method is implemented.
• Separation of concerns: A spec is coherent if it is responsible for just one concern.
Starting with today’s class, we’re going to move beyond abstractions for methods, and look at abstractions for data as well. But we’ll see that methods will still play a crucial role in how we describe data abstraction.
事实上,我们在之前的课程中已经遇到了一些这样的想法,在输入和输出的写作方法的背景下:
• 抽象:规范是一种抽象,客户端只需要了解它的前置条件和后置条件就可以使用它,而不需要了解实现的完整内部行为。
• 模块化:单元测试和规范有助于将方法变成模块。
• 封装:方法的局部变量被封装,因为只有方法本身可以使用或修改它们。与之相反的是全局变量,或者指向具有别名的可变对象的局部变量,这也会威胁封装。
• 信息隐藏:规范使用信息隐藏让实现者在如何实现方法方面有一定的自由度。
• 关注点分离:如果一个规范只负责一个关注点,那么它就是连贯的。
从今天的课程开始,我们将超越对方法的抽象,并了解对数据的抽象。但是我们将看到方法在如何描述数据抽象方面仍然扮演着至关重要的角色。
User-defined types
In the early days of computing, a programming language came with built-in types (such as integers, booleans, strings, etc.) and built-in procedures, e.g., for input and output. Users could define their own procedures: that’s how large programs were built.
A major advance in software development was the idea of abstract types: that one could design a programming language to allow user-defined types, too. This idea came out of the work of many researchers, notably Dahl (the inventor of the Simula language), Hoare (who developed many of the techniques we now use to reason about abstract types), Parnas (who coined the term information hiding and first articulated the idea of organizing program modules around the secrets they encapsulated), and here at MIT, Barbara Liskov and John Guttag, who did seminal work in the specification of abstract types, and in programming language support for them – and developed the original 6.170, the predecessor to 6.005, predecessor to 6.031. Barbara Liskov earned the Turing Award, computer science’s equivalent of the Nobel Prize, for her work on abstract types.
The key idea of data abstraction is that a type is characterized by the operations you can perform on it. A number is something you can add and multiply; a string is something you can concatenate and take substrings of; a boolean is something you can negate, and so on. In a sense, users could already define their own types in early programming languages: you could create a record type date, for example, with integer fields for day, month, and year. But what made abstract types new and different was the focus on operations: the user of the type would not need to worry about how its values were actually stored, in the same way that a programmer can ignore how the compiler actually stores integers. All that matters is the operations.
In Java, as in many modern programming languages, the separation between built-in types and user-defined types is a bit blurry. The classes in java.lang, such as Integer and Boolean are built-in in the sense that the Java language specification requires them to exist and behave in a certain way, but they are defined using the same class/object abstraction as user-defined types. But Java complicates the issue by having primitive types that are not objects. The set of these types, such as int and boolean, cannot be extended by the user.
用户定义的类型
在计算的早期,一种编程语言带有内置类型(如整数、布尔值、字符串等)和内置过程,例如用于输入和输出。用户可以定义自己的过程:大型程序就是这样构建的。
软件开发的一个主要进步是抽象类型的思想:人们可以设计一种允许用户定义类型的编程语言。这个想法出来的许多研究人员的工作,特别是达尔(Simula语言)的发明者,霍尔(开发我们现在使用的许多技术思考抽象类型),Parnas将(他创造了这个词信息隐藏和第一铰接的想法在秘密组织程序模块封装),在麻省理工学院,Barbara Liskov和John Guttag,他们在抽象类型的规范,以及对它们的编程语言支持方面做了开创性的工作,他们开发了最初的6.170,是6.005的前身,也是6.031的前身。芭芭拉·利斯科夫因其在抽象类型方面的研究而获得了图灵奖,这是计算机科学领域的诺贝尔奖。
数据抽象的关键思想是,类型由您可以对其执行的操作来表征。数字是你可以加和乘的东西;字符串是你可以连接并获取子字符串的东西;布尔值是可以否定的,等等。在某种意义上,用户已经可以在早期的编程语言中定义他们自己的类型:例如,您可以创建一个记录类型date,其中包含表示日、月和年的整数字段。但是抽象类型的新特点和不同之处在于它关注的是操作:该类型的用户不需要担心它的值实际上是如何存储的,就像程序员可以忽略编译器实际上如何存储整数一样。重要的是操作。
在Java中,就像在许多现代编程语言中一样,内置类型和用户定义类型之间的区分有点模糊。java中的类。在Java语言规范要求它们以某种方式存在和行为的意义上,诸如Integer和Boolean之类的lang是内置的,但它们使用与用户定义类型相同的类/对象抽象来定义。但是Java的基本类型不是对象,这使得问题更加复杂。这些类型的集合,如int和boolean,不能由用户扩展。
READING EXERCISES
Abstract Data Types
Classifying types and operations
Types, whether built-in or user-defined, can be classified as mutable or immutable. The objects of a mutable type can be changed: that is, they provide operations which when executed cause the results of other operations on the same object to give different results. So Date is mutable, because you can call setMonthand observe the change with the getMonth operation. But String is immutable, because its operations create new String objects rather than changing existing ones. Sometimes a type will be provided in two forms, a mutable and an immutable form. StringBuilder, for example, is a mutable version of String(although the two are certainly not the same Java type, and are not interchangeable).
The operations of an abstract type are classified as follows:
• Creators create new objects of the type. A creator may take an object as an argument, but not an object of the type being constructed.
• Producers create new objects from old objects of the type. The concat method of String, for example, is a producer: it takes two strings and produces a new one representing their concatenation.
• 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 change objects. The add method of List, for example, mutates a list by adding an element to the end.
We can summarize these distinctions schematically like this (explanation to follow):
• creator : t* → T
• producer : T+, t* → T
• observer : T+, t* → t
• mutator : T+, t* → void | t | T
These show informally the shape of the signatures of operations in the various classes. Each T is the abstract type itself; each t is some other type. The + marker indicates that the type may occur one or more times in that part of the signature, and the * marker indicates that it occurs zero or more times. | indicates or. For example, a producer may take two values of the abstract type T, like String.concat() does:
• concat : String × String → String
Some observers take zero arguments of other types t, such as:
• size : List → int
… and others take several:
• regionMatches : String × boolean × int × String × int × int → boolean
A creator operation is often implemented as a constructor, like new ArrayList(). But a creator can simply be a static method instead, like Arrays.asList(). A creator implemented as a static method is often called a factory method. The various String.valueOf methods in Java are other examples of creators implemented as factory methods.
Mutators are often signaled by a void return type. A method that returns void must be called for some kind of side-effect, since it doesn’t otherwise return anything. But not all mutators return void. For example, Set.add() returns a boolean that indicates whether the set was actually changed. In Java’s graphical user interface toolkit, Component.add() returns the object itself, so that multiple add() calls can be chained together.
阅读训练
抽象数据类型
分类类型和操作
类型,无论是内置类型还是用户定义类型,都可以分为可变类型和不可变类型。可变类型的对象是可以更改的:也就是说,它们提供的操作在执行时会导致同一对象上其他操作的结果产生不同的结果。因此,Date是可变的,因为您可以调用setmonth并使用getMonth操作观察更改。但是String是不可变的,因为它的操作创建了新的String对象,而不是改变现有的对象。有时,类型将以两种形式提供,可变形式和不可变形式。例如,StringBuilder是String的可变版本(尽管两者肯定不是相同的Java类型,并且不能互换)。
抽象类型的操作分类如下:
•创建者创建该类型的新对象。创建者可以接受一个对象作为参数,但不能接受正在构造的类型的对象。
•生产者从该类型的旧对象创建新对象。例如,String的concat方法是一个生产者:它接受两个字符串并生成一个表示它们的连接的新字符串。
•观察者接受抽象类型的对象并返回不同类型的对象。例如,List的size方法返回一个int值。
•调整器改变对象。例如,List的add方法通过在末尾添加元素来改变列表。
我们可以这样概述这些区别(解释如下):
•创作者:t*→t
•生产者:T+, T *→T
•观察者:T+, T *→T
•突变: T+, T *→void | T | T
它们非正式地显示了各种类中操作签名的形状。每个T都是抽象类型本身;每一个t都是另一种类型。+标记表示该类型可以在签名的那部分出现一次或多次,*标记表示它出现0次或多次。|表示或。例如,生产者可以接受两个抽象类型T的值,就像String.concat()所做的那样:
•concat:字符串×字符串→字符串
有些观察者对其他类型的t取零参数,例如:
•大小:列表→int
还有一些人选择了几个:
•区域匹配: String × boolean × int × String × int × int→boolean
创建者操作通常作为构造函数实现,如new ArrayList()。但创建者可以是一个静态方法,如Arrays.asList()。作为静态方法实现的创建者通常称为工厂方法。不同的字符串。Java中的valueOf方法是作为工厂方法实现的其他创建者示例。
变量通常由空返回类型来表示。必须调用返回void的方法来产生某种副作用,因为否则它不会返回任何东西。但并不是所有的变式都返回void。例如,set .add()返回一个布尔值,该值指示集合是否实际被更改。在Java的图形用户界面工具包中,Component.add()返回对象本身,因此多个add()调用可以链接在一起。
Abstract data type examples
Here are some examples of abstract data types, along with some of their operations, grouped by kind.
int is Java’s primitive integer type. int is immutable, so it has no mutators.
• creators: the numeric literals 0, 1, 2, …
• producers: arithmetic operators +, -, , /
• observers: comparison operators ==, !=, <, >
• mutators: none (it’s immutable)
List is Java’s list type. List is mutable. List is also an interface, which means that other classes provide the actual implementation of the data type. These classes include ArrayList and LinkedList.
• creators: ArrayList and LinkedList constructors, Collections.singletonList
• producers: Collections.unmodifiableList
• observers: size, get
• mutators: add, remove, addAll, Collections.sort
String is Java’s string type. String is immutable.
• creators: String constructors, valueOf static methods
• producers: concat, substring, toUpperCase
• observers: length, charAt
• mutators: none (it’s immutable)
This classification gives some useful terminology, but it’s not perfect. In complicated data types, there may be an operation that is both a producer and a mutator, for example. Some people reserve the term producer only for operations that do no mutation.
抽象数据类型示例
下面是一些抽象数据类型的例子,以及它们的一些操作,按种类分组。
int是Java的基本整数类型。int是不可变的,所以它没有变量。
•创造者:数字文字0,1,2,…
•生产者:算术运算符+,-,
,/
•观察者:比较运算符==,!=,<,>
•mutators: none(它是不可变的)
List是Java的列表类型。列表是可变的。List也是一个接口,这意味着其他类提供了数据类型的实际实现。这些类包括ArrayList和LinkedList。
•创建者:ArrayList和LinkedList构造函数,Collections.singletonList
•生产商:Collections.unmodifiableList
•观察员:大小,得到
•调整器: add, remove, addAll, Collections.sort
String是Java的字符串类型。字符串是不可变的。
•创建者:字符串构造函数,静态方法的值
•生产者:concat,子字符串,顶级大小写
•观察员:长度,charAt
•调整器: none(它是不可变的)
这种分类提供了一些有用的术语,但并不完美。例如,在复杂的数据类型中,可能有一个操作同时是生成器和变值器。有些人只把不发生突变的操作保留为生产者这个术语。
READING EXERCISES
Operations
An abstract type is defined by its operations
The essential idea here is that an abstract data type is defined by its operations.
阅读训练
操作
抽象类型是由它的操作定义的
这里的基本思想是抽象数据类型由其操作定义。

The set of operations for a type T, along with their specifications, fully characterize what we mean by T. So, for example, when we talk about the Listtype, what we mean is not a linked list or an array or any other specific data structure for representing a list. Instead we mean a set of opaque values – the possible objects that can have Listtype – that satisfy the specifications of all the operations of List: get(), size(), etc.
The values of an abstract type are opaque in the sense that a client can’t examine the data stored inside them, except as permitted by operations. Expanding our metaphor of a specification firewall, you might picture values of an abstract type as hard shells, hiding not just the implementation of an individual function, but of a set of related functions (the operations of the type) and the data they share (the private fields stored inside values of the type).
操作的集合类型T,连同他们的规格,充分描述我们所说的T .所以,例如,当我们谈论List type,我们的意思并不是一个链表或数组或其他任何特定的数据结构表示一个列表。相反,我们指的是一组不透明的值——可能具有List type的对象——满足List所有操作的规范:get()、size()等。
抽象类型的值是不透明的,因为客户端不能检查存储在其中的数据,除非操作允许。扩大我们的隐喻规范防火墙,你可能画值抽象类型的壳一样硬,隐藏不仅仅是一个单独的函数的实现,但一组相关的函数(类型)的操作和数据共享(的私有字段存储在值类型)。
Designing an abstract type
Designing an abstract type involves choosing good operations and determining how they should behave. Here are a few rules of thumb.
It’s better to have a few, simple operations that can be combined in powerful ways, rather than lots of complex operations.
Each operation should have a well-defined purpose, and should have a coherent behavior rather than a panoply of special cases. We probably shouldn’t add a sum operation to List, for example. It might help clients who work with lists of integers, but what about lists of strings? Or nested lists? All these special cases would make sum a hard operation to understand and use.
The set of operations should be adequate in the sense that there must be enough to do the kinds of computations clients are likely to want to do. A good test is to check that every property of an object of the type can be extracted. For example, if there were no get operation, we would not be able to find out what the elements of a list are. Basic information should not be inordinately difficult to obtain. For example, the sizemethod is not strictly necessary for List, because we could apply get on increasing indices until we get a failure, but this is inefficient and inconvenient.
The type may be generic: a list or a set, or a graph, for example. Or it may be domain-specific: a street map, an employee database, a phone book, etc. But it should not mix generic and domain-specific features. A Deck type intended to represent a sequence of playing cards shouldn’t have a generic add method that accepts arbitrary objects like integers or strings. Conversely, it wouldn’t make sense to put a domain-specific method like dealCards into the generic type List.
设计抽象类型
设计一个抽象类型包括选择好的操作和确定它们应该如何运行。以下是一些经验法则。
最好有几个简单的操作,它们可以以强大的方式组合在一起,而不是有许多复杂的操作。
每个行动都应该有明确的目的,应该有连贯的行为,而不是一堆特殊情况。例如,我们可能不应该向List添加求和操作。它可能会帮助处理整数列表的客户端,但是字符串列表呢?或者嵌套列表?这些特殊情况使得求和运算很难理解和使用。
操作集应该是足够的,因为必须有足够的操作来完成客户端可能想要做的各种计算。一个好的测试是检查该类型对象的每个属性是否都可以提取。例如,如果没有get操作,我们将无法找出列表的元素是什么。基本信息应该不难获得。例如,对于List来说,size method并不是严格必需的,因为我们可以在不断增加的索引上应用get,直到出现故障,但这是低效且不方便的。
类型可以是泛型的:例如列表或集合,或者图形。或者它可能是特定于领域的:街道地图、员工数据库、电话本等。但是它不应该混合通用特性和特定于领域的特性。打算表示扑克牌序列的Deck类型不应该有接受任意对象(如整数或字符串)的通用add方法。相反,将特定于领域的方法(如deal Cards)放到泛型类型列表中是没有意义的。
Representation independence
Critically, a good abstract data type should be representation independent. This means that the use of an abstract type is independent of its representation (the actual data structure or data fields used to implement it), so that changes in representation have no effect on code outside the abstract type itself. For example, the operations offered by List are independent of whether the list is represented as a linked list or as an array.
You won’t be able to change the representation of an ADT at all unless its operations are fully specified with preconditions and post conditions, so that clients know what to depend on, and you know what you can safely change.
表示独立
严格地说,一个好的抽象数据类型应该是独立于表示的。这意味着抽象类型的使用独立于它的表示(用于实现它的实际数据结构或数据字段),因此表示中的变化对抽象类型本身之外的代码没有影响。例如,List提供的操作与List表示为链表还是数组无关。
除非用前置条件和后置条件完全指定了ADT的操作,这样客户端就知道要依赖什么,而您也知道可以安全地更改什么,否则您根本无法更改ADT的表示。
Example: different representations for strings
Let’s look at a simple abstract data type to see what representation independence means and why it’s useful. The MyString type below has far fewer operations than the real Java String, and their specs are a little different, but it’s still illustrative. Here are the specs for the ADT:
示例:字符串的不同表示
让我们来看一个简单的抽象数据类型,看看表示独立性意味着什么,以及它为什么有用。下面的My String类型的操作比真正的Java字符串少得多,它们的规范有一点不同,但仍然具有说明性。以下是ADT的说明:
/** MyString represents an immutable sequence of characters.
/public class MyString {
Example
Example of a creator operation
/// /
* @param b a boolean value

  • @return string representation of b, either “true” or “false” */

  • public static MyString valueOf(boolean b)

  • { … }

  • Examples of observer operations

  • ///

  • /** @return number of characters in this string */

  • public int length() { … }

  • /** @param i character position (requires 0 <= i < string length)

    • @return character at position i */
  • public char charAt(int i) { … }

  • Example of a producer operation ///

  • /** Get the substring between start (inclusive) and end (exclusive).

    • @param start starting index
    • @param end ending index. Requires 0 <= start <= end <= string length.
  • @return string consisting of charAt(start)…charAt(end-1) */

  • public MyString substring(int start, int end) { … }

  • }
    These public operations and their specifications are the only information that a client of this data type is allowed to know. Following the test-first programming paradigm, in fact, the first client we should create is a test suite that exercises these operations according to their specs. At the moment, however, writing test cases that use assertEquals directly on MyString objects wouldn’t work, because we don’t have an equality operation defined on MyString. We’ll talk about how to implement equality carefully in a later reading. For now, the only operations we can perform with MyStrings are the ones we’ve defined above: valueOf, length, charAt, and substring. Our tests have to limit themselves to those operations. For example, here’s one test for the valueOf operation:
    这些公共操作及其规范是该数据类型的客户机允许知道的唯一信息。遵循测试优先编程范式,实际上,我们应该创建的第一个客户端是一个测试套件,该测试套件根据它们的规范来执行这些操作。然而,目前,在My String对象上直接编写使用assert Equals的测试用例是行不通的,因为我们没有在My String上定义相等操作。在后面的阅读中,我们会详细讨论如何实现平等。目前,我们只能对My Strings执行上面定义的操作:value Of、length、char At和sub string。我们的测试只能局限于这些操作。例如,下面是value Of操作的一个测试:
    MyString s = MyString.valueOf(true); assertEquals(4, s.length()); assertEquals(‘t’, s.charAt(0)); assertEquals(‘r’, s.charAt(1)); assertEquals(‘u’, s.charAt(2)); assertEquals(‘e’, s.charAt(3));
    We’ll come back to the question of testing ADTs at the end of this reading.
    For now, let’s look at a simple representation for MyString: just an array of characters, exactly the length of the string, with no extra room at the end. Here’s how that internal representation would be declared, as an instance variable within the class:
    在本文的最后,我们将回到ADT s测试的问题上。
    现在,让我们来看看My String的一个简单表示:只是一个字符数组,正好是字符串的长度,末尾没有多余的空间。下面是内部表示的声明方式,作为类中的实例变量:
    private char[] a;
    With that choice of representation, the operations would be implemented in a straightforward way:
    有了这种表示法的选择,操作将以一种直接的方式实现:
    public static MyString valueOf(boolean b) { MyString s = new MyString(); s.a = b ? new char[] { ‘t’, ‘r’, ‘u’, ‘e’ } : new char[] { ‘f’, ‘a’, ‘l’, ‘s’, ‘e’ }; return s; } public int length() { return a.length; } public char charAt(int i) { return a[i]; } public MyString substring(int start, int end) { MyString that = new MyString(); that.a = new char[end - start]; System.arraycopy(this.a, start, that.a, 0, end - start); return that; }
    (The ?: syntax in valueOf is called the ternary conditional operator and it’s a shorthand if-else statement. See The Conditional Operators on this page of the Java Tutorials.)
    Question to ponder: Why don’t charAt and substring have to check whether their parameters are within the valid range? What do you think will happen if the client calls these implementations with illegal inputs?
    One problem with this implementation is that it’s passing up an opportunity for performance improvement. Because this data type is immutable, the substring operation doesn’t really have to copy characters out into a fresh array. It could just point to the original MyString object’s character array and keep track of the start and end that the new substring object represents. The String implementation in some versions of Java do this.
    To implement this optimization, we could change the internal representation of this class to:
    (value Of中的?:语法被称为三元条件运算符,它是一个if-else语句的简写。请参阅Java教程的本页中的条件运算符。)
    思考的问题:为什么char At和sub string不检查它们的参数是否在有效范围内?如果客户端使用非法输入调用这些实现,您认为会发生什么?
    这个实现的一个问题是它错过了一个性能改进的机会。因为该数据类型是不可变的,所以sub string操作实际上不必将字符复制到新数组中。它可以只指向原始的My String对象的字符数组,并跟踪新子字符串对象表示的开始和结束。某些版本的Java中的字符串实现实现了这一点。
    为了实现这种优化,我们可以将该类的内部表示改为:
    private char[] a;private int start;private int end;
    With this new representation, the operations are now implemented like this:
    有了这个新的表示,现在操作是这样实现的:
    public static MyString valueOf(boolean b) { MyString s = new MyString(); s.a = b ? new char[] { ‘t’, ‘r’, ‘u’, ‘e’ } : new char[] { ‘f’, ‘a’, ‘l’, ‘s’, ‘e’ }; s.start = 0; s.end = s.a.length; return s; } public int length() { return end - start; } public char charAt(int i) { return a[start + i]; } public MyString substring(int start, int end) { MyString that = new MyString(); that.a = this.a; that.start = this.start + start; that.end = this.start + end; return that; }
    Because MyString’s existing clients depend only on the specs of its public methods, not on its private fields, we can make this change without having to inspect and change all that client code. That’s the power of representation independence.
    因为MyString的现有客户端只依赖于其公共方法的规范,而不依赖于其私有字段,所以我们可以在无需检查和更改所有客户端代码的情况下进行更改。这就是代表独立的力量。
    READING EXERCISES (阅读练习)
    Representation 1 (表示 1)
    Representation 2 (表示 2)
    Representation 3 (表示 3)
    Representation 4 (表示 4)
    Realizing ADT concepts in Java
    Let’s summarize some of the general ideas we’ve discussed in this reading, which are applicable in general to programming in any language, and their specific realization using Java language features. The point is that there are several ways to do it, and it’s important to both understand the big idea, like a creator operation, and different ways to achieve that idea in practice.
    在Java中实现ADT概念
    让我们总结一下在本文中讨论的一些一般思想,这些思想一般适用于任何语言的编程,以及它们使用Java语言特性的具体实现。关键是有几种方法可以做到这一点,而且重要的是既要理解大的想法(如创建者操作),又要了解在实践中实现这个想法的不同方法。
    ADT concept
    ADT的概念 Ways to do it in Java
    用Java实现它的方法 Examples
    举例
    Abstract data type
    抽象数据类型 Class
    类 String

    Interface + class(es)
    接口和类 List and ArrayList
    note 1
    Enum
    枚举 DayOfWeek
    note 2
    Creator operation
    创造者操作 Constructor
    构造函数 ArrayList()

    Static (factory) method
    静态(工厂)方法 Collections.singletonList(), Arrays.asList()

    Constant
    常数 BigInteger.ZERO
    note 3
    Observer operation
    观察者操作 Instance method
    实例方法 List.get()

    Static method
    静态方法 Collections.max()

Producer operation
生产者操作 Instance method
实例方法 String.trim()

Static method

静态方法 Collections.unmodifiableList()

Mutator operation
突变操作 Instance method
实例方法 List.add()

Static method

静态方法 Collections.copy()

Representation
表示 private fields
私有字段
There are three items in this table that haven’t yet been discussed in this reading:

  1. Defining an abstract data type using an interface. We’ve seen List and ArrayList as an example, and we’ll discuss interfaces in a future reading.
  2. Defining an abstract data type using an enumeration (enum). Enums are ideal for ADTs that have a small fixed set of values, like the days of the week Monday, Tuesday, etc. We’ll discuss enumerations in a future reading.
  3. Using a constant object as a creator operation. This pattern is commonly seen in immutable types, where the simplest or emptiest value of the type is simply a public constant, and producers are used to build up more complex values from it.
    这张表中有三个项目在本文中没有讨论:
    1.使用接口定义抽象数据类型。我们已经以List和ArrayList为例,在以后的阅读中我们将讨论接口。
    2.使用枚举(enum)定义抽象数据类型。枚举对于具有一小组固定值的adt是非常理想的,比如星期一、星期二等。我们将在以后的阅读中讨论枚举。
    3.使用常量对象作为创建者操作。这种模式在不可变类型中很常见,在这种类型中,最简单或最空的值只是一个公共常量,并使用生成器从它构建更复杂的值。

Testing an abstract data type
We build a test suite for an abstract data type by creating tests for each of its operations. These tests inevitably interact with each other. The only way to test creators, producers, and mutators is by calling observers on the objects that result, and likewise, the only way to test observers is by creating objects for them to observe.
Here’s how we might partition the input spaces of the four operations in our MyString type:
测试抽象数据类型
我们为抽象数据类型的每个操作创建测试,从而为其构建测试套件。这些测试不可避免地相互影响。测试创建者、生产者和变异者的唯一方法是在产生结果的对象上调用观察者,同样地,测试观察者的唯一方法是创建对象供他们观察。
下面是我们如何在MyString类型中划分四个操作的输入空间:
// testing strategy for each operation of MyString: valueOf()😕/ true, false// length(): // string len = 0, 1, n// string = produced by valueOf(), produced by substring()// charAt(): // string len = 1, n// i = 0, middle, len-1// string = produced by valueOf(), produced by substring()// substring()😕/ string len = 0, 1, n// start = 0, middle, len// end = 0, middle, len// end-start = 0, n// string = produced by valueOf(), produced by substring()
Then a compact test suite that covers all these partitions might look like:
然后,一个覆盖所有这些分区的紧凑测试套件可能看起来像:
@Test public void testValueOfTrue() { MyString s = MyString.valueOf(true); assertEquals(4, s.length()); assertEquals(‘t’, s.charAt(0)); assertEquals(‘r’, s.charAt(1)); assertEquals(‘u’, s.charAt(2)); assertEquals(‘e’, s.charAt(3)); } @Test public void testValueOfFalse() { MyString s = MyString.valueOf(false); assertEquals(5, s.length()); assertEquals(‘f’, s.charAt(0)); assertEquals(‘a’, s.charAt(1)); assertEquals(‘l’, s.charAt(2)); assertEquals(‘s’, s.charAt(3)); assertEquals(‘e’, s.charAt(4)); } @Test public void testEndSubstring() { MyString s = MyString.valueOf(true).substring(2, 4); assertEquals(2, s.length()); assertEquals(‘u’, s.charAt(0)); assertEquals(‘e’, s.charAt(1)); } @Test public void testMiddleSubstring() { MyString s = MyString.valueOf(false).substring(1, 2); assertEquals(1, s.length()); assertEquals(‘a’, s.charAt(0)); } @Test public void testSubstringIsWholeString() { MyString s = MyString.valueOf(false).substring(0, 5); assertEquals(5, s.length()); assertEquals(‘f’, s.charAt(0)); assertEquals(‘a’, s.charAt(1)); assertEquals(‘l’, s.charAt(2)); assertEquals(‘s’, s.charAt(3)); assertEquals(‘e’, s.charAt(4)); } @Test public void testSubstringOfEmptySubstring() { MyString s = MyString.valueOf(false).substring(1, 1).substring(0, 0); assertEquals(0, s.length()); }
Try to match each test case to the partitions it covers.
Notice that each test case typically calls a few operations that make or modify objects of the type (creators, producers, mutators) and some operations that inspect objects of the type (observers). As a result, each test case covers parts of several operations.
尝试将每个测试用例与它涵盖的分区相匹配。
请注意,每个测试用例通常调用一些创建或修改该类型对象的操作(创建者、生产者、变异者),以及一些检查该类型对象的操作(观察者)。因此,每个测试用例都包含几个操作的部分。
READING EXERCISES
These questions use the following datatype:
阅读训练
这些问题使用以下数据类型:
/** Immutable datatype representing a student’s progress through school. /class Student { /* make a freshman / public Student() { … } /* @return a student promoted to the next year, i.e. freshman returns a sophomore, sophomore returns a junior, junior returns a senior, senior returns an alum, alum stays an alum and can’t be promoted further. / public Student promote() { … } /* @return number of years of school completed, i.e. 0 for a freshman, 4 for an alum */ public int getYears() { … } }
Partitioning ADT operations 分区ADT操作
Choosing ADT test cases 选择ADT测试用例
Summary
• Abstract data types are characterized by their operations.
• Operations can be classified into creators, producers, observers, and mutators.
• An ADT’s specification is its set of operations and their specs.
• A good ADT is simple, coherent, adequate, and representation-independent.
• An ADT is tested by generating tests for each of its operations, but using the creators, producers, mutators, and observers together in the same tests.
总结
•抽象数据类型的特征是它们的操作。
•操作可以分为创建器、生成器、观察器和突变器。
•ADT的规范是它的操作集及其规范。
•一个好的ADT是简单的,连贯的,足够的,和表示独立的。
•测试ADT的方法是为它的每个操作生成测试,但是在相同的测试中同时使用创建者、生产者、变异者和观察者。
These ideas connect to our three key properties of good software as follows:
这些想法与优秀软件的三个关键属性相联系:

Safe from bugs. A good ADT offers a well-defined contract for a data type, so that clients know what to expect from the data type, and implementers have well-defined freedom to vary.
Easy to understand. A good ADT hides its implementation behind a set of simple operations, so that programmers using the ADT only need to understand the operations, not the details of the implementation.
Ready for change. Representation independence allows the implementation of an abstract data type to change without requiring changes from its clients.
这些想法与优秀软件的三个关键属性相联系:
安全的bug。好的ADT为数据类型提供定义良好的约定,这样客户端就知道从数据类型中期望什么,而实现者可以自由地进行定义良好的变化。
容易理解。一个好的ADT将它的实现隐藏在一组简单的操作之后,这样使用ADT的程序员只需要理解这些操作,而不需要了解实现的细节。
准备改变。表示独立性允许更改抽象数据类型的实现,而不需要更改其客户端。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值