面向对象软件构造(第2版)-第6章 Abstract data types抽象数据类型 (中)

来源:互联网 发布:移动路由器mac过滤 编辑:程序博客网 时间:2024/05/17 10:56
 

6.4 FORMALIZING THE SPECIFICATION

6.4 形式化规格

 

The glimpse of data abstraction presented so far is too informal to be of durable use. Consider again our staple example: a stack, as we now understand it, is defined in terms of the applicable operations; but then we need to define these operations!

到目前为止,数据抽象的介绍过于简略而无法有效地使用。再一次考虑我们常用的例子: 栈,正如我们现在所了解的那样,它是根据应用的运算来定义的;那么我们需要定义这些运算!

 

Informal descriptions as above (put pushes an element “on top of ” the stack, remove pops the element “last pushed” and so on) do not suffice. We need to know precisely how these operations can be used by clients, and what they will do for them.

如上的简略描述 (put是把一个元素压入栈顶,remove是取出最后放入的元素,等等)并不足够。我们需要准确地知道客户端如何使用这些运算,这些运算需要做些什么样的动作。

 

An abstract data type specification will provide this information. It consists of four paragraphs, explained in the next sections:

一个抽象数据类型的规格将提供这些信息。它由四段组成,下面将作出解释:

 

• TYPES. 类型

• FUNCTIONS. 函数

• AXIOMS. 定理

• PRECONDITIONS. 前置条件

 

These paragraphs will rely on a simple mathematical notation for specifying the properties of an abstract data type (ADT for short).

这些段落将会使用一个简单的数学符号来详细说明抽象数据类型(缩写为ADT)的属性。

 

The notation — a mathematical formalism, not to be confused with the software notation of the rest of this book even though for consistency it uses a similar syntactic style — has no name and is not a programming language; it could serve as the starting point for a formal specification language, but we shall not pursue this avenue here, being content enough to use self-explanatory conventions for the unambiguous specification of abstract data types.

符号-一个数学的形式,不要被下面的软件符号所迷惑,尽管为了一致性它使用了一种类似语法的风格-没有名字,也不是程序设计语言;它用于一个正式规格语言的起草,但是在这里,我们不将研究这些,对于明确的抽象数据类型的规格,使用清楚的约定已经能够满足了。

 

Specifying types

指定类型

 

The TYPES paragraph indicates the types being specified. In general, it may be convenient to specify several ADTs together, although our example has only one, STACK.

类型段叙述了被指定的类型。通常,把几种ADT放在一起叙述会方便些,尽管我们的例子只有一个,栈STACK

 

By the way, what is a type? The answer to this question will combine all the ideas developed in the rest of this chapter; a type is a collection of objects characterized by functions, axioms and preconditions. If for the moment you just view a type as a set of objects, in the mathematical sense of the word “set” — type STACK as the set of all possible stacks, type INTEGER as the set of all possible integer values and so on — you are not guilty of any terrible misunderstanding. As you read this discussion you will be able to refine this view. In the meantime the discussion will not be too fussy about using “set” for “type” and conversely.

顺便说一下,什么是类型? 这个问题的答案将结合本章余下部分中被介绍的所有概念;一个类型是一个对象的集合,这个对象被函数,定理和前提条件所描述。如果此刻您仅仅把一个类型视为一组对象,在单词“集合(set)”的数学意义上-STACK类型当作所有可能的栈的集合,INTEGER类型作为所有的整数值的集合,诸如此类-对于任何极度地误解,您都没有错。当您读到这里的时候,您要能够提升这个观点。同时,这个讨论并没有过多关注于对类型使用集合或相反的情况。

 

On one point, however, you should make sure to avoid any confusion: an abstract data type such as STACK is not an object (one particular stack) but a collection of objects (the set of all stacks). Remember what our real goal is: finding a good basis for the modules of our software systems. As was noted in the previous chapter, basing a module on one particular object — one stack, one airplane, one bank account — would not make sense. O-O design will enable us to build modules covering the properties of all stacks, all airplanes, all bank accounts — or at least of some stacks, airplanes or accounts.

然而,在一点上,您应该确保避免任何的混乱: 像是STACK这样的抽象数据类型,不是一个对象(一个特别的栈),而是一个对象(所有栈的集合)的集合。记住我们的真正目标是: 为我们的软件系统模块找到一种良好的基础。如前一章所述, 基于一个特别的对象-一个栈,一架飞机,一个银行账户-而构建模块不再有意义。OO的设计将会使我们能够构建模块来包含所有的栈,所有的飞机,所有银行账户的属性-或至少一些栈,飞机或账户。

 

An object belonging to the set of objects described by an ADT specification is called an instance of the ADT. For example, a specific stack which satisfies the properties of the STACK abstract data type will be an instance of STACK. The notion of instance will carry over to object-oriented design and programming, where it will play an important role in explaining the run-time behavior of programs.

属于一个ADT规格所描述的对象集合中的一个对象叫做ADT的一个实例(instance)。举例来说,一个特定的栈,其满足STACK抽象数据类型的属性,它就是STACK的实例。实例的概念将会放到面向对象的设计和编程中去介绍,在那里,它在解释程序的运行时方面起着重要的作用。

 

The TYPES paragraph simply lists the types introduced in the specification. Here:


类型段简单地列举了在规格中所介绍的类型,这里:

 

Our specification is about a single abstract data type STACK, describing stacks of objects of an arbitrary type G.

我们的规格是关于一个单一的抽象数据类型STACK,它描述了一个任意G类型对象的栈。

 

Genericity

泛型

 

In STACK [G], G denotes an arbitrary, unspecified type. G is called a formal generic parameter of the abstract data type STACK, and STACK itself is said to be a generic ADT. The mechanism permitting such parameterized specifications is known as genericity; we already encountered a similar concept in our review of package constructs.

STACK [G]里,G表示一个任意的,未指定的类型。G被称之为抽象数据类型STACK泛化形式参数(formal generic parameter)STACK本身称之为泛化ADT。允许如此参数化规格的机制被认为是泛型;在包结构的回顾中我们已经碰到过相似的概念。

 

It is possible to write ADT specifications without genericity, but at the price of unjustified repetition. Why have separate specifications for the types “stack of bank accounts”, “stack of integers” and so on? These specifications would be identical except where they explicitly refer to the type of the stack elements — bank accounts or integers. Writing them, and then performing the type substitutions manually, would be tedious. Reusability is desirable for specifications too — not just programs! Thanks to genericity, we can make the type parameterization explicit by choosing some arbitrary name, here G, to represent the variable type of stack elements.

编写没有泛型的ADT规格是有可能的,但这是以可能不正确的重复为代价的。对于“银行账户栈”,“整数栈”等等,为什么要不同的规格呢?除了它们所明确使用的栈元素的类型-银行账户或整数-之外,这些规格都是一样的。编写它们,然后手动地执行类型替换,这很单调乏味。复用性对规格而言也同样需要-不仅仅只针对程序!由于泛型,通过选择一些任意的名字,这里是G,我们能够把类型明确地定义成参数,用来表示各种不同的栈元素类型。

 

As a result, an ADT such as STACK is not quite a type, but rather a type pattern; to obtain a directly usable stack type, you must obtain some element type, for example ACCOUNT, and provide it as actual generic parameter corresponding to the formal parameter G. So although STACK is by itself just a type pattern, the notation STACK [ACCOUNT] is a fully defined type. Such a type, obtained by providing actual generic parameters to a generic type, is said to be generically derived.

结果一个ADT不是一个真正的类型,就像STACK这样而更确切地说是一个类型模式要获得一个直接可用的栈类型, 您必须获得一些象ACCOUNT一样的元素类型,以符合形参G泛化实际参数(actual generic parameter)的方式提供。因此,尽管STACK本身只是一个类型模式,但符号STACK [ACCOUNT]是一个完整定义的类型。对一个泛化类型提供泛化实参,从而获得的类型称之为被泛化派生(generically derived)的类型。

 

The notions just seen are applicable recursively: every type should, at least in principle, have an ADT specification, so you may view ACCOUNT as being itself an abstract data type; also, a type that you use as actual generic parameter to STACK (to produce a generically derived type) may itself be generically derived, so it is perfectly all right to use STACK [STACK [ACCOUNT]] specifying a certain abstract data type: the instances of that type are stacks, whose elements are themselves stacks; the elements of these latter stacks are bank accounts.

概念看上去有些递归:至少在理论上,每个类型应该有一个ADT规格,因此,您可以把ACCOUNT看成是它本身的一个抽象数据类型;同样,一个您用于STACK (产生一个泛化派生类型)的泛化实参可以当成本身的泛化派生, 因此,用

STACK [STACK [ACCOUNT]] 来描述一个特定的抽象数据类型是非常正常的:此类型的实例是栈,栈的元素是栈自己;后面栈中的元素是银行账户。

 

As this example shows, the preceding definition of “instance” needs some qualification. Strictly speaking, a particular stack is an instance not of STACK (which, as noted, is a type pattern rather than a type) but of some type generically derived from STACK, for example STACK [ACCOUNT]. It is convenient, however, to continue talking about instances of STACK and similar type patterns, with the understanding that this actually means instances of their generic derivations.

如例子所示,上述“实例”的定义需要一些限制条件。严格来说,一个精确的栈不但是一个STACK(如上所述,这是一个类型模式而非一个类型)的实例而且是从STACK派生出的一些泛化类型的实例,如STACK [ACCOUNT]。不管怎样,理解这些泛化派生的实例的实际意义,对于继续讨论STACK的实例和类似的类型模式是比较有益的。

 

Similarly, it is not quite accurate to talk about STACK being an ADT: the correct term is “ADT pattern”. For simplicity, this discussion will continue omitting the word “pattern” when there is no risk of confusion.

同样地,把STACK作为ADT加以讨论并不十分正确:正确的术语应该是“ADT模式”。为了简单化,在不产生混乱的风险下,我们的讨论将会继续省略“模式”。

 

The distinction will carry over to object-oriented design and programming, but there we will need to keep two separate terms:

•The basic notion will be the class; a class may have generic parameters.

•Describing actual data requires types. A non-generic class is also a type, but a generic class is only a type pattern. To obtain an actual type from a generic class, we will need to provide actual generic parameters, exactly as we derive the ADT STACK [ACCOUNT] from the ADT pattern STACK.

这个区别将会延续到面向对象的设计和编程中,在那里我们需要保持二个独立的术语:

·  基本的概念将会是(class);一个类可以有泛化参数。

·  描述实际数据需要类型(types)。一个非泛化的类也是一个类型,但是一个泛化的类就只是一个类型模式。要从一个泛化类中获得一个实际的类型,我们需要提供泛化实参,正如我们从ADT模式STACK中派生STACK [ACCOUNT]一样。

 

Later chapters will explore the notion of genericity as applied to classes, and how to combine it with the inheritance mechanism.

后面的章节将会研究应用于类的泛型概念,并研究该如何把它和继承机制结合起来。

 

Listing the functions

列出函数

 

After the TYPES paragraph comes the FUNCTIONS paragraph, which lists the operations applicable to instances of the ADT. As announced, these operations will be the prime component of the type definition — describing its instances not by what they are but by what they have to offer.

在类型(TYPES)段之后是函数(FUNCTIONS)段,其列出了应用到ADT实例上的运算。就象所宣称的那样,这些运算将会是类型定义中的主要组件-不仅仅通过定义,还通过所能提供的函数来描述实例。

 

Below is the FUNCTIONS paragraph for the STACK abstract data type. If you are a software developer, you will find the style familiar: the lines of such a paragraph evoke the declarations found in typed programming languages such as Pascal or Ada. The line for new resembles a variable declaration; the others resemble routine headers.

下面是STACK抽象数据类型的函数段。如果您是一位软件开发者,您会发现熟悉的风格:段中的每一行都调用了声明(declarations),这些声明就像在PascalAda这样典型的程序设计语言中的一样。new的那一行类似一个变量的声明;其余的类似于例程的头定义。


Each line introduces a mathematical function modeling one of the operations on stacks. For example function put represents the operation that pushes an element onto the top of a stack.

每一行都介绍了一个数学函数,对应于栈上的一个运算模型。例如,函数put描绘了压入一个元素到栈顶的运算。

 

Why functions? Most software people will not naturally think of an operation such as put as a function. When the execution of a software system applies a put operation to a stack, it will usually modify that stack by adding an element to it. As a result, in the above informal classification of commands, put was a “command” — an operation which may modify objects. (The other two categories of operations were creators and queries).

为什么是函数?大多数的软件工程师都不会想当然地把put这样的一个运算当作一个函数。当软件系统的执行对一个栈申请一个put运算的时候,它通常会加入一个元素来修改那个栈。结果,在上述非正式的命令分类中,put是一个“命令command”-一个可以修改对象的运算。(运算的另外二个种类是创建符和查询)。

 

An ADT specification, however, is a mathematical model, and must rely on wellunderstood mathematical techniques. In mathematics the notion of command, or more generally of changing something, does not exist as such; computing the square root of the number 2 does not modify the value of that number. A mathematical expression simply defines certain mathematical objects in terms of certain other mathematical objects: unlike the execution of software on a computer, it never changes any mathematical object.

然而,一个ADT规格就是一个数学模型,并且必须依赖于简单易懂的数学技术。数学上的命令的概念或更通常的是计算式的方法并不是如此。计算2的平方根并不修改数值2的本身。一个数学表达式只是根据其它的数学对象来定义某个数学对象: 它从不改变任何的数学对象,这不同于在计算机上软件的执行。

 

Yet we need a mathematical concept to model computer operations, and here the notion of function yields the closest approximation. A function is a mechanism for obtaining a certain result, belonging to a certain target set, from any possible input belonging to a certain source set. For example, if R denotes the set of real numbers, the function definition

square_plus_one: R ® R

square_plus_one (x) = x2 + 1 (for any x in R)

introduces a function square_plus_one having R as both source and target sets, and yielding as result, for any input, the square of the input plus one.

你仍然需要一个数学的概念来模拟计算机的运算,同时,这里函数的观念最为接近。一个函数是一种机制,它从任何可能的输入中获得一个特定的结果,其中,输入属于一个特定的来源集合,结果属于一个特定的目标集合。例如,如果R表示为一个实数集合,函数定义

square_plus_one: R ® R

square_plus_one (x) = x2 + 1 (for any x in R)

则表示为函数square_plus_one即把R作为来源集合,也作为目标集合,同时对于任何的输入,产生平方并加一作为其结果。

 

The specification of abstract data types uses exactly the same notion. Operation put, for example, is specified as put: STACK [G] ´ G ® STACK [G] which means that put will take two arguments, a STACK of instances of G and an instance of G, and yield as a result a new STACK [G]. (More formally, the source set of function put is the set STACK [G] ´ G, known as the cartesian product of STACK [G] and G; this is the set of pairs <s, x> whose first element s is in STACK [G] and whose second element x is in G.) Here is an informal illustration:

抽象数据类型的规格正是使用同样的观念。例如,put运算的表达式为 put: STACK [G] ´ G ® STACK [G] 式子表明使用两个参数,一个是G实例所用的STACK,一个是G的实例,并产生一个新的STACK [G]作为结果。(更规范地说,函数put的来源集合是STACK [G] ´ G集合,这就是STACK [G]G笛卡儿乘积(cartesian product);这是一个<s, x>对集,它的第一个元素sSTACK [G],第二个元素xG。)下面是一个非正式的图例:

 

 

With abstract data types, we only have functions in the mathematical sense of the term; they will produce neither side effects nor in fact changes of any kind. This is the condition that we must observe to enjoy the benefits of mathematical reasoning.

对于抽象数据类型,我们只有函数能对应与术语的数学意义;事实上,它们既没有副作用又不生产任何性质上的改变。要想利用数学推理上的便利,这是我们一定要遵守的条件。

 

When we leave the ethereal realm of specification for the rough-and-tumble of software design and implementation, we will need to reintroduce the notion of change; because of the performance overhead, few people would accept a software execution environment where every “push” operation on a stack begins by duplicating the stack. Later we will examine the details of the transition from the change-free world of ADTs to the change-full world of software development. For the moment, since we are studying how best to specify types, the mathematical view is the appropriate one.

当我们离开虚无缥缈的规格仙境,前往软件设计和实现的混乱地界的时候,我们需要再次提出变化的观念;因为性能高于一切,很少有人会接受这样的一个软件执行环境,在栈上的每次“入栈”运算都是从复制栈开始的。稍后我们将会分析从ADTs的无变化世界到软件开发的充满变化世界的转变细节。目前,由于我们正在学习如何最佳地描述类型,数学观点是一个适合的方法。

 

The role of the operations modeled by each of the functions in the specification of STACK is clear from the previous discussion:

STACK规格中的每一个函数所模拟运算的功能在先前的讨论中已经很清楚了:

 

• Function put yields a new stack with one extra element pushed on top. The figure on the preceding page illustrates put (s, x) for a stack s and an element x.

·由于一个额外的元素压入栈顶,函数put产生一个新的栈。上页的图例中描绘了栈s和元素xput (s, x)运算。

 

• Function remove yields a new stack with the top element, if any, popped; like put, this function should yield a command (an object-changing operation, typically implemented as a procedure) at design and implementation time. We will see below how to take into account the case of an empty stack, which has no top to be popped.

·如果有栈顶元素的话,弹出栈顶元素,函数remove产生一个新的栈;就象put一样,此函数在设计和实现阶段应该产生一个指令(一个改变对象的运算,就象一段程序的典型实现)。我们将在下面看到如何考虑一个空栈的情况,栈中并没有栈顶元素可以被弹出。

 

• Function item yields the top element, if any.

·如果有栈顶元素的话,函数item得出栈顶元素。

 

• Function empty indicates whether a stack is empty; its result is a boolean value (true or false); the ADT BOOLEAN is assumed to have been defined separately.

·函数empty指出是否栈为空栈;其结果是一个布尔值(truefalse);假定ADT BOOLEAN另外已经单独定义过了。

 

• Function new yields an empty stack.

·函数new产生一个空栈。

 

The FUNCTIONS paragraph does not fully define these functions; it only introduces their signatures — the list of their argument and result types. The signature of put is

STACK [G] ´ G ® STACK [G]

indicating that put accepts as arguments pairs of the form <s, x> where s is an instance of STACK [G] and x is an instance of G, and yields as a result an instance of STACK [G]. In principle the target set of a function (the type that appears to the right of the arrow in signature, here STACK [G]) may itself be a cartesian product; this can be used to describe operations that return two or more results. For simplicity, however, this book will only use single-result functions.

在函数段中并没有完整地定义这些函数;只是介绍了它们的标记式 (signatures) 它们的参数和结果类型的列表。put的标记式是

STACK [G] ´ G ® STACK [G]

表示了put接受<s, x>对的形式作为参数,其中sSTACK [G]的实例,xG的实例,并产生STACK [G]的实例作为输出结果。理论上,一个函数的目标集合(在标记式中箭头右边显示的类型,这里是STACK [G])本身可以是一个笛卡尔乘积;这能够被用来描述返回两至三个结果的运算。然而,为了简单化,本书只采用单个结果的函数。

 

The signature of functions remove and item includes a crossed arrow instead of the standard arrow used by put and empty. This notation expresses that the functions are not applicable to all members of the source set; it will be explained in detail below.

函数removeitem的标记式上包含了一个十字的箭头,取代了putempty所用的普通箭头。此符号表示了函数不适用于所有的来源集合;详述见后。

 

The declaration for function new appears as just

new: STACK

with no arrow in the signature. This is in fact an abbreviation for

new: ® STACK

introducing a function with no arguments. There is no need for arguments since new must always return the same result, an empty stack. So we just remove the arrow for simplicity. The result of applying the function (that is to say, the empty stack) will also be written new, an abbreviation for new ( ), meaning the result of applying new to an empty argument list.

函数new的声明

new: STACK

在标记式上并没有箭头。这实际上是一个

new: ® STACK

的缩写,表示一个无任何参数的函数。由于总是一定返回同样的结果,一个空栈,参数就没有必要了。因此为了简单化,我们去掉了箭头。申请函数(确切地说,申请一个空栈)的式子可以写成new,这是new ( )的缩写,含义为申请一个无参数列表的new

 

Function categories

函数种类

 

The operations on a type were classified informally at the beginning of this chapter into creators, queries and commands. With an ADT specification for a new type T, such as STACK [G] in the example, we can define the corresponding classification in a more rigorous way. The classification simply examines where T appears, relative to the arrow, in the signature of each function:

在本章开头,一个类型上的运算被非正式地划分为创建符,查询和命令。对于一个新类型TADT规格来说,就像例子中的STACK [G]一样,我们可以用一种更严格的方式来定义相关的分类。在每个函数的标记式中,分类简单地检查T出现在相对箭头的位置:

 

• A function such as new for which T appears only to the right of the arrow is a creator function. It models an operation which produces instances of T from instances of other types — or, as in the case of a constant creator function such as new, from no argument at all. (Remember that the signature of new is considered to contain an implicit arrow.)

·一个函数,如newT只出现在箭头的右边,这是一个创建符函数(creator function)。它模拟一个运算,从另外一个类型中产生T的实例-或者,就像new这样的常量创建符函数,在根本没有参数的情况下产生实例。(记住new的标记式中包含着一个隐含的箭头。)

 

• A function such as item and empty for which T appears only on the left of the arrow is a query function. It models an operation which yields properties of instances of T, expressed in terms of instances of other types (BOOLEAN and the generic parameter G in the examples).

·一个函数,如itememptyT只出现在箭头的左边,这是一个查询函数(query function)。它模拟一个运算,产生T的实例的属性,用其它类型的实例来表达结果(在例子中为BOOLEAN和泛化参数G)

 

• A function such as put or remove for which T appears on both sides of the arrow is a command function. It models an operation which yields new instances of T from existing instances of T (and possibly instances of other types).

·一个函数,如putremoveT出现在箭头的两边,这是一个命令函数(command function)。它模拟一个运算,从已经存在的T的实例(也可能是其它类型的实例)中产生一个新的T的实例。

 

An alternative terminology calls the three categories “constructor”, “accessor” and “modifier”. The terms retained here are more directly related to the interpretation of ADT functions as models of operations on software objects, and will carry over to class features, the software counterparts of our mathematical functions.

另一组对应的术语称这三种分类为构造函数(constructor),存取函数(accessor)和修改函数(modifier)。但在这里所采用的术语更直接地涉及到了,把ADT函数解释成在软件对象上的运算模型的过程,并且概念将延续到类的特性上,那是和这些数学函数相匹配的软件元素。

 

The AXIOMS paragraph

定理段落

 

We have seen how to describe a data type such as STACK through the list of functions applicable to its instances. The functions are known only through their signatures.

通过实例上的函数列表,我们已经了解了如何描述一个象STACK一样的数据类型。函数只有通过它们的标记式才可以得知。

 

To indicate that we have a stack, and not some other data structure, the ADT specification as given so far is not enough. Any “dispenser” structure, such as a first-in first-out queue, will also satisfy it. The choice of names for the operations makes this particularly clear: we do not even have stack-specific names such as push, pop or top to fool ourselves into believing that we have defined stacks and only stacks.

要表明我们有一个栈而不是其它的什么数据结构,到目前为止给出的ADT规格并不足够。任何“自动售货机”式的结构,如先进先出队列,也可以满足它。运算上的命名选择解释得很清楚:我们甚至没有用象pushpoptop这些栈的相关命名来让我们自己误认为我们已经定义了栈,纯粹的栈。

 

This is not surprising, of course, since the FUNCTIONS paragraph declared the functions (in the same way that a program unit may declare a variable) but did not fully define them. In a mathematical definition such as the earlier example

square_plus_one: R ® R

square_plus_one (x) = x2 + 1 (for any x in R)

the first line plays the role of the signature declaration, but there is also a second line which defines the function’s value. How do we achieve the same for the functions of an ADT?

由于函数(FUNCTION)段落声明了函数(用了和程序单元声明一个变量的同样方法)但是没有完整地定义它们,所以这当然没什么奇怪的。在一个如先前例子那样的数学定义

square_plus_one: R ® R

square_plus_one (x) = x2 + 1 ( R的定义域内的任何 x)

中,第一行起了标记式声明的作用,但是这里有着定义了函数值的第二行。我们如何能对一个ADT的函数作同样的事情呢?

 

Here we should not use an explicit definition in the style of the second line of square_ plus_one’s definition, because it would force us to choose a representation — and this whole discussion is intended to protect us from representation choices.

这里,在第二行square_ plus_one的定义中,我们不应该使用一个显定义形式,因为这将强迫我们去选择一个表示法-整个的讨论都在让我们脱离表示法选择。

 

Just to make sure we understand what an explicit definition would look like, let us write one for the stack representation ARRAY_UP as sketched above. In mathematical terms, choosing ARRAY_UP means that we consider any instance of STACK as a pair <count, representation>, where representation is the array and count is the number of pushed elements. Then an explicit definition of put is (for any instance x of G):

put (<count, representation>, x) = <count + 1, representation [count+1: x]>

where the notation a [n: v] denotes the array obtained from a by changing the value of the element at index n so that it is now v, and keeping all other elements, if any, as they are.

为确保我们了解一个定义的表现特征,让我们对于上面简单描述过的栈表示法ARRAY_UP写一个定义。在数学的术语中,选择ARRAY_UP意味着我们把STACK的任何实例看作一个对<count, representation>,其中的representation是一个数组,count是栈中元素的数目。接着是一个put的精确定义(对于G的任何实例x)

put (<count, representation>, x) = <count + 1, representation [count+1: x]>

其中,符号a [n: v]表示通过改变a中索引为n的元素的值为v从而获得的数组,如果数组中有其它的元素,则保持不变。

 

This definition of function put is just a mathematical version of the implementation of the put operation sketched in Pascal notation, next to representation ARRAY_UP, in the picture of possible stack representations at the beginning of this chapter.

函数put的这个定义,仅仅是在Pascal符号中简述put运算实现的一个数学版本,在本章开头的那幅栈表示法的图例上,紧邻着ARRAY_UP表示法。

 

But this is not what we want; “Free us from the yoke of representations!”, the motto of the Object Liberation Front and its military branch (the ADT brigade), is also ours.

但这并不是我们想要的;把我们从表示法的束缚中解放出来!”,这是对象解放阵线和其军事组织(ADT) 的口号,也是我们的。

 

Because any explicit definition would force us to select a representation, we must turn to implicit definitions. We will refrain from giving the values of the functions of an ADT specification; instead we will state properties of these values — all the properties that matter, but those properties only.

因为任何显定义都将迫使我们选择一个表示法,我们必须转向隐定义(implicit definition)。我们要对给一个ADT规格的函数赋值的行为进行克制;取而代之的是我们要声明这些值的属性-所有重要的属性,也仅限于它们。

 

The AXIOMS paragraph states these properties. For STACK it will be:

定理(AXIOMS段描述了这些属性。对应于STACK的是:


 

The first two axioms express the basic LIFO (last-in, first-out) property of stacks. To understand them, assume we have a stack s and an instance x, and define s' to be put (s, x), that is to say the result of pushing x onto s. Adapting an earlier figure:

前两个定理表达了栈的基本的LIFO(last-in, first-out后进先出)属性。为了便于理解,假设我们有一个栈s和一个实例x,同时定义s'代表put (s, x),就是把x压入s所得的结果。和上一张图形类似:

 


Here axiom A1 tells us that the top of s' is x, the last element that we pushed; and axiom A2 tells us that if we remove the top element from s', we get back the stack s that we had before pushing x. These two axioms provide a concise description of the fundamental property of stacks in pure mathematical terms, without any recourse to imperative reasoning or representation properties.

其中,定理A1告诉我们s'的栈顶是x,这是我们压入的最后一个元素;定理A2告诉我们如果我们从s'移除栈顶元素,我们会得到压入x之前的栈s。这两个定理提供了在纯数学术语中栈基本属性的一个简要描述,并没有借助于必要的推理或是表示法属性。

 

Axioms A3 and A4 tell us when a stack is empty and when it is not: a stack resulting from the creator function new is empty; any stack resulting from pushing an element on an existing stack (empty or not) is non-empty.

定理A3A4告诉我们空栈和非空栈:一个由创建符函数new产生的栈是空的;在一个已知的栈中(空或非空)压入一个元素,这个栈是非空的。

 

These axioms, like the others, are predicates (in the sense of logic), expressing that a certain property is always true for every possible value of s and x. Some people prefer to read A3 and A4 in the equivalent form

这些定理和其它的一样,都是谓项(逻辑上的意义),表达了对于sx任何可能的值,一个特定的属性总是为真。一些人更愿意通过同样的形式来了解A3A4

under which you may also view them, informally at least, as defining function empty by induction on the size of stacks.

您也可以把它们看成,至少非正式地看成,是通过归纳栈的大小来定义函数empty

 

Two or three things we know about stacks

有关栈的几点

 

ADT specifications are implicit. We have encountered two forms of implicitness:

ADT规格是隐含的(implicit)。我们曾遇到过隐含性的两种形式:

 

• The ADT method defines a set of objects implicitly, through the applicable functions. This was described above as defining objects by what they have, not what they are. More precisely, the definition never implies that the operations listed are the only ones; when it comes to a representation, you will often add other operations.

·ADT方法以应用函数的方式隐含地定义了一组对象。通过它们所具有的而不是它们是什么来定义对象,这已经在上面被描述过了。更精确的是,定义从未暗示所列出来的这些运算是唯一的;当应用到表示法的时候,你将会加入其它的运算。

 

• The functions themselves are also defined implicitly: instead of explicit definitions (such as was used for square_plus_one, and for the early attempt to define put by reference to a mathematical representation), we use axioms describing the functions’ properties. Here too there is no claim of exhaustiveness: when you eventually implement the functions, they will certainly acquire more properties.

·函数本身也被隐含地定义:我们使用定理来描述函数的特性,而不是显定义(正如在square_plus_one上所采用的,或者是,先前通过引用一个数学表示法来定义put的例子)。这里也没有详尽列出所有属性的要求: 当你最终实现函数的时候,它们肯定会获得更多的属性。

 

This implicitness is a key aspect of abstract data types and, by implication, of their future counterparts in object-oriented software construction — classes. When we define an abstract data type or a class, we always talk about the type or class: we simply list the properties we know, and take these as the definition. Never do we imply that these are the only applicable properties.

隐含性是抽象数据类型的一个主要方面,同时暗示着也是在面向对象软件构造中其将来的类似版本-类-的关键方面。当我们定义一个抽象数据类型或一个类的时候,我们总是就事论事:我们只是列出我们所知道的属性,而且把它们当作定义。我们从来没有表示这些是唯一的可适用的属性。

 

Implicitness implies openness: it should always be possible to add new properties to an ADT or a class. The basic mechanism for performing such extensions without damaging existing uses of the original form is inheritance.

隐含性意味着开放:它应该总是能够把新的属性加入到ADT或是一个类中去。对于完成这样的扩展而不损坏正在使用中的原来形式,其基本机制就是继承。

 

The consequences of this implicit approach are far-reaching. The “supplementary topics” section at the end of this chapter will include more comments about implicitness.

隐含方式的结果有着深远的影响。在本章末尾的“增补主题”小节中将会包括隐含性的更多解释。

 

Partial functions

部分函数

 

The specification of any realistic example, even one as basic as stacks, is bound to encounter the problems of undefined operations: some operations are not applicable to every possible element of their source sets. Here this is the case with remove and item: you cannot pop an element from an empty stack; and an empty stack has no top.

任何现实例子的规格,即使和栈一样的基本,也必定会遇到未定义运算的问题: 一些运算并不适用于它们源集合中的每一个可能的元素。这里有removeitem的情况:你不能够从一个空栈中取出一个元素;而且一个空栈也没有栈顶。

 

The solution used in the preceding specification is to describe these functions as partial. A function from a source set X to a target set Y is partial if it is not defined for all members of X. A function which is not partial is total. A simple example of partial function in standard mathematics is inv, the inverse function on real numbers, whose value for any appropriate real number x is

在上述规格中所使用的解决方案是部分描述这些函数。一个函数,如果它的定义不是对X的所有成员有效,那么这个函数从源集合X到目标集合Y是部分的。一个不是部分的函数就是完全函数(Total Function)。在标准数学中,一个简单的部分函数例子是inv,即实数的倒数,对于任何可能的实数x其值是:


Because inv is not defined for x = 0, we may specify it as a partial function on R, the set of all real numbers:


因为 inv对于x = 0没有定义,我们可以认为它是R的一个部分函数,R是所有实数的集合:

 

To indicate that a function may be partial, the notation uses the crossed arrow  

; the normal arrow ® will be reserved for functions which are guaranteed to be total.

为了表示一个函数是部分的,符号使用了一个十字箭头 ;普通的箭头®使用于完全函数。

 

The domain of a partial function in  is the subset of X containing those elements for which the function yields a value. Here the domain of inv is R {0}, the set of real numbers other than zero.

中,部分函数的定义域(domain)X的子集,包含了能让函数得到有效值的元素。这里的定义域invR {0},一个除零之外的实数集合。

 

The specification of the STACK ADT applied these ideas to stacks by declaring put and item as partial functions in the FUNCTIONS paragraph, as indicated by the crossed arrow in their signatures. This raises a new problem, discussed in the next section: how to specify the domains of these functions.

通过在FUNCTIONS段声明putitem为部分函数,并在其标记式上显示十字箭头,STACK ADT的规格在栈上应用了这些思想。这引发了一个新的问题,下个小节来讨论: 如何指定这些函数的定义域。

 

In some cases it may be desirable to describe put as a partial function too; this is necessary to model implementations such as ARRAY_UP and ARRAY_DOWN, which only support a finite number of consecutive put operations on any given stack. It is indeed a good exercise to adapt the specification of STACK so that it will describe bounded stacks with a finite capacity, whereas the above form does not include any such capacity restriction. This is a new use for partial functions: to reflect implementation constraints. In contrast, the need to declare item and remove as partial functions reflected an abstract property of the underlying operations, applicable to all representations.

在某些情况中, put描述成一个部分函数也是合乎需要的;要是模拟诸如ARRAY_UPARRAY_DOWN之类的实现,这是必须的,这些实现在任何指定的栈上仅仅支持有限数量的连续put运算。尽管上述的形式并不包括任何的容量限制,但是改编STACK的规格使它描述具有一个有限容量的栈,这是会一个不错的练习。 这是一个部分函数的新用法:反映实现上的限制。与之相反的是,把itemremove声明为部分函数的要求反映了基本运算的一个抽象属性,这个属性适用于所有的表示法。

 

Preconditions

前置条件

 

Partial functions are an inescapable fact of software development life, merely reflecting the observation that not every operation is applicable to every object. But they are also a potential source of errors: if f is a partial function from X to Y, we are not sure any more that the expression f (e) makes sense even if the value of e is in X: we must be able to guarantee that the value belongs to the domain of f.

部分函数是一个软件开发活动中的必然事实,它只不过反映了这样的一个结论,即不是每个运算都适用于每个对象。但是它们也是一个潜在的错误源:如果f是一个部分函数,其定义域从XY,那么我们不再能够确定表达式f (e)有意义,即使e的值在X范围内:我们必须能够保证其值属于f的定义域。

 

For this to be possible, any ADT specification which includes partial functions must specify the domain of each of them. This is the role of the PRECONDITIONS paragraph.

要使之可能的话,任何包含部分函数的ADT规格必须指定每一个部分函数的定义域。这就是PRECONDITIONS段的作用。

 

For STACK, the paragraph will appear as:

对于STACK,这段显示如下:

 


where, for each function, the require clause indicates what conditions the function’s arguments must satisfy to belong to the function’s domain.

这里,对于每一个函数,require子句指出了函数的参数必须要满足函数定义域的条件。

 

The boolean expression which defines the domain is called the precondition of the corresponding partial function. Here the precondition of both remove and item expresses that the stack argument must be non-empty. Before the require clause comes the name of the function with dummy names for arguments (s for the stack argument in the example), so that the precondition can refer to them.

定义了定义域的布尔表达式被称之为相关部分函数的前置条件(precondition。这里的removeitem表达式的前置条件是参数栈不能为空。在require子句之前是带有虚参数名称的函数名(例子中的s栈参数),因此,前置条件能够指代它们。

 

Mathematically, the precondition of a function f is the characteristic function of the domain of f. The characteristic function of a subset A of a set X is the total function ch: X ® BOOLEAN such that ch (x) is true if x belongs to A, false otherwise.

精确地说,一个函数f前置条件是f定义域的特征函数(characteristic function)。集合X的一个子集A的特征函数是完全函数ch: X ® BOOLEAN,因此,如果x属于A,那么ch (x)为真,反之为假。

 

The complete specification

完整的规格

 

The PRECONDITIONS paragraph concludes this simple specification of the STACK abstract data type. For ease of reference it is useful to piece together the various components of the specification, seen separately above. Here is the full specification:

PRECONDITIONS段是STACK抽象数据类型的简单规格的最后一段。为了便于引用,把上面我们所得到的各个单独的规格部分合并起来,便得到了这个完整的规格:

 


 


Nothing but the truth

现实性

 

The power of abstract data type specifications comes from their ability to capture the essential properties of data structures without overspecifying. The stack specification collected on the preceding page expresses all there is to know about the notion of stack in general, excluding anything that only applies to some particular representations of stacks. All the truth about stacks; yet nothing but the truth.

抽象数据类型的力量来自于这样的一种能力,它们能捕捉数据结构的基本属性,却并不冗余。上面所收集的栈规格使我们了解了栈的普遍概念,而排除了应用在一些特别的栈表示法上的特殊性。所有这些都是栈的现实性。

 

This provides a general model of computation with data structures. We may describe complex sequences of operations by mathematical expressions enjoying the usual properties of algebra; and we may view the process of carrying out the computation (executing the program) as a case of algebraic simplification.

这里提供了一个数据结构计算的通用模型。通过数学表达式并使用代数的一般性质,我们可以描述复杂的运算次序;并且,作为一个代数简化的例子,我们可以看到完成计算(执行程序)的过程。

 

In elementary mathematics we have been taught to take an expression such as

cos2 (a – b) + sin2 (a + b – 2 ´ b)

and apply the rules of algebra and trigonometry to simplify it. A rule of algebra tells us that we may simplify a + b – 2 ´ b into a – b for any a and b; and a rule of trigonometry tells us that we can simplify cos2 (x) + sin2 (x) into 1 for any x. Such rules may be combined; for example the combination of the two preceding rules allow us to simplify the above expression into just 1.

在初等数学中,我们曾学过使用下列的表达式cos2 (a – b) + sin2 (a + b – 2 ´ b)

,并运用代数和三角函数的定理来简化。代数定理告诉我们,对于任意的ab我们可以把a + b – 2 ´ b简化成a – b;从三角函数定理得之,对于任意的xcos2 (x) + sin2 (x)等于1。这样的定理可以组合使用;例如,结合这两个定理来简化上述表达式,我们得到1

 

In a similar way, the functions defined in an abstract data type specification allow us to construct possibly complex expressions; and the axioms of the ADT allow us to simplify such expressions to yield a simpler result. A complex stack expression is the mathematical equivalent of a program; the simplification process is the mathematical equivalent of a computation, that is to say, of executing such a program.

同样的,在一个抽象数据类型规格中定义的函数允许我们建立或许很复杂的表达式;ADT的定理让我们能够简化这类表达式以产生一个更为简单的结果。一个复杂的栈表达式相当于一个程序上的数学题;简化过程就是一个计算过程的数学解题步骤,更确切地说是一个程序执行过程的数学解题步骤。

 

Here is an example. With the specification of the STACK abstract data type as given above, we can write the expression

这里有一个例子。对于上面给出的STACK抽象数据类型规格,我们可以写出下列表达式

item (remove (put (remove (put (put (

remove (put (put (put (new, x1), x2), x3)),

item (remove (put (put (new, x4), x5)))), x6)), x7)))

 

Let us call this expression stackexp for future reference. It is perhaps easier to understand stackexp if we define it in terms of a sequence of auxiliary expressions:

为了便于下面引用,让我们称此表达式为stackexp。如果我们利用辅助表达的方式,这也许能更容易地理解stackexp

 

s1 = new

s2 = put (put (put (s1, x1), x2), x3)

s3 = remove (s2)

s4 = new

s5 = put (put (s4, x4), x5)

s6 = remove (s5)

y1 = item (s6)

s7 = put (s3, y1)

s8 = put (s7, x6)

s9 = remove (s8)

s10 = put (s9, x7)

s11 = remove (s10)

stackexp = item (s11)

 

Whichever variant of the definition you choose, it is not hard to follow the computation of which stackexp is a mathematical model: create a new stack; push elements x1, x2, x3, in this order, on top of it; remove the last pushed element (x3), calling s3 the resulting stack; create another empty stack; and so on. Or you can think of it graphically:

不论您选择什么样的定义变体,理解stackexp这样的一个数学模型的运算并不困难:  创建一个新的栈;把x1, x2, x3顺序地压入栈顶;移去最后一个压入栈的元素(x3),这样得到的栈称之s3;创建另外一个空栈;等等。或者,您可以把它想象成图表:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


You can easily find the value of such an ADT expression by drawing figures such as the above. (Here you would find x4.) But the theory enables you to obtain this result formally, without any need for pictures: just apply the axioms repeatedly to simplify the expression until you cannot simplify any further. For example:

通过绘制类似上述的图表,您可以轻松地得到这样一个ADT表达式的值。(这里您可以得到x4。)但是,无须借助图形,理论就能帮助我们正式地获得这个结果:仅仅反复地运用定理来简化表达式直到您不再能进一步地简化。比如:

 

• Applying A2 to simplify s3, that is to say remove (put (put (put (s1, x1), x2), x3)), yields put (put (s1, x1), x2)). (With A2, any consecutive remove-put pair cancels out.)

·运用A2简化s3remove (put (put (put (s1, x1), x2), x3)),产生put (put (s1, x1), x2))。(用A2消去任何连续的remove-put对。)

 

• The same axiom indicates that s6 is put (s4, x4); then we can use axiom A1 to deduce that y1, that is to say item (put (s4, x4)), is in fact x4, showing that (as illustrated by the arrow on the above figure) s7 is obtained by pushing x4 on top of s3.

·同样的定理表示s6put (s4, x4);接着,我们能够使用定理A1推导y1item (put (s4, x4)),实际上就是x4,并压入x4s3栈顶得出s7(就是图中箭头所示)。

 

And so on. A sequence of such simplifications, carried out as simply and mechanically as the simplifications of elementary arithmetic, yields the value of the expression stackexp, which (as you are invited to check for yourself by performing the simplification process rigorously) is indeed x4.

诸如此类。这样的简化次序,就和小学算术一样简单和按部就班,得到了表达式stackexp的值x4,(您可以用这样的简化步骤自己试一下)。

 

This example gives a glimpse of one of the main theoretical roles of abstract data types: providing a formal model for the notion of program and program execution. This model is purely mathematical: it has none of the imperative notions of program state, variables whose values may change in time, or execution sequencing. It relies on the standard expression evaluation techniques of ordinary mathematics.

这个例子简单地描绘了抽象数据类型的一个主要理论作用:对程序概念和程序执行提供了一个正式的模型。这个模型是纯数学的:它没有程序中的声明,可以随时改变值的变量,或者执行次序的必要概念。它依赖于常规数学的标准表达式的推导方法。

原创粉丝点击