6.5 FROM ABSTRACT DATA TYPES TO CLASSES
6.5 从抽象数据类型到类
We have the starting point of an elegant mathematical theory for modeling data structures and in fact, as we just saw, programs in general. But our subject is software architecture, not mathematics or even theoretical computing science! Have we strayed from our path?
对数据结构建模,事实上正如我们刚才所见的,通常是对程序建模,我们有了一个数学理论上的起点。但是我们的目标是软件架构,不是数学,甚至不是理论上的计算科学!我们偏离方向了吗?
Not by much. In the search for a good modular structure based on object types, abstract data types provide a high-level description mechanism, free of implementation concerns. They will lead us to the fundamental structures of object technology.
还没有。在对象类型的基础上研究一个健全的模块结构,抽象数据类型提供了一个高层次的描述机制,并不考虑实现上的关系。它们将引导我们去了解对象技术的基本结构。
Classes
类
ADTs will serve as the direct basis for the modules that we need in the search begun in chapter 3. More precisely, an object-oriented system will be built (at the level of analysis, design or implementation) as a collection of interacting ADTs, partially or totally implemented. The basic notion here is class:
ADT将提供模块的直接基础,那是我们在第3章开始需要研究的东西。更精确地说,一个面向对象系统将被构建(在分析,设计和实现的层次)作为一个部分或完全实现的交互ADT的集合。这里的基本概念是类:
Definition: class A class is an abstract data type equipped with a possibly partial implementation. 定义:类 类是一个具有可能部分实现的抽象数据类型。 |
So to obtain a class we must provide an ADT and decide on an implementation. The ADT is a mathematical concept; the implementation is its computer-oriented version. The definition, however, states that the implementation may be partial; the following terminology separates this case from that of a fully implemented class:
因此,要获得一个类,我们必须提供一个ADT并决定一个实现。ADT是一个数学上的概念;实现是其面向计算机的版本。然而,定义说明了实现也许只是部分的;下面的术语从完全实现的类中分开了这两种情况:
Definition: deferred, effective class A class which is fully implemented is said to be effective. A class which is implemented only partially, or not at all, is said to be deferred. Any class is either deferred or effective. 定义:延期类和有效类 一个完全实现的类称之为有效类(effective)。一个只是部分实现,或完全没有实现的类,称之为延期类(deferred)。任何类要么是有效类,要么是延期类。 |
To obtain an effective class, you must provide all the implementation details. For a deferred class, you may choose a certain style of implementation but leave some aspects of the implementation open. In the most extreme case of “partial” implementation you may refrain from making any implementation decision at all; the resulting class will be fully deferred, and equivalent to an ADT.
要获得一个有效类,您必须提供所有的实现细节。对于一个延期类,您可以选择一部分实现,但要保留一些实现部分的开放。在最极端的“部分”实现的情况中,您也许会根本不作出任何的实现决定;所得到的类将是完全延期,并等同于一个ADT。
How to produce an effective class
如何产生一个有效类
Consider first the case of effective classes. What does it take to implement an ADT? Three kinds of element will make up the resulting effective class:
先来考虑有效类的情形。它用什么来实现ADT?三种元素将建立有效类。
E1 • An ADT specification (a set of functions with the associated axioms and preconditions, describing the functions’ properties).
E1· 一个ADT规格(具有相关定理和前置条件来描述函数属性的一组函数)。
E2 • A choice of representation.
E2· 一个表示法的选择。
E3 • A mapping from the functions (E1) to the representation (E2) in the form of a set of mechanisms, or features, each implementing one of the functions in terms of the representation, so as to satisfy the axioms and preconditions. Many of these features will be routines (subprograms) in the usual sense, although some may also appear as data fields, or “attributes”, as explained in the next chapters.
E3·以一组方法或特性(features)的形式表示的从函数(E1)到表示法(E2)的对应关系,每一个方法或特性根据表示法实现了一个函数,使得满足定理和前置条件。许多的特性将表现为通常意义上的例程(子程序),虽然一部分可能以数据字段或“属性”的形式出现,下一章会给出解释。
For example, if the ADT is STACK, we may choose as representation (step E2) the solution called ARRAY_UP above, which implements any stack by a pair
<representation, count>
where representation is an array and count an integer. For the function implementations (E3) we will have features corresponding to put, remove, item, empty and new, which achieve the corresponding effects; for example we may implement put by a routine of the form
举个例子,如果ADT是STACK,我们可以选择表示法(步骤E2),方案是上述的ARRAY_UP,其使用一个<representation, count>对实现任意的栈。这里representation是一个数组,count是一个整数。对于函数的实现(E3),我们将有与put, remove, item, empty和new相对应的特性,这些特性完成了与之相关的作用;例如,我们可以用一个例程的形式来实现put
put (x: G) is
-- Push x onto stack.
-- (No check for possible stack overflow.)
do
count := count + 1
representation [count] := x
end
The combination of elements obtained under E1, E2 and E3 will yield a class, the modular structure of object technology.
组合从E1,E2和E3所得到的元素,将产生一个类,这是对象技术上的模块结构。
The role of deferred classes
延期类的角色
For an effective class, all of the implementation information (E2, E3 above) must be present. If any of it is missing, the class is deferred.
对于一个有效类,所有的实现信息(上面的E2,E3)必须存在。如果缺少其中的任何一个,类就是延期的。
The more deferred a class, the closer it is to an ADT, gussied up in the kind of syntactic dress that will help seduce software developers rather than mathematicians. Deferred classes are particularly useful for analysis and for design:
类越是延期的,就越接近一个ADT,穿着语法的外衣将帮助诱惑软件开发者而不是数学家。延期类对分析和设计特别地有用:
• In object-oriented analysis, no implementation details are needed or desired: the method uses classes only for their descriptive power.
•在面向对象的分析中,实现细节不是必须的或期望的:分析方法使用类只是为了它们的描述能力。
• In object-oriented design, many aspects of the implementation will be left out; instead, a design should concentrate on high-level architectural properties of the system — what functionalities each module provides, not how it provides them.
•在面向对象的设计中,实现的许多方面将被省略;与之替代的是,设计应该集中在系统的高级架构属性上—每个模块所能提供什么样的功能性,而不是如何提供它们。
• As you move your design gradually closer to a full implementation, you will add more and more implementation properties until you get effective classes.
•由于您不断地完善您的设计并逐渐地接近一个完全实现,您将会加入越来越多的实现属性,直到您得到有效类。
But the role of deferred classes does not stop there, and even in a fully implemented system you will often find many of them. Some of that role follows from their previous applications: if you started from deferred classes to obtain effective ones, you may be well inspired to keep the former as ancestors (in the sense of inheritance) to the latter, to serve as a living memory of the analysis and design process.
但是,延期类的角色并不止这些,并且,在一个完全实现的系统中,您也会经常发现它们的身影。其中的一些角色来自于它们的先前应用:如果您是从延期类开始而获得有效类,那么您可能有着足够的动机来保留延期类作为有效类的来源(有着继承的意义),当做是分析和设计过程中的活生生的记录。
Too often, in software produced with non-object-oriented approaches, the final form of a system contains no record of the considerable effort that led to it. For someone who is asked to perform maintenance — extensions, ports, debugging — on the system, trying to understand it without that record is as difficult as it would be, for a geologist, to understand a landscape without having access to the sedimentary layers. Keeping the deferred classes in the final system is one of the best ways to maintain the needed record.
经常性的,在用非面向对象方法产生的软件中,系统的最终形式没有包含系统产生的来龙去脉的记录。对于那些系统维护—扩展,迁移,除错—的人员来说,试图理解没有记录的系统,就和地质学家不经过沉积层就来了解地表一样的困难。在最终系统中保留延期类对于维护所需之记录是最好的方式之一。
Deferred classes also have purely implementation-related uses. They serve to classify groups of related types of objects, provide some of the most important high-level reusable modules, capture common behaviors among a set of variants, and play a key role (in connection with polymorphism and dynamic binding) in guaranteeing that the software architecture remains decentralized and extendible.
延期类也有着纯粹的实现相关的用途。它们用于对对象的相关类型的组进行分类,提供一些最重要的高阶层的可复用的模块,捕获一组变体中的通用行为,在保证软件架构保持分散性和扩充性起着关键的作用(与多态和动态绑定有关)。
The next few chapters, which introduce the basic object-oriented techniques, will at first concentrate on effective classes. But keep in mind the notion of deferred class, whose importance will grow as we penetrate the full power of the object-oriented method.
后续的一些介绍基本面向对象技术的章节,会首先集中在有效类上。但是,紧记延期类的观念,当我们洞察面向对象方法的全部能力的时候,它的重要性将会逐步体现出来。
Abstract data types and information hiding
抽象数据类型和信息隐藏
A particularly interesting consequence of the object-oriented policy of basing all modules on ADT implementations (classes) is that it provides a clear answer to a question that was left pending in the discussion of information hiding: how do we select the public and private features of a module — the visible and invisible parts of the iceberg?
对于所有的模块都基于ADT实现(类)
这样的面向对象策略,一个特别引起关注的结果是它对一个问题提供了一个清晰的答案,这是在信息隐藏的讨论中悬而未决的问题:我们如何选择一个模块的公共和私有特性—冰山的可视和隐藏部分?
If the module is a class coming from an ADT as outlined above, the answer is clear: of the three parts involved in the transition, E1, the ADT specification, is public; E2 and E3, the choice of representation and the implementation of the ADT functions in terms of this representation, should be secret. (As we start building classes we will encounter a fourth part, also secret: auxiliary features needed only for the internal purposes of these routines.)
如果模块是一个来自于之前所描述的ADT类,答复是很清楚的:所有的三个部分都包含在转变中,ADT规格E1是公共的;E2和E3,表示法选择和根据这个表示法所得到的ADT函数的实现,应该是秘密的。(当我们开始建立类的时候,我们将会遇到第四部分,那也是秘密的:辅助特性,这仅仅是这些例程的内部用途的需求。)
So the use of abstract data types as the source of our modules gives us a practical, unambiguous guideline for applying information hiding in our designs.
因此,把抽象数据类型用于我们的模块的来源,对于在我们的设计中应用信息隐藏给出了一个实用的,明确的指导方针。
Introducing a more imperative view
引进一个命令性的观点
The transition from abstract data types to classes involves an important stylistic difference: the introduction of change and imperative reasoning.
从抽象数据类型到类的转变中包括了一个重要的格式差异:引进了变化和命令推理。
As you will remember, the specification of abstract data types is change-free, or, to use a term from theoretical computing science, applicative. All features of an ADT are modeled as mathematical functions; this applies to creators, queries and commands. For example the push operation on stacks is modeled by the command function
put: STACK [G] ´ G ® STACK [G]
specifying an operation that returns a new stack, rather than changing an existing stack.
请记住,抽象数据类型的规格是无变化的,或者,使用一个计算科学理论的术语,适用的(applicative)。ADT的所有特性都被模拟成数学函数;创建符、查询和命令皆是如此。例如压栈运算被命令函数
put: STACK [G] ´ G ® STACK [G]
模拟,这个函数描述了一个返回一个新栈的运算,而不是改变现有的栈。
Classes, which are closer to the world of design and implementation, abandon this applicative-only view and reintroduce commands as operations that may change objects.
类更加接近设计和实现领域,它摒弃了这个单纯适用的观点,并且从新引进了命令作为可以改变对象的运算。
For example, put will appear as a routine which takes an argument of type G (the formal generic parameter), and modifies a stack by pushing a new element on top — instead of producing a new stack.
举个例子,put将作为例程出现,它接受一个G类型的参数(泛化形参),通过压入一个新的元素进栈顶而修改一个栈—而不是生产一个新的栈。
This change of style reflects the imperative style that prevails in software construction. (The word “operational” is also used as synonym for “imperative”.) It will require the corresponding change in the axioms of ADTs. Axioms A1 and A4 of stacks, which appeared above as
这种风格上的变动反映了在软件构造上命令格式的盛行。(单词“运算的”也被使用为“命令的”同义词。)它要求在ADT的定理上有着对应的变化。之前曾出现的栈的定理A1和A4
will yield, in the imperative form, a clause known as a routine postcondition, introduced by the keyword ensure in
将在命令形式中产生一个例程后置条件(routine postcondition)的子句,引进了关键字ensure
put (x: G) is
-- Push x on top of stack
require
¼ The precondition, if any ¼
do
¼ The appropriate implementation, if known ¼
ensure
item = x
not empty
end
Here the postcondition expresses that on return from a call to routine put, the value of item will be x (the element pushed) and the value of empty will be false.
在这里,后置条件表示了在调用例程put的返回值中, item的值将是x (被压入的元素),empty的值将为假(false)。
Other axioms of the ADT specification will yield a clause known as the class invariant. Postconditions, class invariants and other non-applicative avatars of an ADT’s preconditions and axioms will be studied as part of the discussion of assertions and Design by Contract.
ADT规格的其它定理将产生一个类不变式(class invariant)的子句。我们将会学习后置条件,类不变式,ADT前置条件和公理的其它非适用性的具体形式,作为断言和契约设计讨论的一部分。
Back to square one?
又回到了起点?
If you followed carefully, starting with the chapter on modularity, the line of reasoning that led to abstract data types and then classes, you may be a little puzzled here. We started with the goal of obtaining the best possible modular structures; various arguments led to the suggestion that objects, or more precisely object types, would provide a better basis than their traditional competitors — functions. This raised the next question: how to describe these object types. But when the answer came, in the form of abstract data types (and their practical substitutes, classes), it meant that we must base the description of data on¼ the applicable functions! Have we then come full circle?
从模块化那章的开始就存在着一条推理线索,推导了抽象数据类型和其后的类,如果您仔细地跟随了这条线索,那么这里您可能有一点困惑。我们从得到最佳模块化结构的目标开始;各种各样的论据导致了这样的建议,即对象或者更加精确的是对象类型,比它们的传统竞争对手—函数—提供了一个更优良的基础。这引发了下面的这个问题:如何描述这些对象类型。但是,当从抽象数据类型(和它们的实际应用上的类型,类)之中得到答复的时候,它意味着我们必须把数据的描述建立在应用函数上! 转了一大圈,我们又回到了原地?
No. Object types, as represented by ADTs and classes, remain the undisputed basis for modularization.
不。如ADT和类所描绘的对象类型依然是模块化的毫无疑问的基础。
It is not surprising that both the object and function aspects should appear in the final system architecture: as noted in the previous chapter, no description of software issues can be complete if it misses one of these two components. What fundamentally distinguishes object-oriented methods from older approaches is the distribution of roles: object types are the undisputed winners when it comes to selecting the criteria for building modules. Functions remain their servants.
对象和函数这两者都应该出现在最终的系统架构中,这一点也不奇怪:先前章节已经提到,如果软件问题的描述没有包含这二者,那么它是不完整的。面向对象的方法与之前的旧方法在根本上的区别是角色的分配:当提及构建模块的选择标准时,对象类型是毋须质疑的优胜者。函数是为对象服务的。
In object-oriented decomposition, no function ever exists just by itself: every function is attached to some object type. This carries over to the design and implementation levels: no feature ever exists just by itself; every feature is attached to some class.
在面向对象的分解中,函数不能单独存在:每个函数附着在某个对象类型上。这一直延伸到设计和实现层次:特性不能单独存在;每个特性附着在某个类上。
Object-oriented software construction
面向对象软件构造
The study of abstract data types has given us the answer to the question asked at the beginning of this chapter: how to describe the object types that will serve as the backbone of our software architecture.
抽象数据类型的研究给了我们在本章开始时所问问题的答案:如何描述将担当我们的软件架构中的中流砥柱的对象类型。
We already had a definition of object-oriented software construction: remaining at a high level of generality, it presented the method as “basing the architecture of any software system on modules deduced from the types of objects it manipulates”. Keeping that first definition as the framework, we can now complement it with a more technical one:
我们已经有了一个面向对象软件构造的定义:保持在普遍性的高级层次上,它提出了方法如“建立在模块之上的软件系统的架构从它所操纵的对象类型中推导出来”。保持第一个定义作为框架,现在我们可以用更加技术化的词语补全它:
Object-oriented software construction (definition 2) Object-oriented software construction is the building of software systems as structured collections of possibly partial abstract data type implementations. 面向对象软件构造(定义2) 面向对象软件构造是软件系统的建筑,这种软件系统是可能部分的抽象数据类型实现的结构化集合。 |
This will be our working definition. Its various components are all important:
这将是我们采用的定义。它的各个组成部分都很重要:
• The basis is the notion of abstract data type.
·主要要素是抽象数据类型的概念。
• For our software we need not the ADTs themselves, a mathematical notion, but ADT implementations, a software notion.
·对于我们的软件,我们不需要ADT本身,一个数学概念,而需要ADT实现,一个软件概念。
• These implementations, however, need not be complete; the “possibly partial ” qualification covers deferred classes — including the extreme case of a fully deferred class, where none of the features is implemented.
·然而,这些实现不必完成;“可能部分的”规格包含了延期类—包括了一个完全延期类的极端情况,其中没有一个特性是实现的。
• A system is a collection of classes, with no one particularly in charge — no top or main program.
·一个系统是一个类的集合,没有一个特别的领导—没有顶端,也没有主程序。
• The collection is structured thanks to two inter-class relations: client and inheritance.
·集合是结构化的,这是由于两个类之间的关系:客户端和继承。
6.6 BEYOND SOFTWARE
6.6 超越软件
As we are completing our study of abstract data types it is worth taking a moment to reflect on the significance of this notion outside of its immediate intended application area.
我们正在完成我们的抽象数据类型的研究,花一些时间来思考一下这个概念的直接应用范围之外的意义,还是值得的。
What the ADT approach tells us is that a successful intellectual investigation should renounce as futile any attempt at knowing things from the inside, and concentrate instead on their usable properties. Do not tell me what you are; tell me what you have — what I can get out of you. If we need a name for this epistemological discipline, we should call it the principle of selfishness.
ADT方法告诉我们的是,一次成功的理性研究应该放弃从内部了解事物这种徒劳的尝试,改为集中在它们可用的属性上。不要告诉我您是什么;告诉我您有什么-什么是我可以从您那里获得的。如果我们需要对于这个认识论规律起一个名字的话,那么我们应该称它为自私原则(principle of selfishness)。
If I am thirsty, an orange is something I can squeeze; if I am a painter, it is color which might inspire my palette; if I am a farmer, it is produce that I can sell at the market; if I am an architect, it is slices that tell me how to design my new opera house, overlooking the harbor; but if I am none of these, and have no other use for the orange, then I should not talk about it, as the concept of orange does not for me even exist.
如果渴了,我可以用桔子榨汁;如果我是画家,桔色可以激发我的色调;如果我是农夫,桔子是我可以在市场上出售的农产品;如果我是建筑师,桔子的剖面告诉我如何设计可以俯视港口的新歌剧院;但如果我并不是这些人,也没有桔子的其它用途,那么我不需要谈论它,因为桔子的概念并不为我而存在。
The principle of selfishness — you are but what you have — is an extreme form of an idea that has played a central role in the development of science: abstraction, or the importance of separating concerns. The two quotations at the beginning of this chapter, each in its own remarkable way, express the importance of this idea. Their authors, Diderot and Stendhal, were writers rather than scientists, although both obviously had a
good understanding of the scientific method (Diderot was the living fire behind the Great Encyclopedia, and Stendhal prepared for admission into the École Polytechnique, although in the end he decided that he could do better things with his life). It is striking to see how both quotations are applicable to the use of abstraction in software development.
自私原则-您有什么-是一种思想的极端形式,这个思想在科学的发展中起着一个重要的作用:抽象性,或是分割理念的重要性。在本章开始的二段引文,每一段都用它自己的非凡的方式,表达了这个思想的重要性。它们的作者Diderot和Stendhal,尽管显而易见地对科学方法有着卓越的见解(Diderot是在被大百科全书点燃热情之后,Stendhal准备进入École Polytechnique工程学校,虽然最后他决定他可以在他的生命中做一件更有意义的的事),但是两人是作家而不是科学家。看到这两段引文是如何在软件开发中应用到抽象性的使用上,这很令人震惊。
Yet there is more than abstraction to the principle of selfishness: the idea, almost shocking at first, that a property is not worth talking about unless it is useful in some direct way to the talker.
并不仅仅只有抽象性有着自私原则:一个属性不值得考虑,除非它用某种直接的方式作用于对话者。这个想法起初几乎是骇人听闻。
This suggests a more general observation as to the intellectual value of our field.
这对软件领域的智力价值提出了一个更加全面的评论。
Over the years many articles and talks have claimed to examine how software engineers could benefit from studying philosophy, general systems theory, “cognitive science”, psychology. But to a practicing software developer the results are disappointing. If we exclude from the discussion the generally applicable laws of rational investigation, which enlightened minds have known for centuries (at least since Descartes) and which of course apply to software science as to anything else, it sometimes seems that experts in the disciplines mentioned may have more to learn from experts in software than the reverse.
多年以来,许多文章和会议都声称在研究软件工程师如何能从学习哲学,一般体系理论,“认知科学”,心理学中受益。但对于一位实践中的软件开发者来说,结果是令人失望的。如果我们从讨论排除理性调研的普遍适用规律,这个规律几个世纪以来(至少从Descartes之后)启迪了我们的思想,并且当然由于其它的原因也适用于软件科学,那么,有时看起来我们所谈到的相关科学的专家能从软件专家那里学到的,要比软件专家从相关科学的专家那里学到的更多。
Software builders have tackled — with various degrees of success — some of the most challenging intellectual endeavors ever undertaken. Few engineering projects, for example, match in complexity the multi-million line software projects commonly being launched nowadays. Through its more ambitious efforts the software community has gained precious insights on such issues and concepts as size, complexity, structure, abstraction, taxonomy, concurrency, recursive reasoning, the difference between
description and prescription, language, change and invariants. All this is so recent and so tentative that the profession itself has not fully realized the epistemological implications of its own work.
软件建造者-以各种不同的成功方式-解决了一些最富挑战性的智力活动,这些智力活动曾被挑战过(但未获成功)。例如,数百万行代码的软件项目如今正不断地发布,很少有工程项目能与之匹敌。通过更加斗志昂扬的努力,软件社区在大小,复杂性,结构,抽象,分类学,并发,递归推理,描述和规则的差异、语言、变化和不变式,诸如此类的议题和概念上获得了弥足珍贵的洞察力。所有这些都是新近发生的,还处于尝试阶段,以至于软件行业本身还没有完全地意识到其工作中的认识论涵义。
Eventually someone will come and explain what lessons the experience of software construction holds for the intellectual world at large. No doubt abstract data types will figure prominently in the list.
对于进行在智力领域中的软件构造,最终会有人出来详尽地解释从其经历中所获得的经验。毫无疑问,抽象数据类型将会突出地显示其中。
6.7 SUPPLEMENTARY TOPICS
6.7 主题补充
The view of abstract data types presented so far will suffice for the uses of ADTs in the rest of this book. (To complement it, doing the exercises will help you sharpen your understanding of the concept.)
到目前为止,抽象数据类型所描绘的观点对于本书其余部分的ADT的使用已经足够了。(作为补充,做一些练习将帮助您加深对概念的理解。)
If, as I hope, you have been conquered by the elegance, simplicity and power of ADTs, you may want to explore a few more of their properties, even though the discussion of object-oriented methods will not use them directly. These supplementary topics, which may be skipped on first reading, are presented in the next few pages:
如果您被ADT的高雅、朴素和力量所折服,即使面向对象方法的讨论不会直接地使用它们,我希望您也应该对其属性了解得更多一些。在下面的几页中将描述一些增补的主题(如果您是首次阅读本书,您可以跳过去不看):
• Implicitness and its relationship to the software construction process.
·软件构造过程中的隐含性及其关联。
• The difference between specification and design.
·规格和设计之间的差异。
• The differences between classes and records.
·类和记录之间的差异。
• Potential alternatives to the use of partial functions.
·对于部分函数的使用,另一种替代选择的可能性。
• Deciding whether a specification is complete or not.
·一个规格是否完整之决定。
The bibliographical references to this chapter point to more advanced literature on abstract data types.
本章的参考书目列出了更高阶的抽象数据类型文献。
More on implicitness
更多的隐含性
The implicit nature of abstract data types and classes, discussed above, reflects an important problem of software construction.
上面所讨论的抽象数据类型和类的隐含性反映一个软件构造的重要问题。
One may legitimately ask what difference there is between a simplified ADT specification, using the function declarations
x: POINT ® REAL
y: POINT ® REAL
and the record type declaration which we may express in a traditional programming language such as Pascal under the form
type
POINT =
record
x, y: real
end
一个也许合理地提问是:一个简单的用函数声明的ADT规格
x: POINT ® REAL
y: POINT ® REAL
和用一个传统的程序语言如Pascal来表示记录类型
type
POINT =
record
x, y: real
end
之间有什么样的不同。
At first sight, the two definitions appear equivalent: both state that any instance of type POINT has two associated values x and y, of type REAL. But there is a crucial if subtle difference:
乍一看,两个定义有着相似性:两者都陈述了POINT类型的任何实例有着两个相关联的值x和y,它们是REAL类型。但是,这里有一个至关重要但不太明显的差异:
• The Pascal form is closed and explicit: it indicates that a POINT object is made of the two given fields, and no other.
·Pascal形式是关闭和显示的:它指出了一个POINT对象是由两个仅有的给定字段组成。
• The ADT function declarations carry no such connotation. They indicate that one may query a point about its x and its y, but do not preclude other queries — such as a point’s mass and velocity in a kinematics application.
·ADT函数的声明没有这样的含意。它们表示了一个可以查询其x和y的点,但是并没有排除其它的查询—如在运动学应用中的一个点的质量速度。
From a simplified mathematical perspective, you may consider that the above Pascal declaration is a definition of the mathematical set POINT as a cartesian product:
POINT= D REAL ´ REAL
where = D means “is defined as”: this defines POINT fully. In contrast, the ADT specification does not explicitly define POINT through a mathematical model such as the cartesian product; it just characterizes POINT implicitly by listing two of the queries applicable to objects of this type.
从简单的数学观点来看,您可以把上述的Pascal定义认为是一个笛卡尔乘积的数学集合POINT定义
POINT= D REAL ´ REAL
这里,= D是“定义为”的意思:这是POINT完整定义。相反,ADT规格没有明确地把POINT定义为如笛卡尔乘积这样的一个完全的数学模型;她只是通过应用到这种类型的对象上的两个查询来隐含地刻画了POINT。
If at some stage you think you are done with the specification of a certain notion, you may want to move it from the implicit world to the explicit world by identifying it with the cartesian product of the applicable simple queries; for example you will identify points with <x, y> pairs. We may view this identification process as the very definition of the transition from analysis and specification to design and implementation.
如果您认为在某个阶段您已经完成了某个概念的规格,那么,通过应用简单查询的笛卡尔乘积来验证,您也许想把它从隐含的领域移动到显示的领域;比如,您可以用<x, y>对来证明点。我们可以把这个验证过程看成是从分析和规格到设计和实现演变的真正定义。
Specification versus design
规格v设计
The last observation helps clarify a central issue in the study of software: the difference between the initial activities of software development — specification, also called analysis — and later stages such as design and implementation.
最后的观察结果帮助澄清了在软件的研究当中的一个核心问题:软件开发的初始活动—规格,也叫分析—和后期阶段例如设计和实施之间的区别。
The software engineering literature usually defines this as the difference between “defining the problem” and “building a solution”. Although correct in principle, this definition is not always directly useful in practice, and it is sometimes hard to determine where specification stops and design begins. Even in the research community, people routinely criticize each other on the theme “you advertize notation x as a specification language, but what it really expresses is designs”. The supreme insult is to accuse the notation of catering to implementation; more on this in a later chapter.
软件工程文献通常定义这个问题为“定义问题”和“构建解决方案”之间的区别。虽然原则上正确,但是这个定义却不总是能在实践中直接运用,并且有时很难决定在什么地步停止规格开始设计。甚至在研究团体中,人们通常会相互指责“您大肆宣传x符号是规格语言,但它真正表达的却是设计”。指责符合实现需求的符号真是极大的讽刺;在稍后的章节中会进一步讨论。
The above definition yields a more precise criterion: to cross the Rubicon between specification and design is to move from the implicit to the explicit; in other words:
上述定义产生一个更加精确的标准:要跨越在规格和设计之间的界限就是要从隐含转到显示;换句话说:
Definition: transition from analysis (specification) to design To go from specification to design is to identify each abstraction with the cartesian product of its simple queries. 定义:从分析(规格)到设计的转变 要从规格转变到设计是要用简单查询的笛卡尔乘积来验证每一个抽象性。 |
The subsequent transition — from design to implementation — is simply the move from one explicit form to another: the design form is more abstract and closer to mathematical concepts, the implementation form is more concrete and computer-oriented, but they are both explicit. This transition is less dramatic than the preceding one; indeed, it will become increasingly clear in the pages that follow that object technology all but removes the distinction between design and implementation. With good object-oriented notations, what our computers directly execute (with the help of our compilers) is what to the non-O-O world would often appear as designs.
随后的转变—从设计到实现 —是简单地从一个明确形式移动到另一个:设计形式是更加抽象的,并且更接近于数学概念,实现形式更加具体的和面向计算机的,但它们都是明确的。相比较之前的从规格转变到设计,这个转变较平和;的确,在对象技术之后的章节中它将变得越来越清楚,几乎消除了在设计和实现之间的差异。伴随着优异的面向对象的符号,我们的计算机直接执行(在我们的编译器帮助下)的正是非O-O世界里经常作为设计出现的东西。
Classes versus records
类v记录
Another remarkable property of object technology, also a result of the focus on implicit definition, is that you can keep your descriptions implicit for a much longer period than with any other approach. The following chapters will introduce a notation enabling us to define a class under the form
class POINT feature
x, y: REAL
end
对象技术的另一个卓越的属性,也是集中在隐定义上的结果,是您能比以其它的方法保持您的隐含描述更长的时间。随后的章节将介绍一个符号使我们能用下面的方式定义类
class POINT feature
x, y: REAL
end
This looks suspiciously close to the above Pascal record type definition. But in spite of appearances the class definition is different: it is implicit! The implicitness comes from inheritance; the author of the class or (even more interestingly) someone else may at any time define a new class such as
class MOVING_POINT inherit
POINT
feature
mass: REAL
velocity: VECTOR [REAL]
end
which extends the original class in ways totally unplanned for by the initial design. Then a variable (or entity, to use the terminology introduced later) of type POINT, declared as
p1: POINT
may become attached to objects which are not just of type POINT but also of any descendant type such as MOVING_POINT. This occurs in particular through “polymorphic assignments” of the form
p1 := mp1
where mp1 is of type MOVING_POINT.
这看着感觉和上面的Pascal记录类型定义相近。尽管看上去类似但其实类定义是不同的:它是隐含的!隐含性来自继承;类的作者或者(更感兴趣的)其他人在任何时候可以定义一个象这样的新类
class MOVING_POINT inherit
POINT
feature
mass: REAL
velocity: VECTOR [REAL]
end
其用了初始设计完全没有计划到的方式扩展了最初的类。接着,声明一个POINT类型的变量(或者实体,这个术语以后介绍)
p1: POINT
它不但可以赋与一个POINT类型的对象,而且还可以赋给任何派生类型的对象,如MOVING_POINT。这尤其出现在多态赋值
p1 := mp1
的时候,这里的mp1是MOVING_POINT类型。
These possibilities illustrate the implicitness and openness of the class definition: the corresponding entities represent not just points in the narrow sense of direct instances of class POINT as initially defined, but, more generally, instances of any eventual class that describes a concept derived from the original.
这些可能性举例说明了类定义的隐含性和开放性:在初始定义的POINT类的直接实例上,对应的实体不只是精确地表达了点,而且更通常的,如果一个类描述了从基类派生而来的概念,那么实体可以表达任何这样的类。
The ability to define software elements (classes) that are directly usable while remaining implicit (through inheritance) is one of the major innovations of object technology, directly answering the Open-Closed requirement. Its full implications will unfold progressively in the following chapters.
软件元素(类)当保持隐含(通过继承)时能直接使用,定义这种软件元素的能力是对象技术的主要创新之一,这直接地解决的开闭的需求。其完整的含义将在随后的章节中逐步地展开。
Not surprisingly for such a revolutionary concept, the realm of new possibilities that it opens still scares many people, and in fact many object-oriented languages restrict the openness in some way. Later chapters will mention examples.
这样的一个革命性的概念并不令人惊奇,许多开放着的全新的可能性领域一直让许多人惊叹不已,并且许多面向对象语言在某些方面事实上制约着开放性。后续章节将会举出一些例子。
Alternatives to partial functions
部分函数的另一种选择
Among the techniques of this chapter that may have caused you to raise your eyebrows is its use of partial functions. The problem that it addresses is inescapable: any specification needs to deal with operations that are not always defined; for example, it is impossible to pop an empty stack. But is the use of partial functions the best solution?
在本章所介绍的技术当中,让您感到惊讶的也许是部分函数的使用。它所涉及的问题无法回避:任何规格都需要处理未事先定义的运算;例如,有可能弹出一个空栈。可是,部分函数的使用是一个最佳的解决方案吗?
It is certainly not the only possible one. Another technique that comes to mind, and is indeed used by some of the ADT literature, is to make the function total but introduce special error values to denote the results of operations applied to impossible cases.
这当然并不是唯一的。另一种引起注意,并正在被一些ADT文献实际所使用的技术,是定义完整的函数,但是引进特别的错误值来表示应用于不完整状况下的运算结果。
For every type T, this method introduces a special “error” value; let us write it wT. Then for any function f of signature
f: ¼ Input types ¼ ® T
it specifies that any application of f to an object for which the corresponding computer operation may not be executed will produce the value wT.
对于每一个T类型,这个方法引进了一个特别的“错误”值;让我们记为wT。接着,对于任何标记式为f: ¼ Input types ¼ ® T
的f函数,它指明了在一个对象上的任何f的应用,其对应的计算运算无法执行,将会产生值wT。
Although usable, this method leads to mathematical and practical unpleasantness. The problem is that the special values are rather bizarre animals, which may unduly disturb the lives of innocent mathematical creatures.
虽然便于使用,但是这个方法导致了数学上的和实际上的不和谐。问题是,这个特别的值就象一个奇特的动物,它不恰当地打乱了无辜的数学创造者的生活。
Assume for example that we consider stacks of integers — instances of the generic
derivation STACK [INTEGER], where INTEGER is the ADT whose instances are integers. Although we do not need to write the specification of INTEGER completely for this discussion, it is clear that the functions defining this ADT should model the basic operations (addition, subtraction, “less than” and the like) defined on the mathematical set of integers. The axioms of the ADT should be consistent with ordinary properties of integers; typical among these properties is that, for any integer n:
[Z1]
n + 1 ¹ n
举个例子,假定我们考虑整数栈—泛化派生STACK [INTEGER]的实例,这里INTEGER是实例为整数的ADT。对这个例子虽然我们不需要完整地写下INTEGER的规格,但是清楚的是,在这个ADT中定义的函数应该定义了在数学的整数集合上基本运算(加,减,小于等等)的模型。ADT的定理应该与整数的基本属性一致;对于任意的整数n,典型的属性是:
[Z1]
n + 1 ¹ n
Now let n be the result of requesting the top of an empty stack, that is to say, the value of item (new), where new is an empty stack of integers. With the “special error element” approach, n must be the special value wINTEGER. What then is the value of the expression n + 1? If the only values at our disposal are normal integers and wINTEGER, then we ought to choose wINTEGER as the answer:
wINTEGER + 1 = wINTEGER
现在,设n为对一个空栈查询顶点元素的结果,也就是说,item (new)的值,其中,new是一个整数型的空栈。对于“特殊错误元素”的方法,n应该是特殊值wINTEGER。那么,表达式n + 1的值又是什么?如果在我们的处理中唯一值是常规整数和wINTEGER,那么,我们应该选择wINTEGER作为结果:wINTEGER + 1 = wINTEGER
This is the only acceptable choice: any other value for wINTEGER + 1, that is to say, any “normal” integer q, would mean in practical terms that after we attempt to access the top of an empty stack, and get an error value as a result, we can miraculously remove any trace of the error, simply by adding one to the result! This might have passed when all it took to erase the memory of a crime was a pilgrimage to Santiago de Compostela and the purchase of a few indulgences; modern mores and computers are not so lenient.
这是唯一可以接受的选择:对于wINTEGER + 1的任何其它值,也就是说,任何“常规“的整数q,实际上意味着在我们试图接近一个空栈的顶点,并且得到一个错误的结果之后,我们能够奇迹般地除掉任何错误的轨迹,只是简单地在结果上加一!要去掉犯罪记录所要做的只是去西班牙圣地亚哥·德孔波斯特拉古城朝圣并买回一些特赦,所有的一切就过去了;现代的道德观念和计算机并不会如此仁慈。
But choosing wINTEGER as the value of n + 1 when n is wINTEGER violates the above Z1 property. More generally, wINTEGER + p will be wINTEGER for any p. This means we must develop a new axiom system for the updated abstract data type (INTEGER enriched with an error element), to specify that every integer operation yields wINTEGER whenever any one of its arguments is wINTEGER. Similar changes will be needed for every type.
但是当n是wINTEGER时选择wINTEGER为n + 1的值,这违背了上述的Z1属性。更通常的是,对任意的p,wINTEGER + p都将为wINTEGER。这意味着我们必须对最新的抽象数据类型(添加一个错误元素的INTEGER)开发一个新的定理体系,来详细说明每一个整数运算都将产生wINTEGER只要其中任意的一个参数是wINTEGER。每一种类型都将需要这种类似的改变。
The resulting complication seems unjustifiable. We cannot change the specification of integers just for the purpose of modeling a specific data structure such as the stack.
结果的复杂性似乎没什么道理。我们不可能仅仅只是为了塑造一种特殊的数据结构例如栈的模型而改变整数的规格。
With partial functions, the situation is simpler. You must of course verify, for every expression involving partial functions, that the arguments satisfy the corresponding preconditions. This amounts to performing a sanity check — reassuring yourself that the result of the computation will be meaningful. Having completed this check, you may apply the axioms without further ado. You need not change any existing axiom systems.
用部份函数,情况就简单多了。当然对含有部份函数的每个表示式,您必须核实参数是否满足对应的前置条件。这相当于完成一个智力检查—让您自己确信计算的结果是有意义的。完成了这样地检查,您就可以放心大胆地运用定理了。您不需要改变任何现有的定理体系。
Is my specification complete?
我的规格完成了吗?
Another question may have crossed your mind as you were reading the above example of abstract data type specification: is there is any way to be sure that such a specification describes all the relevant properties of the objects it is intended to cover? Students who are asked to write their first specifications (for example when doing the exercises at the end of this chapter) often come back with the same question: when do I know that I have specified enough and that I can stop?
当您阅读上述的抽象数据类型规格例子的时候,另一个问题也许出现在您的脑海里:有没有一种方式来确定一个规格,它已经描述了它试图使用的对象的所有相关的属性?被要求编写他们的第一个规格时(例如,当在做本章末尾所列出的练习的时候),学生们经常反复地问同一个问题:我何时知道我已经足够详细的描述了规格,可以不再继续了?
In more general terms: does a method exist to find out whether an ADT specification is complete?
用更概况的话来说:是否存在一个方法来判断一个ADT规格是否完整?
If the question is asked in this simple form, the answer is a plain no. This is true of formal specifications in general: to say that a specification is complete is to claim that it covers all the needed properties; but this is only meaningful with respect to some document listing these properties and used as a reference. Then we face one of two equally disappointing situations:
如果问题以这种简单形式来问,那么答复也很简单-没有。一般来说正式的规格是这样的:要说一个规格是完整的就是要要求它包括了所有需要的属性;但这只是对列出这些属性并作为参考来使用的一些文档有意义。那么,有二种令人失望的情况,我们需要面对它们:
• If the reference document is informal (a natural-language “requirements document” for a project, or perhaps just the text of an exercise), this lack of formality precludes any attempt to check systematically that the specification meets all the requirements described in that document.
•如果参考文档是非正式的(一个项目中用自然语言描述的“需求文档”,或者只是一段练习用的文字),那么,由于缺乏正式的形式,这阻碍了任何要系统性地证实规格实现了文档中所描述的所有要求的尝试。
• If the reference document is itself formal, and we are able to check the completeness of our specification against it, this merely pushes the problem further: how do we ascertain the completeness of the reference document itself ?
• 如果参考文档本身是正式的,并且我们能检查规格的完整性,那么这只是把问题推进了一步:我们怎么确定参考文档本身是完整的?
In its trivial form, then, the completeness question is uninteresting. But there is a more useful notion of completeness, derived from the meaning of this word in mathematical logic. For a mathematician, a theory is complete if its axioms and rules of inference are powerful enough to prove the truth or falsity of any formula that can be expressed in the language of the theory. This meaning of completeness, although more limited, is intellectually satisfying, since it indicates that whenever the theory lets us express a property it also enables us to determine whether the property holds.
在一些微不足道的形式中的完整性问题不会引人注意。但是这里完整性有一个比较有用的概念,这个概念是从这个单词在数学逻辑的意思中获得的。对于数学家来说,如果推导一个理论的定理和规则能足够强有力地证明任何可以用理论的语言被表达的表达式真或假的话,那么这个理论就是完整的。完整性的这个含意,虽然有着较多的限制,但是理性上令人满意,因为它表明了只要理论能让我们表达一个属性,它也能使我们决定属性是否有效。
How do we transpose this idea to an ADT specification? Here the “language of the theory” is the set of all the well-formed expressions, those expressions which we may build using the ADT’s functions, applied to arguments of the appropriate types. For example, using the specification of STACK and assuming a valid expression x of type G, the following expressions are well-formed:
我们如何把这个想法移置到ADT规格上来?这里的“理论语言”是所有合适形式的表达式(well-formed expressions)的集合,我们可以使用ADT函数,并应用适当类型的参数来建立那些表达式。例如,使用STACK规格并且假设一个有效的G类型的表达式x,下列的表达式是well-formed:
new put (new, x) item (new) -- If this seems strange, see comments on the next page. empty (put (new, x)) stackexp -- The complex expression defined on page 140.
|
The expressions put (x) and put (x, new), however, are not well-formed, since they do not abide by the rules: put always requires two arguments, the first of type STACK [G] and the second of type G; so put (x) is missing an argument, and put (x, new) has the wrong argument types.
可是,表达式put (x)和put (x, new)并不是well-formed,这是由于它们没有遵守规则:put总是需要俩个参数,第一个是STACK [G]类型,第二个是G类型;因此,put (x)缺少一个参数,put (x, new)有着错误的参数类型。
The third example in the preceding box, item (new), does not describe a meaningful computation since new does not satisfy the precondition of item. Such an expression, although well-formed, is not correct. Here is the precise definition of this notion:
由于new没有满足item的前置条件,在上面的第三个例子item (new)没有描述一个有意义的计算。这样的表达式,虽然是well-formed的,但是却不正确。这里有一个此概念的精确定义:
Definition: correct ADT expression Let f (x1, ¼, xn ) be a well-formed expression involving one or more functions on a certain ADT. This expression is correct if and only if all the xi are (recursively) correct, and their values satisfy the precondition of f, if any. 定义:正确的ADT表达式 设f (x1, ¼, xn )是一个well-formed的表达式,在一个特定的ADT上包含了一个或多个函数。有且只有所有的xi(递归)都是正确的,并且它们的值满足f的前置条件(如果有的话),那么这个表达式是正确地。 |
Do not confuse “correct” with “well-formed”. Well-formedness is a structural property, indicating whether all the functions in an expression have the right number and types of arguments; correctness, which is only defined for a well-formed expression, indicates whether the expression defines a meaningful computation. As we have seen, the expression put (x) is not well-formed (and so it is pointless to ask whether it is correct), whereas the expression item (new) is well-formed but not correct.
不要把“正确(correct)”误解成“well-formed”。Well-formedness是一个结构属性,表示在一个表达式中所有的函数是否有正确的参数数目和类型;正确性,这只能定义在一个well-formed表达式之上,表示表达式是否定义了一个有意义的计算。我们已经知道了,表达式put (x)并不是一个well-formed(因此问它是否正确没什么意义),然而表达式item (new)是well-formed的,但却不正确。
An expression well-formed but not correct, such as item (new), is similar to a program that compiles (because it is built according to the proper syntax and satisfies all typing constraints of the programming language) but will crash at run time by performing an impossible operation such as division by zero or popping an empty stack.
象item (new)这样的一个well-formed但不正确的表达式,类似于这样的一个程序,能够通过编译(因为它是依照正确地语法构建并满足所有的编程语言的类型约束)但是会在运行的时候崩溃,因为执行了一个不可能的运算如被零除或弹出一个空栈。
Of particular interest for completeness, among well-formed expressions, are query expressions, those whose outermost function is a query. Examples are:
在well-formed的表达式当中,与完整性密切相关的是查询表达式(query expressions),它的最外层的函数是一个查询。譬如:
empty (put (put (new, x1), x2))
item (put (put (new, x1), x2))
stackexp -- See page 140
A query expression denotes a value which (if defined) belongs not to the ADT under definition, but to another, previously defined type. So the first query expression above has a value of type BOOLEAN; the second and third have values of type G, the formal generic parameter — for example INTEGER if we use the generic derivation STACK [INTEGER].
一个查询表达式表示了一个值,这个值不但属于ADT定义,而且属于先前定义的类型。因此,上面的第一个查询表达式有一个BOOLEAN类型的值;第二个和第三个是G类型的值,这是一个泛化形参—如果我们使用泛化派生STACK [INTEGER] 的话,参数就是INTEGER。
Query expressions represent external observations that we may make about the results of a certain computation involving instances of the new ADT. If the ADT specification is useful, it should always enable us to find out whether such results are defined and, if so, what they are. The stack specification appears to satisfy this property, at least for the three example expressions above, since it enables us to determine that the three expressions are defined and, by applying the axioms, to determine their values:
查询表达式描绘了一个外部的观察结果,即我们可以生成一个特定的计算结果,这个计算包括了最新的ADT实例。如果ADT规格是有用的,那么它将总能使我们发现是否这样的结果已经被定义好,假如这样的话,那么它们是什么。栈的规格看上去满足这个属性,至少上面的三个表达式是这样的,由于它使我们能够确定三个表达式是定义的,并且,可以通过应用定理来决定它们的值:
empty (put (put (new, x1), x2)) = False
item (put (put (new, x1), x2)) = x2
stackexp = x4
Transposed to the case of arbitrary ADT specifications, these observations suggest a pragmatic notion of completeness, known as sufficient completeness, which expresses that the specification contains axioms powerful enough to enable us to find the result of any query expression, in the form of a simple value.
把它运用到任意的ADT规格的情况中,这些研究结果提出了完整性的一个实用性概念,即人们所知的充分(sufficient)完整性。这个概念定义了规格须包含充分有效的定理,以使我们能够以简单值的形式得到任何询问表达式的结果。
Here is the precise definition of sufficient completeness. (Non-mathematically inclined readers should skip the rest of this section.)
这里是充分完整性的精确定义。(对数学不感兴趣的读者可以跳过下面的部分。)
Definition: sufficient completeness An ADT specification for a type T is sufficiently complete if and only if the axioms of the theory make it possible to solve the following problems for any well-formed expression e: S1 • Determine whether e is correct. S2 • If e is a query expression and has been shown to be correct under S1, express e’s value under a form not involving any value of type T. 定义:充分完整性 对于任何well-formed的表达式e,有且只有在理论定理可以解决下列问题的情况下: S1·决定e是否正确。 S2·如果e是一个查询表达式,并且在S1的条件下显示为正确,那么在没有包含T类型的任何值的形式中表示e的值。 那么,我们说一个T类型的ADT规格是充分完整的。 |
In S2, expression e is of the form f (x1, ¼, xn) where f is a query function, such as empty and item for stacks. S1 tells us that e has a value, but this is not enough; in this case we also want to know what the value is, expressed only in terms of values of other types (in the STACK example, values of types BOOLEAN and G). If the axioms are strong enough to answer this question in all possible cases, then the specification is sufficiently complete.
在S2中,表达式e是由f (x1, ¼, xn) 的形式组成,其中f是一个查询函数,如栈的empty和item函数。S1告诉我们e有一个值,但是这并不足够;在这个例子里,我们同时想知道,根据其它类型的值(在STACK的例子中,BOOLEAN和G类型的值)所得出的值是多少。在任何可能的情况下,如果定理足够强大能回答这个问题,那么,规格就是充分完整的。
Sufficient completeness is a useful practical guideline to check that no important property has been left out of a specification, answering the question raised above: when do I know I can stop looking for new properties to describe in the specification? It is good practice to apply this check, at least informally, to any ADT specification that you write — starting with your answers to the exercises of this chapter. Often, a formal proof of sufficient correctness is possible; the proof given below for the STACK specification defines a model which can be followed in many cases.
充分完整性是一个有用的应用方针,用来检查规格之外的不重要的属性,回答上面提出的问题:我何时知道我可以停止寻找新的在规格描述的属性?在您写的任何ADT规格中运用这种检查—可以从本章练习的答案开始,至少可以非正式地运用,这将是很好的练习。通常,一个充分正确性的正式证明是可行的;下面对STACK规格所给出的证明定义了可以在许多情况下被遵循的一个模型。
As you may have noted, S2 is optimistic in talking about “the” value of e: what if the axioms yield two or more? This would make the specification useless. To avoid such a situation we need a further condition, known from mathematical logic as consistency:
您已经知道了,S2在所涉及的e的值中是乐观的:定理产生两个或更多的值会发生什么?这将使规格无效。要避免这种情况,我们需要更进一步的条件,这就是从数学逻辑中得知的一致性:
Definition: ADT consistency An ADT specification is consistent if and only if, for any well-formed query expression e, the axioms make it possible to infer at most one value for e. 定义:ADT 一致性 对任何well-formed的查询表达式e,有且只有定理能推断出至多一个值,那么这个ADT的规格是一致的。 |
The two properties are complementary. For any query expression we want to be able to deduce exactly one value: at least one (sufficient completeness), but no more than one (consistency).
两个属性互为补充。对于任何查询表达式来说,我们希望能够推论出一个精确值:至少一个(充分完整性),但是只有一个(一致性)。
Proving sufficient completeness
充分完整性的验证
(This section and the rest of this chapter are supplementary material and its results are not needed in the rest of the book.)
(这段和本章的余下部分是些补充的内容,和本书并无相关。)
The sufficient completeness of an abstract data type specification is, in general, an undecidable problem. In other words, no general proof method exists which, given an arbitrary ADT specification, would tell us in finite time whether or not the specification is sufficiently complete. Consistency, too, is undecidable in the general case.
一般来说,一个抽象数据类型规格的充分完整性是一个不可判定的问题。换句话说,对于任意给出的一个ADT规格,在有限时间内并不存在一概通用的证明方法能告诉我们是否规格是充分完整的。同样的,在通常的情况下一致性也是不可判定的。
It is often possible, however, to prove the sufficient completeness and the consistency of a particular specification. To satisfy the curiosity of mathematically inclined readers, it is interesting to prove, as a conclusion to this chapter, that the specification of STACK is indeed sufficiently complete. The proof of consistency will be left as an exercise.
然而,证明一个特殊规格的充分完整性和一致性这经常倒是有可能的。要满足于有着数学兴趣的读者的求知欲,并作为本章的结束,证明STACK的规格是真正地充分完整的也挺有趣。一致性的证明将被留作练习。
Proving the sufficient completeness of the stack specification means devising a valid rule addressing problems S1 and S2 above; in other words the rule must enable us, for an arbitrary stack expression e:
证明栈规格的充分完整性意味着构想一个有效的规则来解决上面的问题 S1和 S2;换句话说,对一个任意的栈表达式e,此规则必须使我们能:
S1 • To determine whether e is correct.
S1·决定e是否正确。
S2 • If e is correct under S1 and its outermost function is item or empty (one of the two query functions), to express its value in terms of BOOLEAN and G values only, without any reference to values of type STACK [G] or to the functions of STACK’s specification.
S2·如果S1中e是正确的,并且它的最外层函数是item或者empty(两个查询函数之一),那么,只根据BOOLEAN和G的值,我们要能表达它的值,并不借助于任何STACK [G]类型的值或者STACK规格的函数。
It is convenient for a start to consider only well-formed expressions which do not involve any of the two query functions item and empty — so that we only have to deal with expressions built out of the functions new, put and remove. This means that only problem S1 (determining whether an expression is defined) is relevant at this stage. Query functions and S2 will be brought in later.
仅仅考虑不包含这两个查询函数item和empty的well-formed表达式—因此我们只研究没有new,put和remove的函数的表达式,那么能很方便地开始。这意味着此阶段只有S1(决定一个表达式是否被定义)相关。查询函数和S2放在后面考虑。
The following property, which we must prove, yields a rule addressing S1:
我们将证明的下列属性产生了一个处理S1的规则:
Weight Consistency rule A well-formed stack expression e, involving neither item nor empty, is correct if and only if its weight is non-negative, and any subexpression of e is (recursively) correct. 重量一致性规则 一个well-formed的栈表达式e,不包含item和empty,有且只有它的重量是非负的,并且任何e的子表达式是(递归)正确的,那么它就是正确的。 |
Here the “weight” of an expression represents the number of elements in the corresponding stack; it is also the difference between the number of nested occurrences of put and remove. Here is the precise definition of this notion:
这里,一个表达式的“重量”描述了在相应的栈中的元素数目;它也是put和remove嵌套发生数量之间的差异。这里是这个概念的精确定义:
Definition: weight The weight of a well-formed stack expression not involving item or empty is defined inductively as follows: W1 • The weight of the expression new is 0. W2 • The weight of the expression put (s, x) is ws + 1, where ws is the weight of s. W3 • The weight of the expression remove (s) is ws — 1, where ws is the weight of s. 定义:重量 一个没有item和empty的well-formed栈表达式的重量的定义归纳如下: W1·表达式new的重量为0。 W2·表达式put (s, x)的重量是ws + 1,这里ws是s的重量。 W3·表达式remove (s)的重量是ws- 1,这里ws是s的重量。 |
Informally, the Weight Consistency rule tells us that a stack expression is correct if and only if the expression and every one of its subexpressions, direct or indirect, has at least as many put operations (pushing an element on top) as it has remove operations (removing the top element); if we view the expression as representing a stack computation, this means that we never try to pop more than we have pushed. Remember that at this stage we are only concentrating on put and remove, ignoring the queries item and empty.
重量一致性规则简略地告诉我们栈表达式是正确的,有且只有当表达式和每一个它的子表达式,直接或者间接地,至少拥有和put运算(压入一个元素到栈顶)一样多的remove运算(去除栈顶元素);如果我们把表达式当作是一个栈的计算,这意味着我们不可以弹出多于压入的元素。切记我们在这个阶段我们只是集中考虑put和remove,而忽略item和empty的查询。
This intuitively seems right but of course we must prove that the Weight Consistency rule indeed holds. It will be convenient to introduce a companion rule and prove the two rules simultaneously:
直观地看上去没什么问题,当然我们必须证明重量一致性规则真正地有效。引入一个相关的规则,并证明这两个规则相似,这会比较简单。
Zero Weight rule Let e be a well-formed and correct stack expression not involving item or empty. Then empty (e) is true if and only if e has weight 0. 零重量规则 设e是一个well-formed和正确的栈表达式,其不包含item或empty。那么有且只有e重量为零时empty (e)成立。 |
The proof uses induction on the nesting level (maximum number of nested parentheses pairs) of the expression. Here again, for ease of reference, are the earlier axioms applying to function empty:
在表达式的嵌套层次上(嵌套圆括号对的最大数目)证明使用了归纳。为了便于引用,这里,再一次在empty函数上应用早期的定理:
STACK AXIOMS For any x: G, s: STACK [G] A3 • empty (new) A4 • not empty (put (s, x))
|
An expression e with nesting level 0 (no parentheses) may only be of the form new; so its weight is 0, and it is correct since new has no precondition. Axiom A3 indicates that empty (e) is true. This takes care of the base step for both the Weight Consistency rule and the Zero Weight rule.
一个嵌套层次为零(没有圆括号)的表达式e只可以是new的形式;因此它的重量为零,并且由于new没有前置条件,它也是正确的。定理A3表示empty (e)为真。这处理了重量一致性规则和零重量规则两者的基本步骤。
For the induction step, assume that the two rules are applicable to all expressions of nesting level n or smaller. We must prove that they apply to an arbitrary expression e of nesting level n + 1. Since for the time being we have excluded the query functions from our expressions, one of the following two forms must apply to e:
对于归纳步骤,假设两个规则应用于所有的嵌套层次为n或少于n的表达式。我们必须证明它们可以应用于一个任意的嵌套层次为n + 1的表达式e。由于暂时我们不考虑查询函数,下列两个形式中的任一个必须可以应用到e:
E1 • e = put (s, x)
E2 • e = remove (s)
where x is of type G, and s has nesting level n. Let ws be the weight of s.
这里的x为G类型,s有着n层嵌套。设ws为s的重量。
In case E1, since put is a total function, e is correct if and only if s is correct, that is to say (by the induction hypothesis) if and only if s and all its subexpressions have nonnegative weights. This is the same as saying that e and all its subexpressions have nonnegative weights, and so proves that the Weight Consistency rule holds in this case. In addition, e has the positive weight ws + 1, and (by axiom A4) is not empty, proving that the Zero Weight rule also holds.
在E1的情况中,由于put是一个完全函数,那么e是正确的有且只有s是正确的,这就是说(通过归纳假设),有且只有s和s所有的子表达式有着非负的重量。这等同于说e和其所有的子表达式有着非负的重量,因此证明了重量一致性规则此情况下有效。另外,e有着正重量ws + 1,并且(定理A4)非空,证明了零重量规则也有效。
In case E2, expression e is correct if and only if both of the following conditions hold:
EB1 • s and all its subexpressions are correct.
EB2 • not empty (s) (this is the precondition of remove).
在E2中,e表达式是正确的,有且只有下面两个条件有效:
EB1·s和s所有的子表达式正确。
EB2·非empty (s)(这是remove的前置条件)。
Because of the induction hypothesis, condition EB2 means that ws, the weight of s, is positive, or, equivalently, that ws – 1, the weight of e, is non-negative. So e satisfies the Weight Consistency rule. To prove that it also satisfies the Zero Weight rule, we must prove that e is empty if and only if its weight is zero. Since the weight of s is positive, s must contain at least one occurrence of put, which also appears in e. Consider the outermost occurrence of put in e; this occurrence is enclosed in a remove (since e has a remove at the outermost level). This means that a subexpression of e, or e itself, is of the form
remove (put (stack_expression, g_expression))
which axiom A2 indicates may be reduced to just stack_expression. Performing this replacement reduces the weight of e by 2; the resulting expression, which has the same value as e, satisfies the Zero Weight rule by the induction hypothesis. This proves the induction hypothesis for case E2.
因为归纳假设,条件EB2意味着s的重量ws是正数,或者等同于,e的重量ws – 1是非负数。因此,e满足重量一致性规则。要证明它也满足零重量规则,我们必须证明有且只有e的重量为零时,它是空的。由于s的重量是正数,s必须包含至少一个put事件,这个事件也在e中出现。考虑在e中put的最外层出现;它被包含在一个remove中(因为e在最外层有一个remove)。这意味着e的一个子表达式或者e的本身是
remove (put (stack_expression, g_expression))
这样的一种形式,这个形式通过定理A2推导为stack_expression。执行这个置换减少了e的一半重量;结果表达式具有e和一样的值,并通过归纳假设满足了零重量规则。这证明了E2情况下的归纳假设。
The proof has shown in passing that in any well-formed and correct expression which does not involve the query functions item and empty we may “remove every remove”, that is to say, obtain a canonical form that involves only put and new, by applying axiom A2 wherever possible. For example, the expression
put (remove (remove (put (put (remove (put (put (new, x1), x2)), x3), x4))), x5)
has the same value as the canonical form
put (put (new, x1), x5)
证明同时也显示了在任何well-formed并正确的表达式中,不包括查询函数item和empty,我们可以“去掉每一个remove”,也就是说,只要可能的话通过应用定理A2,我们能够获得一个只包括put和new的规范形式。例如,表达式
put (remove (remove (put (put (remove (put (put (new, x1), x2)), x3), x4))), x5)
和规范形式put (put (new, x1), x5)有着相同的结果。
For the record, let us give this mechanism a name and a definition:
让我们正式地给出这种机制的名字和定义:
Canonical Reduction rule Any well-formed and correct stack expression involving neither item nor empty has an equivalent “canonical” form that does not involve remove (that is to say, may only involve new and put). The canonical form is obtained by applying the stack axiom A2 as many times as possible. 规范简化规则 任何栈表达式,如果不包括item和empty,并具有well-formed和正确的形式,那么它有一个相等的不包含remove的“规范”形式(也就是说,只包含new和put)。这个规范形式可以通过尽可能多次运用栈定理A2获得。 |
This takes care of the proof of sufficient completeness but only for expressions that do not involve any of the query functions, and consequently for property S1 only (checking the correctness of an expression). To finish the proof, we must now take into account expressions that involve the query functions, and deal with problem S2 (finding the values of these query expressions). This means we need a rule to determine the correctness and value of any well-formed expression of the form f (s), where s is a well-formed expression and f is either empty or item.
这处理了充分完整性的证明,但是只是对于那些不包含任何查询函数的表达式,并且结果只针对的S1属性(检查一个表达式的正确性)。要完成证明,我们必须考虑包含查询函数和处理S2问题(找出这些查询表达式的值)的表达式。这意味着我们需要一个规则来决定f (s)形式的任何well-formed表达式的正确性和值,这里的s是一个well-formed表达式,f即要么empty或是item。
The rule and the proof of its validity use induction on the nesting level, as defined above. Let n be the nesting level of s. If n is 0, s can only be new since all the other functions require arguments, and so would have at least one parenthesis pair. Then the situation is clear for both of the query functions:
• empty (new) is correct and has value true (axiom A3).
• item (new) is incorrect since the precondition of item is not empty (s).
它的有效性规则和证明使用了嵌套层次上的归纳,就如上面定义的那样。设n是s的嵌套层次。如果n为零,由于所有的其它函数需要参数,并且因此至少有一对圆括号,那么s只能是new。由此,下列的这两个查询函数状态就很清楚了:
• empty (new) 正确,并有true 值(定理A3)。
• item (new) 不正确,由于item的前置条件是非empty (s)。
For the induction step, assume that s has a nesting depth n of one or more. If any subexpression u of s has item or empty as its outermost function, then u has a depth of at most n – 1, so the induction hypothesis indicates that we can determine whether u is correct and, if it is, obtain the value of u by applying the axioms. By performing all such possible subexpression replacements, we obtain for s a form which involves no stack function other than put, remove and new.
对于归纳步骤,假设s有一个或多个嵌套深度n。如果s的任一个子表达式u有item或empty作为最外层的函数,那么u有一个至多n – 1的深度,因此,归纳假设表示了我们能否决定u正确,如果正确的话,应用定理可以获得u的值。通过执行所有类似的子表达式置换,对于s我们得到一个包含put,remove和new栈函数的形式。
Next we may apply the idea of canonical form introduced above to get rid of all occurrences of remove, so that the resulting form of s may only involve put and new. The case in which s is just new has already been dealt with; it remains the case for which s is of the form put (s', x). Then for the two expressions under consideration:
• empty (s) is correct, and axiom A3 indicates that the value of this expression is false.
• item (s) is correct, since the precondition of item is precisely not empty (s); axiom A1 indicates that the value of this expression is x.
下一步,我们可以运用上面介绍的规范形式的概念来约去所有出现的remove,如此,s的结果形式只包含put和new。s中正好是new的情况已经被处理过了;剩下了s是put (s', x)的形式。那么考虑这两个表达式:
• empty (s) 正确,定理A3表示此表达式的值是false。
• item (s) 正确,由于item的前置条件正好是非empty (s);定理A1表示此表达式值为x。
This concludes the proof of sufficient completeness since we have now proved the validity of a set of rules — the Weight Consistency rule and the Canonical Reduction rule — enabling us to ascertain whether an arbitrary stack expression is correct and, for a correct query expression, to determine its value in terms of BOOLEAN and G values only.
这推导了充分完整性的证明,因为我们现在证明了一组规则的有效性—重量一致性规则和规范约分规则—使我们能够确定一个任意的栈是否正确,并且对一个正确的查询表达式,使我们能够只是根据BOOLEAN和G的值来确定它的值。
6.8 KEY CONCEPTS INTRODUCED IN THIS CHAPTER
6.8 摘要
• The theory of abstract data types (ADT) reconciles the need for precision and completeness in specifications with the desire to avoid overspecification.
·在要求避免冗余的规格中,抽象数据类型(ADT)的理论协调了对精度和完整性的需要。
• An abstract data type specification is a formal, mathematical description rather than a software text. It is applicative, that is to say change-free.
·一个抽象数据类型规格是一个正式,数学的描述,而并非一个软件文本。它是可适用的,也就是说无变化的。
• An abstract data type may be generic and is defined by functions, axioms and preconditions. The axioms and preconditions express the semantics of a type and are essential to a full, unambiguous description.
·一个抽象数据类型可以是泛化的并且被函数,定理和前置条件所定义。定理和前置条件表达了一个类型的语义,它们对于一个完整的,明确的描述很必要。
• To describe operations which are not always defined, partial functions provide a convenient mathematical model. Every partial function has a precondition, stating the condition under which it will yield a result for any particular candidate argument.
·为了描述并不总是有定义的运算,部分函数提供一个方便的数学模型。每一个部分函数有一个前置条件,规定了它将会为任何特别的待选参数产生一个结果的条件。
• An object-oriented system is a collection of classes. Every class is based on an abstract data type and provides a partial or full implementation for that ADT.
·一个面向对象的系统是一个类的集合。每个类以一个抽象数据类型为基础,而且对这个ADT提供了一个部分的或完全的实现。
• A class is effective if it is fully implemented, deferred otherwise.
·如果一个类被完全地实现,那么它是有效的,否则就是延期类。
• Classes should be designed to be as general and reusable as possible; the process of combining them into systems is often bottom-up.
·类应该尽可能地被设计成通用的和可复用的;把它们整合到系统中的过程通常是由下而上的。
• Abstract data types are implicit rather than explicit descriptions. This implicitness, which also means openness, carries over to the entire object-oriented method.
·抽象数据类型是隐含的而并非显示的描述。隐含性,也意谓着开放性,延续到整个的面向对象的方法中。
• No formal definition exists for the intuitively clear concept of an abstract data type specification being “complete”. A rigorously defined notion, sufficient completeness, usually provides the answer. Although no method is possible to ascertain the sufficient completeness of an arbitrary specification, proofs are often possible for specific cases; the proof given in this chapter for the stack specification may serve as a guide for other examples.
·对于一个正在“完成中的”抽象数据类型规格,并不存在一个正式的定义来描述出直观清晰的概念。一个严密定义的观念,充份完整性,通常提供了答案。虽然没有方法能用来确定一个任意规格的充份完整性, 但是有时对特定的范例是有可能被证明的;在本章中栈规格所给出的证明可以做为其它例子的指导。