面向对象软件构造(第2版)-第7章 静态结构: 类 (中)

 

7.4 A UNIFORM TYPE SYSTEM

7.4 一个统一的类型系统

 

An important aspect of the O-O approach as we will develop it is the simplicity and uniformity of the type system, deriving from a fundamental property:

OO方法的一个重要方面是类型系统的简单化和统一性,这个我们即将研究的方面是得自于一个基本的属性:

 

Object rule

Every object is an instance of some class.

对象规则

任何对象都是某个类的实例。

 

The Object rule will apply not just to composite, developer-defined objects (such as data structures with several fields) but also to basic objects such as integers, real numbers, boolean values and characters, which will all be considered to be instances of predefined library classes (INTEGER, REAL, DOUBLE, BOOLEAN, CHARACTER).

对象规则不但适用于复合的,开发者自定义的对象例如具有多个字段的数据结构,而且也适用于基本的对象,例如整数,实数,布尔值和字符,所有这些都被认为是预定义的库类(INTEGER, REAL, DOUBLE, BOOLEAN, CHARACTER)的实例

 

This zeal to make every possible value, however simple, an instance of some class may at first appear exaggerated or even extravagant. After all, mathematicians and engineers have used integers and reals successfully for a long time, without knowing they were manipulating class instances. But insisting on uniformity pays off for several reasons:

每个可能而简单的值变成类的实例,这种热忱起初看上去可能有些过于夸张甚至过分。 毕竟,数学家和工程师长期成功地使用着整数和实数,并不知道他们正操作着类的实例。但是,基于下列的几个原因坚持统一性会带来回报

 

• It is always desirable to have a simple and uniform framework rather than many special cases. Here the type system will be entirely based on the notion of class.

有一个简单和一致的框架而不是许多特殊的情况总是值得的。这里的类型系统将完全地基于类的概念。

 

• Describing basic types as ADTs and hence as classes is simple and natural. It is not hard, for example, to see how to define the class INTEGER with features covering arithmetic operations such as "+", comparison operations such as "<=", and the associated properties, derived from the corresponding mathematical axioms.

基本类型描述ADTs并进而为类,这是简单而自然的。例如,对于类INTEGER,其中的特性包括了从对应的数学定理中得来的术运算例如"+"较运算例如 <=和关联的属性,了解如何定义这个类并不困难。

 

• By defining the basic types as classes, we allow them to take part in all the O-O games, especially inheritance and genericity. If we did not treat the basic types as classes, we would have to introduce severe limitations and many special cases.

过把基本类型定义为类,我们允许它们参加所有的OO活动,特别是继承和泛型。如果不这样的话,我们将不得不引进严格的限制和许多特例。

 

As an example of inheritance, classes INTEGER, REAL and DOUBLE will be heirs to more general classes: NUMERIC, introducing the basic arithmetic operations such as "+", "" and "S", and COMPARABLE, introducing comparison operations such as "<". As an example of genericity, we can define a generic class MATRIX whose generic parameter represents the type of matrix elements, so that instances of MATRIX [INTEGER] represent matrices of integers, instances of MATRIX [REAL] represent matrices of reals and so on. As an example of combining genericity with inheritance, the preceding definitions also allow us to use the type MATRIX [NUMERIC], whose instances represent matrices containing objects of type INTEGER as well as objects of type REAL and objects of any new type T defined by a software developer so as to inherit from NUMERIC.

作为一个继承的例子,类INTEGERREALDOUBLE将继承自更通用的类:NUMERICCOMPARABLE,从NUMERIC得到基本的运算,如"+""""S",从COMPARABLE得到比较运算如"<"。作为一个泛型的例子,我们能够定义一个泛化类MATRIX,其泛化参数表示了矩阵元素的类型,因此,MATRIX [INTEGER]的实例表示了整数矩阵,MATRIX [REAL]的实例表示了实数矩阵,诸如此类。作为一个联合继承的泛型例子,上述的定义也允许我们使用类型MATRIX [NUMERIC],其实例表示了矩阵可以容纳类型INTEGER的对象,也可以容纳类型REAL的对象,并且可以容纳任何新类型T的对象,这个新类型是由软件开发者定义,并从NUMERIC继承而来。

 

With a good implementation, we do not need to fear any negative consequence from the decision to define all types from classes. Nothing prevents a compiler from having special knowledge about the basic classes; the code it generates for operations on values of types such as INTEGER and BOOLEAN can then be just as efficient as if these were built-in types in the language.

因为有着良好的实现,从做出决定到把所有的类型义成类,我们不需要担心任何消极的后果。编译器总是具有关于基本类的特别知识那么,为诸如INTEGERBOOLEAN类型的值运算所生成的代码能够和内建在语言中的类型一样的高效。

 

Reaching the goal of a fully consistent and uniform type system requires the combination of several important O-O techniques, to be seen only later: expanded classes, to ensure proper representation of simple values; infix and prefix operators, to enable usual arithmetic syntax (such as a < b or –a rather than the more cumbersome a.less_than (b) or a.negated); constrained genericity, needed to define classes which may be adapted to various types with specific operations, for example a class MATRIX that can represent matrices of integers as well as matrices of elements of other numeric types.

达到一个完全稳定和一致的类型系统的目的,这需要结合几个重要的O-O术,稍后可以了解到的扩展类(expanded classes),来保证简单值的正确的表示;缀和前缀运算符,来提供通常的算术语法(例如a < b–a而不是更加臃肿的a.less_than (b)a.negated);限制泛型,需要定义一个类,其可以适应具有特定运算的各种类型,例如类MATRIX,能够代表整数矩阵,也可以代表其它数字类型元素的矩阵。

 

7.5 A SIMPLE CLASS

7.5 一个简单的类

 

Let us now see what classes look like by studying a simple but typical example, which shows some of the fundamental properties applicable to almost all classes.

现在让我们通过研究一个简单却很典型的例子来看类是什么,这个例子显示了一些几乎对所有类都适用的基本属性。

 

The features

特性

 

The example is the notion of point, as it could appear in a two-dimensional graphics system.

例子是一个点的概念,它出现在一个两维的图象系统中。

 

 

To characterize type POINT as an abstract data type, we would need the four query functions x, y, r, q. (The names of the last two will be spelled out as rho and theta in software texts.) Function x gives the abscissa of a point (horizontal coordinate), y its ordinate (vertical coordinate), r its distance to the origin, q the angle to the horizontal axis.

要把类型POINT描述成一个抽象数据类型,我们需要四个查询函数xyrq。(在软件代码中最后两个的名字读成rhotheta。)函数x 给出了一个点的横坐标(水平坐标),y是纵坐标(垂直坐标),r是到原点的距离,q是到水平轴的角度。

 

The values of x and y for a point are called its cartesian coordinates, those of r and q its polar coordinates. Another useful query function is distance, which will yield the distance between two points.

一个点的xy值叫做笛卡儿坐标,rq叫做极坐标。其它有用的查询函数是distance,这是在两点之间所产生的距离。

 

Then the ADT specification would list commands such as translate (to move a point by a given horizontal and vertical displacement), rotate (to rotate the point by a certain angle, around the origin) and scale (to bring the point closer to or further from the origin by a certain factor).

接着,ADT规格列出了命令,如translate(根据一个给定的水平和垂直位置移动一个点),rotate(根据一定的角度围绕原点旋转点)和scale(根据某个因素把点移近原点或移出原点)。

 

It is not difficult to write the full ADT specification including these functions and some of the associated axioms. For example, two of the function signatures will be

x: POINT ® REAL

translate: POINT ´ REAL ´ REAL ® POINT

and one of the axioms will be (for any point p and any reals a, b):

x (translate (p1, a, b)) = x (p1) + a

expressing that translating a point by <a, b> increases its abscissa by a.

要编写包括这些函数和一些相关定理的完整的ADT规格,这并不困难。例如,其中两个函数的标记式为

x: POINT ® REAL

translate: POINT ´ REAL ´ REAL ® POINT

一个定理为(对于任意的点p和任意实数a, b):

x (translate (p1, a, b)) = x (p1) + a

其表达了一个点移动<a, b>横坐标增量为a

 

You may wish to complete this ADT specification by yourself. The rest of this discussion will assume that you have understood the ADT, whether or not you have written it formally in full, so that we can focus on its implementation — the class.

您也许希望单独完成这个ADT规格。无论您曾经是否正式地完整编写过ADT这次讨论的余下部分都将假设您已经了解了,因此我们可以集中在它的实现上——类。

 

Attributes and routines

属性和例程

 

Any abstract data type such as POINT is characterized by a set of functions, describing the operations applicable to instances of the ADT. In classes (ADT implementations), functions will yield features — the operations applicable to instances of the class.

任何类似于POINT的抽象数据类型都被一组函数所描绘,函数描述了应用到ADT实例上的运算。在类(ADT实现)中,函数将产生特性——应用到类实例上的运算。

 

We have seen that ADT functions are of three kinds: queries, commands and creators. For features, we need a complementary classification, based on how each feature is implemented: by space or by time.

们已经知道了ADT函数有类:查询,命令和创建符。对于特性,我们需要一个补充的分类,这基于每个特性的实现方式:根据间或是根据时间。

 

The example of point coordinates shows the difference clearly. Two common representations are available for points: cartesian and polar. If we choose Cartesian representation, each instance of the class will contain two fields representing the x and y of the corresponding point:

点座标的例子清楚地显示出了这两者的差别。点可以使用二种普通的表示法:直角坐标和极坐标。 如果我们选择直角坐标的表示法,那么类的每个实例都将包含两个字段,这两个字段代表了对应点的xy

 

 

If p1 is the point shown, getting its x or its y simply requires looking up the corresponding field in this structure. Getting r or q, however, requires a computation: for r we must compute  , and for q we must compute arctg (y/x) with non-zero x.

如果p1是这样的点,那么简单地从这个结构中查询相关的字段就可以得到xy。然而,要得到rq需要一些计算:对于r,我们需要计算 ,要得到q我们需要计算非零xarctg (y/x)

 

If we use polar representation, the situation is reversed: r and q are now accessible by simple field lookup, x and y require small computations (of r cos q and r sin q).

如果我们使用极坐标表示法,情况正好相反:简单地字段查询就可以得到rq,要得到xy需要一些小小的计算(r cos qr sin q)。

 

 

This example shows the need for two kinds of feature:

这个例子展示了对两种特性的需求:

 

• Some features will be represented by space, that is to say by associating a certain piece of information with every instance of the class. They will be called attributes. For points, x and y are attributes in cartesian representation; rho and theta are attributes in polar representation.

·一些特性根据空间来表示,也就是说,根据类的每一个实例所关联的特定信息。它们被称之为属性(attributes。拿点来说,xy就是直角坐标表示法的属性;rhotheta是极坐标表示法中的属性。

 

• Some features will be represented by time, that is to say by defining a certain computation (an algorithm) applicable to all instances of the class. They will be called routines. For points, rho and theta are routines in cartesian representation; x and y are routines in polar representation.

·一些特性根据时间来表示,也就是说,根据定义一个特定的计算(一个运算法则)应用到类的所有实例上。它们被称之为例程(routines。拿点来说,rhotheta直角坐标表示法的例程;xy是极坐标表示法中的例程。

 

A further distinction affects routines (the second of these categories). Some routines will return a result; they are called functions. Here x and y in polar representation, as well as rho and theta in cartesian representation, are functions since they return a result, of type REAL. Routines which do not return a result correspond to the commands of an ADT specification and are called procedures. For example the class POINT will include procedures translate, rotate and scale.

一个更深的差别影响着例程(第二个种类)。某些例程有着返回值;它们被称之为函数(functions。这里极坐标表示法中的xy,连同直角坐标表示法中的rhotheta,由于都有着类型REAL的返回值,所以都是函数。不带有返回值的例程符合ADT规格中的命令,并被称为过程(procedures。比如,类POINT包括了过程translaterotatescale

 

Be sure not to confuse the use of “function” to denote result-returning routines in classes with the earlier use of this word to denote the mathematical specifications of operations in abstract data types. This conflict is unfortunate, but follows from well-established usage of the word in both the mathematics and software fields.

请务必不要混淆,在类中函数用于表示有返回结果的例程,这个词的早期用法是在抽象数据类型中表示运算的数学规格。这种冲突是不幸的,但我们只能跟随在数学和软件领域中这个词的习惯用法。

 

The following tree helps visualize this classification of features:

下面的树形象化了这个特性的分类:

 

 

This is an external classification, in which the principal question is how a feature will look to its clients (its users).

这是一个外分类,在其中主要的问题是一个特性如何看待它的客户(它的用户)。

 

We can also take a more internal view, using as primary criterion how each feature is implemented in the class, and leading to a different classification:

我们也能够从内部观察,使用一个基本的标准:每个特性在类中如何被实现,这导致了一个不同的分类:

 

Uniform access

统一存取

 

One aspect of the preceding classifications may at first appear disturbing and has perhaps caught your attention. In many cases, we should be able to manipulate objects, for example a point p1, without having to worry about whether the internal representation of p1 is cartesian, polar or other. Is it appropriate, then, to distinguish explicitly between attributes and functions?

上述分类中的一个地方最初出现了些混乱。您也可能注意到了。许多情况下,我们应该能够操作象点p1这样的对象,并不需要考虑其内部表达形式直角坐标,极坐标还是其它。那么,把属性和函数之间明确地区别开来正确吗?

 

The answer depends on whose view we consider: the supplier’s view (as seen by the author of the class itself, here POINT) or the client’s view (as seen by the author of a class that uses POINT). For the supplier, the distinction between attributes and functions is meaningful and necessary, since in some cases you will want to implement a feature by storage and in others by computation, and the decision must be reflected somewhere. What would be wrong, however, would be to force the clients to be aware of the difference. If I am accessing p1, I want to be able to find out its x or its r without having to know how such queries are implemented.

复取决于我们采用谁的观点:应者的观点(POINT类作者自己本身所了解的或客户的观点(POINT类的使用者所了解的)。对于供应者来说,属性和函数之间的区别是有意义并且是必要的,因为在某些情况下您想要由存储来实现一个特性,在其它的情况下由计算来实现特性,并且这些决定必须在某些地方反映出来。然而,要强迫户(clients知道这些区别却是错误的。如果我正在访问p1我想要能得到它的xr,而不必知道这样的查询是如何被实现的。

 

The Uniform Access principle, introduced in the discussion of modularity, answers this concern. The principle states that a client should be able to access a property of an object using a single notation, whether the property is implemented by memory or by computation (space or time, attribute or routine). We shall follow this important principle in devising a notation for feature call below: the expression denoting the value of the x feature for p1 will always be

p1.x

whether its effect is to access a field of an object or to execute a routine.

统一存取原则回答了这个问题,这在模块性的讨论中已经介绍过了。则阐明了客户应该能使用一个单一的符号来访问对象的属性,不管属性由存储实现或由计算实现间或时间,属性或例程对下面的特性调用们应该遵循这项重要原则来构想一个符号:表达式表示p1x特性的值总是为

p1.x

不管它的作用是访问对象的一个字段还是执行一个例程。

 

As you will have noted, the uncertainty can only exist for queries without arguments, which may be implemented as functions or as attributes. A command must be a procedure; a query with arguments must be a function, since attributes cannot have arguments.

您要注意了,不确定性只可能存在于没有参数的查询,这个查询也许作为函数或是属性来实现。一个命令必须是一个过程;由于属性不可能有参数,所以一个具有参数的询问肯定是函数。

 

The Uniform Access principle is essential to guarantee the autonomy of the components of a system. It preserves the class designer’s freedom to experiment with various implementation techniques without disturbing the clients.

统一存取原则对保证系统的组件自治是非常必要的。它保护类设计者可以自由试验各种各样的实现技术,而不会干扰客户。

 

Pascal, C and Ada violate the principle by providing a different notation for a function call and for an attribute access. For such non-object-oriented languages this is understandable (although we have seen that Algol W, a 1966 predecessor to Pascal, satisfied uniform access). More recent languages such as C++ and Java also do not enforce the principle. Departing from Uniform Access may cause any internal representation change (such as the switch from polar to cartesian or some other representation) to cause upheaval in many client classes. This is a primary source of instability in software development.

PascalCAda过对函数调用和对属性存取提供了两个不同的符号而违犯了这项原则。对这样的非面向对象语言,这是可理解的尽管我们看到了Algol W,一个Pascal 1966年的早期版本,满足了统一存取原则)。最近的语言例如C++Java也没有强制执行此项原则。违反统一存取原则可能导致所有的内部表达形式上的变动例如坐标转换到直角坐标或是某些其它的表示法进而引发在许多客户类上的剧变。在软件开发中这是不稳定性的主要源头。

 

The Uniform Access principle also yields a requirement on documentation techniques. If we are to apply the principle consistently, we must ensure that it is not possible to determine, from the official documentation on a class, whether a query without arguments is a function or an attribute. This will be one of the properties of the standard mechanism for documenting a class, known as the short form.

统一存取原则在文档技术上也有着一个要求。如果我们自始至终地运用这项原则,那么我们必须保证从类的正式文档开始就不可能要去确定没有参数的查询是否是函数还是属性。这是制定类文档的标准机制的属性之一。

 

The class

 

Here is a version of the class text for POINT. (Any occurrence of consecutive dashes -- introduces a comment, which extends to the end of the line; comments are explanations intended for the reader of the class text and do not affect the semantics of the class.)

这里有POINT类代码的一个版本。(任何连续的破折号--都是注释,一直延续到一行的末尾;注释是解释给代码文本的读者看的,并不影响类的语义。)

indexing

description: "Two-dimensional points"

class POINT feature

x, y: REAL

-- Abscissa and ordinate

rho: REAL is

-- Distance to origin (0, 0)

do

Result := sqrt (x ^ 2 + y ^ 2)

end

theta: REAL is

-- Angle to horizontal axis

do

¼Left to reader (exercise E7.3, page 216) º

end

distance (p: POINT): REAL is

-- Distance to p

do

Result := sqrt ((x – p lx) ^ 2 + (y – p l y) ^ 2)

end

translate (a, b: REAL) is

-- Move by a horizontally, b vertically.

do

x := x + a

y := y + b

end

scale (factor: REAL) is

-- Scale by factor.

do

x := factor S x

y := factor S y

end

rotate (p: POINT; angle: REAL) is

-- Rotate around p by angle.

do

¼Left to reader (exercise E7.3, page 216) ¼

end

end

 

The next few sections explain in detail the non-obvious aspects of this class text.

随后的几个小节将详细解释这段类代码中的不易理解的部分。

 

The class mainly consists of a clause listing the various features and introduced by the keyword feature. There is also an indexing clause giving general description information, useful to readers of the class but with no effect on its execution semantics. Later on we will learn three optional clauses: inherit for inheritance, creation for nondefault creation and invariant for introducing class invariants; we will also see how to include two or more feature clauses in one class.

类主要由一个子句(clause)组成,子句列出了各种特性,并由关键字feature引出。这里同时有一个indexing子句,给出了一般性的description信息,这对类的读者很有用却不影响执行语义。稍后,我们将学习三种可选子句:inherit继承,creation非省缺创建符,和invariant类不变量;我们也将会看到如何在一个类中包含两个或更多feature子句。

 

7.6 BASIC CONVENTIONS

7.6 基本约定

 

Class POINT shows a number of techniques which will be used throughout later examples. Let us first look at the basic conventions.

POINT展示了许多将在后面的例子中被使用的技术。让我们先看一下基本的约定。

 

Recognizing feature kinds

了解特性的种类

 

Features x and y are just declared as being of type REAL, with no associated algorithm; so they can only be attributes. All other features have a clause of the form

is

do

¼ Instructions¼

end

which defines an algorithm; this indicates the feature is a routine. Routines rho, theta and distance are declared as returning a result, of type REAL in all cases, as indicated by declarations of the form

rho: REAL is ¼

特性xy只是声明为类型REAL的要素,并没有关联的算法;因此,它们只能是属性。所有其它的特性都有一个如下形式的子句

is

do

¼ Instructions¼

end

其定义了一个算法;这表明了此特性是一个例程。例程rhothetadistance声明返回一个结果,所有的结果都是类型REAL,声明为如下形式

rho: REAL is ¼

 

This defines them as functions. The other two, translate and scale, do not return a result (since they do not have a result declaration of the form :T for some type T), and so they are procedures.

这种形式把它们定义成函数。其它的两个translatescale并没有返回一个结果(这是因为它们没有声明为:T形式的结果,T是某种类型),因此,它们是过程。

 

Since x and y are attributes, while rho and theta are functions, the representation chosen in this particular class for points is cartesian.

由于xy是属性,而rhotheta是函数,所以在这里类所用的表示法是直角坐标表示法。

 

Routine bodies and header comments

例程主体和头注释

 

The body of a routine (the do clause) is a sequence of instructions. You can use semicolons, in the Algol-Pascal tradition, to separate successive instructions and declarations, but the semicolons are optional. We will omit them for simplicity between elements on separate lines, but will always include them to delimit instructions or declarations appearing on the same line.

例程(do子句)的主体是一段连续的指令。在Algol-Pascal的语法中,您能够使用分号来分割连续的指令和声明,但分号是可选的。为了简单,在不同的行中我们可以省略分号,但是如果在同一行中,我们始终要使用这个符号来分割指令或声明。

 

All the instructions in the routines of class POINT are assignments; for assignment, the notation uses the := symbol (again borrowed from the Algol-Pascal conventions). This symbol should of course not be confused with the equality symbol =, used, as in mathematics, as a comparison operator.

POINT例程中的所有指令都是赋值;对于赋值,符号使用了:=(再一次从Algol-Pascal语法中借用)。当然,这个符号不要和等于号=混淆,等于号在数学或比较运算符中使用。

 

Another convention of the notation is the use of header comments. As already noted, comments are introduced by two consecutive dashes --. They may appear at any place in a class text where the class author feels that readers will benefit from an explanation. A special role is played by the header comment which, as a general style rule, should appear at the beginning of every routine, after the keyword is, indented as shown by the examples in class POINT. Such a header comment should tersely express the purpose of the routine.

另一个符号的约定是头注释的使用。我们已经注意到了,注释采用了两个连续的短横--。它们可以在类代码中的任何地方出现,在那里类的作者认为读者可以从解释中受益。作为一个通用风格的规则,头注释header comment)有着一个特别的作用,它将出现在每个例程的开头,在关键字is之后,在类POINT的例子中显示的缩进部分。这样的一个头注释简要地表达了例程的目的。

 

Attributes should also have a header comment immediately following their declaration, aligned with routine’s header comments, as illustrated here with x and y.

属性也会有一个头注释紧接着它们的声明之后,和例程的头注释对齐,就象在xy里所展示的那样。

 

The indexing clause

indexing子句

 

At the beginning of the class comes a clause starting with the keyword indexing. It contains a single entry, labeled description. The indexing clause has no effect on software execution, but serves to associate information with the class. In its general form it contains zero or more entries of the form

index_word: index_value, index_value, ¼

where the index_word is an arbitrary identifier, and each index_value is an arbitrary language element (identifier, integer, string¼).

在类的开头有一个由关键字indexing开始的子句。它包含了一个标注为description的单行。在软件执行时indexing子句并不起作用,而是起着类信息的作用。在它的一般形式中它包含零个或多个index_word: index_value, index_value, ¼形式的项,其中index_word是一个任意的标识符,每一个index_value是一个任意的语言元素(标识符,整数,字符串

 

The benefit is twofold:

它的好处是两方面的:

 

• Readers of the class get a summary of its properties, without having to see the details.

·类的读者不用看细节就可以得到属性的概要。

 

• In a software development environment supporting reuse, query tools (often known as browsers) can use the indexing information to help potential users find out about available classes; the tools can let the users enter various search words and match them with the index words and values.

·在一个支持复用的软件开发环境中,查询工具(如browsers)能够使用indexing信息去帮助潜在的用户找到可用的类;工具能够让用户输入各种各样的查找关键字,并与索引字和索引值匹陪。

 

The example has a single indexing entry, with description as index word and, as index value, a string describing the purpose of the class. All classes in this book, save for short examples, will include a description entry. You are strongly encouraged to follow this example and begin every class text with an indexing clause providing a concise overview of the class, in the same way that every routine begins with a header comment.

例子里有一个单独的索引项,以description作为索引字,并且用一个描述类的目的的字符串作为索引值。本书中除了一些精简的例子之外所有的类都将包括description项。我强烈地建议您照这个例子做,在每个类代码中以indexing子句开始,来提供一个简明的类描述,同样地每一个例程也以一个头注释开始。

 

Both indexing clauses and header comments are faithful applications of the Self-Documentation principle: as much as possible of a module’s documentation should appear in the text of the module itself.

索引子句和头注释都是自包含文档则的正确应用:一个模块的文档尽可能应该出现在模块代码的自身当中。

 

Denoting a function’s result

指明函数的返回值

 

We need another convention to understand the texts of the functions in class POINT: rho, theta and distance.

我们需要另一个约定来理解类POINT中函数的代码:rhothetadistance

 

Any language that supports functions (value-returning routines) must offer a notation allowing the body of a function to set the value which will be returned by any particular call. The convention used here is simple: it relies on a predefined entity name, Result, denoting the value that the call will return. For example, the body of rho contains an assignment to Result:

Result := sqrt (x ^ 2 + y ^ 2)

任何支持函数(有着返回值的例程)的语言必须提供一个符号来允许一个函数体设置返回值,这个返回值将返回给任何正规的调用。这里的约定很简单:它依靠预先设定的实体名字Result,表示将返回的值。例如,rho的函数体包含了一个Result的赋值:

Result := sqrt (x ^ 2 + y ^ 2)

 

Result is a reserved word, and may only appear in functions. In a function declared as having a result of type T, Result is treated in the same way as other entities, and may be assigned values through assignment instructions such as the above.

Result是一个保留关键字,并且只可能出现在函数中。在一个函数中Result被声明为一个类型T的返回结果,和其它的实体一样对待,并且可以象上面一样通过赋值指令来赋值。

 

Any call to the function will return, as its result, the final value assigned to Result during the call’s execution. That value always exists since language rules (to be seen in detail later) require every execution of the routine, when it starts, to initialize Result to a preset value. For a REAL the initialization value is zero; so a function of the form

non_negative_value (x: REAL): REAL is

-- The value of x if positive; zero otherwise.

do

if x > 0.0 then

Result := x

end

end

will always return a well-defined value (as described by the header comment) even though the conditional instruction has no else part.

任何调用函数都将返回最终值作为其结果,最终值是在调用的执行中赋给Result的。由于语言的规则要求(后面会了解到细节)每一个例程在开始执行的时候用一个预定义的值来初始化Result,所以这个值总是存在。对于一个REAL,初始化值是零;因此下列的函数

non_negative_value (x: REAL): REAL is

-- The value of x if positive; zero otherwise.

do

if x > 0.0 then

Result := x

end

end

将总是返回一个定义明确的值(与头注释里描述一样),即使条件指令没有else部分。

 

The discussion section of this chapter examines the rationale behind the Result convention and compares it with other techniques such as return instructions. Although this convention addresses an issue that arises in all design and programming languages, it blends particularly well with the rest of the object-oriented approach.

本章的讨论部分会分析约定Result背后的理论基础,并与其它的技术相比较,例如返回指令。虽然这个约定致力于在所有的设计和编程语言中出现问题,但是它特别地和其它的面向对象方法工作得很好。

 

Style rules

式样规则

 

The class texts in this book follow precise style conventions regarding indentation, fonts (for typeset output), choice of names for features and classes, use of lower and upper case.

本书中的类代码采用了简明的式样约定,如缩进,字体(排版输出),特性和类命名的选择,大小字母的使用等等。

 

The discussion will point out these conventions, under the heading “style rules”, as we go along. They should not be dismissed as mere cosmetics: quality software requires consistency and attention to all details, of form as well as of content. The reusability goal makes these observations even more important, since it implies that software texts will have a long life, during which many people will need to understand and improve them.

在标题式样规则”引导讨论中将会指出这些约定。应该仅仅因为装饰作用就拒绝它们:高质量的软件要求一致性并注意所有细节,对待形式就和对待内容一样。用性的目标使这些要求更加重要,因为它暗示软件代码将有着较长的寿命,在这个期间许多人将需要了解并改进它们。

 

You should apply the style rules right from the time you start writing a class. For example you should never write a routine without immediately including its header comment. This does not take long, and is not wasted time; in fact it is time saved for all future work on the class, whether by you or by others, whether after half an hour or after half a decade. Using regular indentation, proper spelling for comments and identifiers, adequate lexical conventions — a space before each opening parenthesis but not after, and so on — does not make your task any longer than ignoring these rules, but compounded over months of work and heaps of software produces a tremendous difference. Attention to such details, although not sufficient, is a necessary condition for quality software (and quality, the general theme of this book, is what defines software engineering).

应该从一开始就运用样式规则。例如您从不应该编写没有包括头注释的例程。这并不花费很多时间,也并不浪费时间实际上对于往后的工作这是节省时间,不论是对您或是对别人,不论是在半小时以后或是在五年之后。使用适当的缩进、正确的注释和标识符的拼写,充分的词汇约定——在每个开头括号之前而不是在之后的一个格,等等——并不会使您的工作比忽略这些规则花费更长的时间,但是在混合了几个月的工作和大量的代码之后会使产生一个巨大的差异。虽然并不充分,但是注重这样的细节却是一个软件品质的必要条件这就是软件工程所定义的品质,也是本书的主题

 

The elementary style rules are clear from the preceding class example. Since our immediate goal is to explore the basic mechanisms of object technology, their precise description will only appear in a later chapter.

在先前的类例子中基本的样式规则很清楚。由于我们的当前目标是探索对象技术的基本机制,所以它们的精确描述将会出现于后面的章节中。

 

Inheriting general-purpose facilities

从多用途的工具类中继承

 

Another aspect of class POINT which requires clarification is the presence of calls to the sqrt function (in rho and distance). This function should clearly return the square root of a real number, but where does it come from?

POINT中需要澄清的另一个方面是调用中出现的sqrt函数(在rhodistance里面)。这个函数很明显地应该返回一个实数的平方根,但是它是哪里来的呢?

 

Since it does not seem appropriate to encumber a general-purpose language with specialized arithmetic operations, the best technique is to define such operations as features of some specialized class — say ARITHMETIC — and then simply require any class that needs these facilities to inherit from the specialized class. As will be seen in detail in a later chapter, it suffices then to write POINT as

class POINT inherit

ARITHMETIC

feature

¼ The rest as before ¼

end

由于系统并没有排斥一个带有特殊数学运算的多用途语言,所以最好的技术就是定义这样的运算作为某个特殊类的特性,这个类假定为ARITHMETIC ,并且简单地要求任何需要这些运算的类从这个特殊类中继承。把POINT定义为

class POINT inherit

ARITHMETIC

feature

¼ The rest as before ¼

end

在后面的章节中我们将会详细地了解。

 

This technique of inheriting general-purpose facilities is somewhat controversial; one can argue that O-O principles suggest making a function such as sqrt a feature of the class representing the object to which it applies, for example REAL. But there are many operations on real numbers, not all of which can be included in the class. Square root may be sufficiently fundamental to justify making it a feature of class REAL; then we would write a.sqrt rather than sqrt (x). We will return, in the discussion of design principles, to the question of whether “facilities” classes such as ARITHMETIC are desirable.

这个从多用途工具类中继承的技术有着一些争议;一个争论是OO原则建议了定义一个象sqrt这样的函数,作为其应用的对象的类的一个特性,如类REAL。但是在实数中有这许多的运算,不是所有的都能包含在类中。平方根也许足够基本来成为类REAL的一个特性;接着我们将使用a.sqrt,而不是sqrt (x)。在设计原则的讨论中,我们将回到象ARITHMETIC这样的“工具”类是否值得的问题上来。

 

7.7 THE OBJECT-ORIENTED STYLE OF COMPUTATION

7.7 计算的面向对象式样

 

Let us now move to the fundamental properties of class POINT by trying to understand a typical routine body and its instructions, then studying how the class and its features may be used by other classes — clients.

现在让我们转到类POINT的基本属性上来,我们要试着理解一个典型的例程体和它的指令,接着研究类和其特性如何可以被其它类——客户——所使用。

 

The current instance

当前实例

 

Here again is the text of one of our example routines, procedure translate:

这里再次使用的是我们例子中的一个例程的代码,过程translate

translate (a, b: REAL) is

-- Move by a horizontally, b vertically

do

x := x + a

y := y + b

end

 

At first sight this text appears clear enough: to translate a point by a horizontally, b vertically, we add a to its x and b to its y. But if you look at it more carefully, it may not be so obvious anymore! Nowhere in the text have we stated what point we were talking about. To whose x and whose y are we adding a and b? In the answer to this question will lie one of the most distinctive aspects of the object-oriented development style. Before we are ready to discover that answer we must understand a few intermediate topics.

刚看到这段代码会觉得很清楚:通过水平增量a和垂直增量b移动一个点,我们把点的x加上a,点的y加上b。但是如果您再仔细看看,也许并不那么显而易见!代码中并没有任何地方声明了我们正在谈论的点。我们要把ab加到哪一个点的xy上?此问题的答案将展现一个面向对象开发式样中最与众不同的方面。在我们获得答案之前,我们必须要理解一些中间的主题。

 

A class text describes the properties and behavior of objects of a certain type, points in this example. It does so by describing the properties and behavior of a typical instance of that type — what we could call the “point in the street” in the way newspapers report the opinion of the “man or woman in the street”. We will settle for a more formal name: the current instance of the class.

类代码描述了一个特定类型的对象的属性和行为,在本例中这个类型是点。它通过描述那个类型的一个典型实例的属性和行为来做到这一点——报纸把我们称之为在街上的”的观点叫做“在街上的人们们将接受一个更加正式的名字:类的当前实例(current instance

 

Once in a while, we may need to refer to the current instance explicitly. There served word

Current

will serve that purpose. In a class text, Current denotes the current instance of the enclosing class. As an example of when Current is needed, assume we rewrite distance so that it checks first whether the argument p is the same point as the current instance, in which case the result is 0 with no need for further computation. Then distance will appear as

distance (p: POINT): REAL is

-- Distance to p

do

if p /= Current then

Result := sqrt ((x — p.x) ^ 2 + (y — p.y) ^ 2)

end

end

(/= is the inequality operator. Because of the initialization rule mentioned above, the conditional instruction does not need an else part: if p = Current the result is zero.)

偶尔,我们也许需要明确地引用当前的实例。保留关键字Current就是用于这个目的。在类的代码中,Current表示外围类(enclosing class)的当前实例。举一个什么时候需要Current的例子,假定我们重写distance,想让它先检查参数p和当前实例是否是同样的点,如果结果是零就不需要进一步的计算了。那么distance显示如下

distance (p: POINT): REAL is

-- Distance to p

do

if p /= Current then

Result := sqrt ((x — p.x) ^ 2 + (y — p.y) ^ 2)

end

end

/=是不等运算符。因为上面所提及的初始化规则,条件指令并不需要else部分:如果p = Current,结果为零。)

[]enclosing class有的书上叫做封闭类,我采用了侯捷的译法。

 

In most circumstances, however, the current instance is implicit and we will not need to refer to Current by its name. For example, references to x in the body of translate and the other routines simply mean, if not further qualified: “the x of the current instance”.

然而,在绝大部分的情况中,当前的实例是隐含的,我们不需要通过它的名字来引用Current。比如,在translate的函数体和其它例程中的x引用简单地意味着,如果没有进一步的限定,就是“当前实例的x”。

 

This only pushes back the mystery, of course: “who” really is Current? The answer will come with the study of routine calls below. As long as we only look at the routine text, it will suffice to know that all operations are relative, by default, to an implicitly defined object, the current instance.

只剩下一个秘密了,就是:Current到底是“谁”?答案将随着下面的例程调用的学习而浮现出来。在我们只考虑例程代码的时候,就足以知道在缺省的情况下,所有的运算都关联到一个隐含的定义对象:当前的实例。

 

Clients and suppliers

客户和供应者

 

Ignoring for a few moments the enigma of Current’s identity, we know how to define simple classes. We must now study how to use their definitions. Such uses will be in other classes — since in a pure object-oriented approach every software element is part of some class text.

暂时把Current特征之迷放在一边,我们知道了如何定义简单的类。现在我们应当研究如何使用它们的定义。这样的用法将会出现在其它的类中——这是由于在一个纯粹的面向对象的方法中每一个软件元素都是某个类代码的一部分。

 

There are only two ways to use a class such as POINT. One is to inherit from it; this is studied in detail in later chapters. The other one is to become a client of POINT.

这里只有两种方法来使用诸如POINT这样的类。一种是继承;细节将在随后的章节中研究。另一种是成为POINT客户client)。

 

The simplest and most common way to become a client of a class is to declare an entity of the corresponding type:

要成为类的一个客户,最简单和最常用的方法是声明一个对应类型的实体:

 

Definition: client, supplier

Let S be a class. A class C which contains a declaration of the form a: S is said to be a client of S. S is then said to be a supplier of C.

定义:客户,供应者

S为一个类。包含一个a: S形式的声明的类C被称之为S的客户。那么,S被称之为C的供应者。

 

In this definition, a may be an attribute or function of C, or a local entity or argument of a routine of C.

在这个定义中,a也许是C的一个函数或特性,或者是C的例程的一个局部实体或参数。

 

For example, the declarations of x, y, rho, theta and distance above make class POINT a client of REAL. Other classes may in turn become clients of POINT. Here is an example:

举个例子,上面的xyrhothetadistance声明使得类POINT成为REAL的一个客户。其它的类依此成为POINT的客户。举例如下:

 

class GRAPHICS feature

p1: POINT

¼

some_routine is

-- Perform some actions with p1.

do

¼ Create an instance of POINT and attach it to p1 ¼

p1.translate (4.0, –1.5) --SS

¼

end

¼

End

 

Before the instruction marked --SS gets executed, the attribute p1 will have a value denoting a certain instance of class POINT. Assume that this instance represents the origin, of coordinates x = 0, y = 0:

在带有--SS注释的指令执行之前,属性p1将有一个值表示类POINT的一个特定实例。假设这个实例表示了坐标为x = 0y = 0的原点:

 

 

Entity p1 is said to be attached to this object. We do not worry at this point about how the object has been created (by the unexplained line that reads “¼Create object¼”) and initialized; such topics will be discussed as part of the object model in the next chapter. Let us just assume that the object exists and that p1 is attached to it.

实体p1被称之为附属(attached在这个对象上。此时我们不用担心对象如何被创建(¼Create¼注释行)和初始化;这个主题会在下一章中的对象模型部分中讨论。让我们只是假定对象存在并且p1附属其上。

 

Feature call

特性调用

 

The starred instruction,

p1.translate (4.0, –1.5)

deserves careful examination since it is our first complete example of what may be called the basic mechanism of object-oriented computation: feature call. In the execution of an object-oriented software system, all computation is achieved by calling certain features on certain objects.

由于这是我们用第一个完整的例子来研究什么是面向对象计算的基本机制(basic mechanism of object-oriented computation):特性调用,所以主要的指令p1.translate (4.0, –1.5)应该仔细地分析。在一个面向对象软件系统的执行中,所有的计算都是在一定的对象上调用某些特性来完成的。

 

This particular feature call means: apply to p1 the feature translate of class POINT, with arguments 4.0 and –1.5, corresponding to a and b in the declaration of translate as it appears in the class. More generally, a feature call appears in its basic form as one of

x.f

x.f (u, v, ¼)

这个特别的特性调用意思为:将类POINT的特性translate应用到p1上,并带有参数4.0–1.5,参数对应于类中定义的translate声明中的ab。通常,一个特性调用所表现的基本形式是下面的一种:

x.f

x.f (u, v, ¼)

 

In such a call, x, called the target of the call, is an entity or expression (which at run time will be attached to a certain object). As any other entity or expression, x has a certain type, given by a class C; then f must be one of the features of C. More precisely, in the first form, f must be an attribute or a routine without arguments; in the second form, f must be a routine with arguments, and u, v, ¼, called the actual arguments for the call, must be expressions matching in type and number the formal arguments declared for f in C.

在这种调用中,x称做调用的目标(target,它是一个实体或是表达式(其在运行的时候将会附属在某个对象上)。与任何其它的实体或表达式一样,类C给予了x一个固定的类型;于是f必须是C的特性之一。更精确地说,在第一个形式中,f必须是一个属性或是一个不带参数的特性;在第二个形式中,f必须是一个带有参数的例程,其中uv叫做实际参数(实参actual arguments),它们必须要匹配C中的f所声明的形式参数类型和个数。

 

In addition, f must be available (exported) to the client containing this call. This is the default; a later section will show how to restrict export rights. For the moment, all features are available to all clients.

另外,f必须对有此调用的客户有效(输出)。这是默认的;后面的部分将展示如何限制输出的权限。此刻,所有的特性对所有的客户都有效。

 

The effect of the above call when executed at run time is defined as follows:

在运行的时候,上述调用执行的实现定义如下:

 

Effect of calling a feature f on a target x

Apply feature f to the object attached to x, after having initialized each formal argument of f (if any) to the value of the corresponding actual argument.

在目标x上调用特性f的实现

在把每一个f的形参(如果有的话)初始化成对应的实参值之后,调用赋属在x上的对象的特性f

 

 

The Single Target principle

单一目标原则

 

What is so special about feature call? After all, every software developer knows how to write a procedure translate which moves a point by a certain displacement, and is called in the traditional form (available, with minor variants, in all programming languages):

translate (p1, 4.0, –1.5)

什么是特性调用的特殊性?毕竟,每一个软件开发者都知道如何编写一个经过一个确定的偏移来移动一个点的过程,这个过程的传统调用形式(除了很小的一些变化之外,在所有的编程语言中都适用):

translate (p1, 4.0, –1.5)

 

Unlike the object-oriented style of feature call, however, this call treats all arguments on an equal basis. The O-O form has no such symmetry: we choose a certain object (here the point p1) as target, relegating the other arguments, here the real numbers 4.0 and –1.5, to the role of supporting cast. This way of making every call relative to a single target object is a central part of the object-oriented style of computing:

然而,不同于特性调用的面向对象方式,这个调用在一个平等的基础上对待所有的参数。OO形式没有这样的对称性:我们选择一个确定的对象(这儿是点p1)为目标,把其它的参数,这里是实数4.0–1.5,归类到支持计算的角色。这种让每个调用关系到一个单一目标对象的方式是面向对象计算式样的核心部分:

 

Single Target principle

Every operation of object-oriented computation is relative to a certain object, the current instance at the time of the operation’s execution.

单一目标原则
面向对象计算的每一个运算都关联到一个特定的对象上,这是运算执行的时候当前的对象实例。

 

To novices, this is often the most disconcerting aspect of the method. In object-oriented software construction, we never really ask: “Apply this operation to these objects”. Instead we say: “Apply this operation to this object here.” And perhaps (in the second form): “Oh, by the way, I almost forgot, you will need those values there as arguments”.

对于新手来说,这常常是最令人糊涂的部分。在面向对象软件架构中,我们从不说:“把这个运算应用到这些对象上”。而是说:“应用此运算到这个this)对象上。”同时,也许说(第二种形式):“哦,顺便说一下,我几乎忘了您需要那些值作为参数”。

 

What we have seen so far does not really suffice to justify this convention; in fact its negative consequences will, for a while, overshadow its advantages. An example of counter-intuitive effect appears with the function distance of class POINT, declared above as distance (p: POINT): REAL, implying that a typical call will be written

p1.distance (p2)

which runs against the perception of distance as a symmetric operation on two arguments. Only with the introduction of inheritance will the Single Target principle be fully vindicated.

到目前为止,我们所看到的并没有真正地体现了这个约定的好处;事实上,它的消极作用暂时超过了它的益处。一个违背直觉的例子出现在了类POINT的函数distance中,上述的函数声明为distance (p: POINT): REAL,暗示着一个典型的调用将写成p1.distance (p2),这违反了把distance看作是一个在两个参数上的对称运算的理解。只有随着继承的引进才能证明单一目标原则的正确性。

 

The module-type identification

模块类型的验证

 

The Single Target principle is a direct consequence of the module-type merge, presented earlier as the starting point of object-oriented decomposition: if every module is a type, then every operation in the module is relative to a certain instance of that type (the current instance). Up to now, however, the details of that merge remained a little mysterious. A class, it was said above, is both a module and a type; but how can we reconcile the syntactic notion of module (a grouping of related facilities, forming a part of a software system) with the semantic notion of type (the static description of certain possible runtime objects)? The example of POINT makes the answer clear:

单一目标原则是一个模块融合的直接结果,早先是作为面向对象分解性的起点而被介绍的:如果每个模块都是一个类型,那么每个在模块中的运算都关联到那个类型的某个实例(当前的实例)上。然而,到目前为止,那个融合的细节仍然保留着一丝的神秘。如上所说,一个类既是一个模块也是一个类型;但是我们如何能够让模块的语法概念(一组相关的工具,一个软件系统的组成部分)和类型的语义概念(某个可能的运行时对象的静态描述)一致呢?POINT的例子清楚地得到了答案:

 

How the module-type merge works

The facilities provided by class POINT, viewed as a module, are precisely the operations available on instances of class POINT, viewed as a type.

模块-类型的融合如何工作

POINT所提供的工具正好是类POINT的实例上的有效运算,把工具看做一个模块,把实例看做一个类型。

 

This identification between the operations on instances of a type and the services provided by a module lies at the heart of the structuring discipline enforced by the object-oriented method.

在一个类型实例上的运算和一个模块所提供的服务之间的辨识,存在于面向对象方式所推行的结构科学的核心中。

 

The role of Current

Current的作用

 

With the help of the same example, we are now also in a position to clear the remaining mystery: what does the current instance really represent?

在同一个例子的帮助下,我们现在也能够明白剩下的神秘了:当前的实例真正代表了什么?

 

The form of calls indicates why the text of a routine (such as translate in POINT) does not need to specify “who” Current is: since every call to the routine will be relative to a certain target, specified explicitly in the call, the execution will treat every feature name appearing in the text of the routine (for example x in the text of translate) as applying to that particular target. So for the execution of the call

p1.translate (4.0, –1.5)

every occurrence of x in the body of translate, such as those in the instruction

x := x + a

means: “the x of p1”.

调用的形式指出了为什么一个例程的代码(如POINT中的translate)不需要指明Current是“谁”:由于每个例程的调用都将关联到一个特定的目标上,这个目标在调用中已经明确地指明了,所以执行过程将把每一个出现在例程代码中的特性名字(比如translate代码中的x)认为是应用到那个特别的目标上的。因此,对于调用p1.translate (4.0, –1.5)的执行来说,在translate代码中出现的每个x,如在指令x := x + a中的那样,都认为是:“p1x”。

 

The exact meaning of Current follows from these observations. Current means: “the target of the current call”. For example, for the duration of the above call, Current will denote the object attached to p1. In a subsequent call, Current will denote the target of that new call. That this all makes sense follows from the extreme simplicity of the object-oriented computation model, based on feature calls and on the Single Target principle:

Current的准确意思是从这些结论中得来的。Current意思为:“当前调用的目标”。譬如,上面调用的过程中,Current将代表附属到p1的对象。在后面的调用中,Current将代表新调用的目标。所有这一切从面向对象计算模型的极端简单性的根据而来的意义,都基于特性调用和单一目标原则:

 

Feature Call principle

F1 • No software element ever gets executed except as part of a routine call.

F2 • Every call has a target.

特性调用原则

F1·没有软件元素能在例程调用之外被执行。

F2·每一个调用都有一个目标。

 

Qualified and unqualified calls

限定的和非限定的调用

 

It was said above that all object-oriented computation relies on feature calls. A consequence of this rule is that software texts actually contain more calls than meet the eye at first. The calls seen so far were of one of the two forms introduced above:

x.f

x.f (u, v, ¼)

上面我们所说的是所有的面向对象计算方法都基于特性调用。此规则的一个后果是软件代码实际上包含有更多的调用,而不是我们刚开始所看到的那样。到目前为止,我们所知道的调用只是上面介绍的两种形式中的一个:

x.f

x.f (u, v, ¼)

 

Such calls use so-called dot notation (with the “.” symbol) and are said to be qualified because the target of the call is explicitly identified: it is the entity or expression (x in both cases above) that appears before the dot.

这样的调用使用了所谓的圆点符号(“.”),并且称之为限定的(qualified,因为调用的目标是很显然被确定了的:它是出现在圆点之前的实体或是表达式(上面两种情况中的x)。

 

Other calls, however, will be unqualified because their targets are implicit. As an example, assume that we want to add to class POINT a procedure transform that will both translate and scale a point. The procedure’s text may rely on translate and scale:

然而,另外一种调用将是非限定的,因为它们的目标并不明确。举一个例子,假定我们要在类POINT中增加一个过程transform,这个过程将移动并绘制一个点。过程的代码使用translatescale

transform (a, b, factor: REAL) is

-- Move by a horizontally, b vertically, then scale by factor.

do

translate (a, b)

scale ( factor)

end

 

The routine body contains calls to translate and scale. Unlike the earlier examples, these calls do not show an explicit target, and do not use dot notation. Such calls are said to be unqualified.

例程体包含有translatescale的调用。不象先前的例子,这些调用没有出现一个明显的目标,也没有使用圆点符号。这样的调用称之为非限定的(unqualified

 

Unqualified calls do not violate the property called F2 in the Feature Call principle: like qualified calls, they have a target. As you have certainly guessed, the target in this case is the current instance. When procedure transform is called on a certain target, its body calls translate and scale on the same target. It could in fact have been written

非限定的调用并没有违法特性调用原则中的属性调用F2:就象限定调用一样,它们也有着目标。正如您猜到的,这个情况中目标就是当前的实例。当过程transform在一个特定的目标上被调用的时候,它的代码在同一个目标上调用translatescale。事实上,它们可以写成:

do

Current.translate (a, b)

Current.scale ( factor)

 

More generally, you may rewrite any unqualified call as a qualified call with Current as its target. The unqualified form is of course simpler and just as clear.

通常地,您可以重写任何的非限定调用,带上Current作为它的目标变成一个限定调用。当然,非限定的形式更简单,更清晰。

 

The unqualified calls that we have just examined were calls to routines. The same discussion applies to attributes, although the presence of calls is perhaps less obvious in this case. It was noted above that, in the body of translate, the occurrence of x in the expression x + a denotes the x field of the current instance. Another way of expressing this property is that x is actually a feature call, so that the expression as a whole could have been written as Current.x + a.

我们刚才研究过的非限定调用是例程上的调用。虽然在这个例子中调用的出现并不明显,但是同样的讨论将适用于属性。如上所述,在translate体中,表达式x + a中的x表示了当前实例的x字段。表达这个属性的另一种方法是x是一个事实上的特性调用,如此表达式可以完整地写成Current.x + a

 

More generally, any instruction or expression of one of the forms

f

f (u, v, ¼)

is in fact an unqualified call, and you may also write it in qualified form as (respectively)

Current.f

Current.f (u, v, ¼)

although the unqualified forms are more convenient. If you use such a notation as an instruction, f must be a procedure (with no argument in the first form, and with the appropriate number and types of arguments in the second). If it is an expression, f may be an attribute (in the first form only, since attributes have no arguments) or a function.

事实上,下列两者中的任何一个指令或者表达式都是一个非限定调用

f

f (u, v, ¼)

同时,虽然非限定形式更方便一些,但您也可以把它们对应地写成限定的形式

Current.f

Current.f (u, v, ¼)

如果您使用这种符号当作一个指令,那么f必须是一个过程(在第一种形式中没有参数,在第二种中带有适当数目和类型的参数)。如果f是一个表达式,它必须是一个属性(只是针对第一种形式,这是由于属性没有参数)或是一个函数。

 

Be sure to note that this syntactical equivalence only applies to a feature used as an instruction or an expression. So in the following assignment from procedure translate

x := x + a

only the occurrence of x on the right-hand side is an unqualified call: a is a formal argument, not a feature; and the occurrence of x on the left is not an expression (one cannot assign a value to an expression), so it would be meaningless to replace it by Current.x.

要确信注意的是这个法只是等同于应用一个作为一个指令或是表达式而使用的特性。因此,在下列的translate过程中的赋值语句中

x := x + a

只有x出现在右边才是一个非限定调用:a是一个形式参数,不是一个特性;x出现在左边不是一个表达式(它不能赋一个值到表达式上),因此,把它置换成Current.x是无意义的。

 

Operator features

运算符特性

 

A further look at the expression x + a leads to a useful notion: operator features. This notion (and the present section) may be viewed as pure “cosmetics”, that is to say, covering only a syntactical facility without bringing anything really new to the object-oriented method. But such syntactical properties can be important to make developers’ life easier if they are present — or miserable if they are absent. Operator features also provide a good example of how successful the object-oriented paradigm can be at integrating earlier approaches smoothly.

更进一步地考虑表达式x + a,它引出了一个有用的符号:运算符特性。这个符号(和所表达的部分)也许被看作是纯粹的“修饰”,也就是说,只是披着语法的外衣,而没有给面向对象方法带来任何新的东西。但是如果这种语法属性存在的话,能够显著的让开发者的工作更简单——或者说没有它们却是非常的痛苦。运算符特性也提供了一个良好的范例,既面向对象样式如何成功的顺利结合早期的方法。

 

Here is the idea. Although you may not have guessed it, the expression x + a contains not just one call — the call to x, as just seen — but two. In non-O-O computation, we would consider + as an operator, applied here to two values x and a, both declared of type REAL. In a pure O-O model, as noted, the only computational mechanism is feature call; so you may consider the addition itself, at least in theory, to be a call to an addition feature.

这里有一个想法。您也许没有想到,表达式x + a并不仅仅包含一个调用——正如您看到的调用x ——而是两个。在非OO的计算中,我们常把+考虑成一个运算符,这里把两个值xa相加,这两个值都声明成类型REAL。在一个纯OO模型中,计算机制只是特性调用;因此至少在理论上,您可以把加法本身考虑成一个加法特性的调用。

 

To understand this better, consider how we could define the type REAL. The Object rule stated earlier implied that every type is based on some class. This applies to predefined types such as REAL as well as developer-defined types such as POINT. Assume you are requested to write REAL as a class. It is not hard to identify the relevant features: arithmetic operations (addition, subtraction, negation¼), comparison operations (less than, greater than¼). So a first sketch could appear as:

为了更好地理解,请考虑我们如何定义类型REAL。先前的对象规则隐含地规定了每一种类型都基于某个类。这同时适用于如REAL这样的预定义类型,也适用于如POINT这样的开发者定义的(自定义的)类型。假设要求我们写一个REAL类。确定相关的特性并不困难:算术运算(加,减,负),比较运算(小于,大于…)。初始框架如下:

indexing

description: "Real numbers (not final version!)"

class REAL feature

plus (other: REAL): REAL is

-- Sum of current value and other

do

¼

end

minus (other: REAL) REAL is

-- Difference of current value and other

do

¼

end

negated: REAL is

-- Current value but with opposite sign

do

¼

end

less_than (other: REAL): BOOLEAN is

-- Is current value strictly less than other?

do

¼

end

¼ Other features ¼

End

 

With such a form of the class, you could not write an arithmetic expression such as x + a any more; instead, you would use a call of the form

x.plus (a)

您不能用这样的类形式写出一个象x + a之类的算术表达式;您需要使用x.plus (a)这样形式的调用。

 

Similarly, you would have to write x.negated instead of the usual –x.

同样地,您需要使用x.negated,而不是平时所用的–x

 

One might try to justify such a departure from usual mathematical notation on the grounds of consistency with the object-oriented model, and invoke the example of Lisp to suggest that it is sometimes possible to convince a subset of the software development community to renounce standard notation. But this argument contains it owns limitations: usage of Lisp has always remained marginal. It is rather dangerous to go against notations which have been in existence for centuries, and which people have been using since elementary school, especially when there is nothing wrong with these notations.

以与面向对象模型保持一致为理由而违背常用的数学符号,这件事情也许需要设法证明是对是错,同时,借用LISP的例子来说明一些小的软件开发团队有的时候放弃标准的符号是极有可能的。但是这个根据有着自己的局限性:LISP的用法总是会留下标记。违反几百年来一直存在,并且人们自小就使用的符号这是相当危险的,尤其是这些符号并没有什么不对的地方。

 

A simple syntactical device reconciles the desire for consistency (requiring here a single computational mechanism based on feature call) and the need for compatibility with traditional notations. It suffices to consider that an expression of the form

x + a

is in fact a call to the addition feature of class REAL; the only difference with the plus feature suggested above is that we must rewrite the declaration of the corresponding feature to specify that calls will use operator notation rather than dot notation.

一个简单的语法设计即协调了一致性的要求(这里需要一个单一的基于特性调用的计算机制),也协调了与传统符号兼容的需要。它满足了一个x + a形式的表达式实际上是一个类REAL的加法特性的调用;唯一和上面建议的plus特性不同的是我们必须重写对应特性的声明,来指明调用将使用运算符而不是圆点符号。

 

Here is the form of a class that achieves this goal:

这里是实现这个目的的类的形式:

 

indexing

description: "Real numbers"

class REAL feature

infix "+" (other: REAL): REAL is

-- Sum of current value and other

do

¼

end

infix "–" (other: REAL) REAL is

-- Difference of current value and other

do

¼

end

prefix "–": REAL is

-- Current value but with opposite sign

do

¼

end

infix "<" (other: REAL): BOOLEAN is

-- Is current value strictly less than other?

do

¼

end

¼ Other features ¼

End

 

Two new keywords have been introduced: infix and prefix. The only syntactical extension is that from now on we may choose feature names which, instead of identifiers (such as distance or plus), are of one of the two forms

infix "§"

prefix "§"

where § stands for an operator symbol chosen from a list which includes +, , S, <, <= and a few other possibilities listed below. A feature may have a name of the infix form only if it is a function with one argument, such as the functions called plus, minus and less_than in the original version of class REAL; it may have a name of the prefix form only if it is a function with no argument, or an attribute.

介绍两个新的关键字:infixprefix。从现在开始,最好的语法选择范围是,我们可以选择用以下两种形式之一来命名的特性,而不是标识符(如distanceplus

infix "§"

prefix "§"

这里的§代表着一个运算符号,选自于包括+S<<=和其它的一些可能的符号的列表。一个特性可以有一个infix格式的名字,如果它只是一个带有一个参数的函数,如在类REAL原始版本中的plusminusless_than函数;它也可以有一个prefix格式的名字,如果它只是一个不带有参数的函数,或是一个属性。

 

Infix and prefix features, collectively called operator features, are treated exactly like other features (called identifier features) with the exception of the two syntactical properties already seen:

Infixprefix特性,叫做运算符特性(operator features,除了已经知道的两个语法属性以外,它们等同于其它的特性(叫做标识符特性(identifier features):

 

• The name of an operator feature as it appears in the feature’s declaration is of the form infix "§" or prefix "§", rather than an identifier.

·当出现在特性声明中的时候,一个运算符特性的名字是infix "§"或是prefix "§"的形式,而不是一个标识符。

 

• Calls to operator features are of the form u § v (in the infix case) or § u (in the prefix case) rather than using dot notation.

·调用运算符特性是u § v(中缀)或§ u(前缀)的形式,而不是使用圆点符号。

 

As a consequence of the second property, operator features only support qualified calls. If a routine of class REAL contained, in the first version given earlier, an unqualified call of the form plus (y), yielding the sum of the current number and y, the corresponding call will have to be written Current + y in the second version. With an identifier feature, the corresponding notation, Current.plus (y), is possible but we would not normally use it in practice since it is uselessly wordy. With an operator feature we do not have a choice.

第二个属性的结果是,运算符特性只支持限定调用。在之前给出的第一个版本中,如果类REAL的一个例程包含有一个plus (y)形式的非限定调用,其产生当前数字与y的和,那么在第二个版本中相应的调用将必须写成Current + y。带有一个标识符的相应符号是Current.plus (y),这没什么问题但是由于拖沓冗长我们通常在实际上并不使用。对于一个运算符特性我们没有选择。

 

Other than the two syntactical differences noted, operator features are fully equivalent to identifier features; for example they are inherited in the same way. Any class, not just the basic classes such as REAL, can use operator features; for example, it may be convenient in a class VECTOR to have a vector addition function called infix "+".

除了这两个语法的不同之外,运算符特性完全等同于标识符特性;比如可以用同样的方式继承它们。不仅仅是象REAL这样的基本类,任何的类都能够使用运算符特性;譬如,在类VECTOR中,有一个infix "+"向量加法函数是很方便的。

 

The following rule will apply to the operators used in operator features. An operator is a sequence of one or more printable characters, containing no space or newline, and beginning with one of

+ – S / < > = / ^ @ # | &

随后的规则将应用到在运算符特性中使用的运算符上。一个运算符是一个或多个可打印字符的序列,不包含空格和换行,并以下列的其中一个开头+ – S / < > = / ^ @ # | &

 

In addition, the following keywords, used for compatibility with usual Boolean notation, are permitted as operators:

另外,下面和通常的布尔符号兼容的关键字,被认为是运算符:

not and or xor and then or else implies

 

In the non-keyword case, the reason for restricting the first character is to preserve the clarity of software texts by ensuring that any use of an infix or prefix operator is immediately recognizable as such from its first character.

在非关键字的情况中,限定第一个字符的原因是保护软件代码的清晰度,这确保任何使用中缀或前缀的运算符可以立刻从其第一个字符中辨认出来。   

 

Basic classes (INTEGER etc.) use the following, known as standard operators:

• Prefix: + – not.

• Infix: + – S / < > <= >= // // ^ and or xor and then or else implies.

基本类(INTEGER等)使用下列的标准运算符:

·前缀:+ – not

·中缀:+ – S / < > <= >= // // ^ and or xor and then or else implies

 

The semantics is the usual one. // is used for integer division, // for integer remainder, ^ as the power operation, xor as exclusive or. In class BOOLEAN, and then and or else are variants of and and or, the difference being explained in a later chapter, and implies is the implication operator, such that a implies b is the same as (not a) or else b.

语义也符合惯例。//用作整数的除,//用作整数的余数,^是幂运算,xor是异或。在类BOOLEAN中,and thenor elseandor的变体,不同之处将在后面解释,implies蕴涵运算符,a implies b(not a) or else b一样。

[]: implies有的书也称为蕴涵算子.

 

Operators not in the “standard” list are called free operators. Here are two examples of possible operator features using free operators:

不在“标准”列表中的运算符叫做自由运算符(free operators)。这里有两个使用自由运算符的例子:

 

• When we later introduce an ARRAY class, we will use the operator feature infix "@" for the function that returns an array element given by its index, so that the i-th element of an array a may be written simply as a @ i.

·当我们后面介绍ARRAY类的时候,对于返回一个给定索引的数组元素的函数,我们将使用运算符特性infix "@",因此,一个数组a的第i个元素可以简单地写成a @ i

 

• In class POINT, we could have used the name infix "|–|" instead of distance, so that the distance between p1 and p2 is written p1 |–| p2 instead of p1.p2.

·在类POINT中,我们能够使用infix "|–|"命名来替代distance,因此,p1p2之间的距离用p1 |–| p2来替代p1.distance (p2)

 

The precedence of all operators is fixed; standard operators have their usual precedence, and all free operators bind tighter than standard operators.

所有运算符的优先级是固定的;标准运算符有着它们通常的优先级,所有的自由运算符比标准运算符绑定得更加紧密。

 

The use of operator features is a convenient way to maintain compatibility with well-accepted expression notation while ensuring the goal of a fully uniform type system (as stated by the Object Rule) and of a single fundamental mechanism for computation. In the same way that treating INTEGER and other basic types as classes does not need to cause any performance problem, treating arithmetic and boolean operations as features does not need to affect efficiency. Conceptually, a + x is a feature call; but any good compiler will know about the basic types and their features, and will be able to handle such a call so as to generate code at least as good as the code generated for a + x in C, Pascal, Ada or any other language in which + is a special hard-wired language construct.

当需要确保一个完整统一的类型系统(如对象规则的陈述)和一个单一基本的计算机制的目标的时候,使用运算符特性是一种方便的方法,以公认的表达式符号维护着兼容性。用同样的方法把INTEGER和其它的基本类型视为类,这不会引发任何的性能问题,把算术和布尔运算视为特性不会影响效率。从概念上来说,a + x是一个特性调用;但是任何优秀的编译器都知道基本类型和它们的特性,同时能够处理这样的一种调用,以致生成的代码至少和在CPasclAda和任何其它的把+当作一个特别固化的语言结构的语言为a + x生成的代码一样好。

 

When using operators such as +, < and others in expressions, we may forget, most of the time, that they actually stand for feature calls; the effect of these operators is the one we would expect in traditional approaches. But it is pleasant to know that, thanks to the theoretical context of their definition, they do not cause any departure from object-oriented principles, and fit in perfectly with the rest of the method.

当在表达式中使用诸如+<等等运算符的时候,我们几乎可以忘记它们其实是特性调用;这些运算符的效果是和我们在传统的方法中所期待的一样。但是,很高兴地知道的是,由于理论上的定义环境,它们并没有引起任何背离面向对象原则的事情,而且极好的符合了其它的方式。

 

7.8 SELECTIVE EXPORTS AND INFORMATION HIDING

7.8 选择性输出和信息隐藏

 

In the examples seen so far all the features of a class were exported to all possible clients. This is of course not always acceptable; we know from earlier discussion how important information hiding is to the design of coherent and flexible architectures.

到目前为止在所举的例子中,类的所有特性都对所有可能的客户输出了。当然这不总是可以接受的们从早先的讨论中懂得了如何隐藏重要信息是连贯的和灵活的系统架构的设计。

 

Let us take a look at how we can indeed restrict features to no clients, or to some clients only. This section only introduces the notation; the chapter on the design of class interfaces will discuss its proper use.

让我们看看如何真正地限制特性,不对客户开放,或者只对某些客户开放。 这个部分只介绍符号;类接口设计的章节中将讨论其正确的用法。

 

Full disclosure

完全公开

 

By default, as noted, features declared without any particular precaution are available to all clients. In a class of the form

class S1 feature

f ¼

g ¼

¼

end

features f, g, ¼ are available to all clients of S1. This means that in a class C, for an entity x declared of type S1, a call

x.f ¼

is valid, provided the call satisfies the other validity conditions on calls to f, regarding the number and types of arguments if any. (For simplicity the discussion will use identifier features as examples, but it applies in exactly the same way to operator features, for which the clients will use calls in infix or prefix form rather than dot notation.)

缺省的情况下,没有任何特别保护的特性声明是对所有的客户都有效的。在类

class S1 feature

f ¼

g ¼

¼

end

的形式中,特性fgS1的所有客户都可用。这意味着在类C中,对于一个声明为类型S1的实体x,调用x.f ¼是有效的,提供调用满足其它有效性达到调用的条件,如果有的话,关于参数的数目和类型。(为了简单,讨论将使用标识符特性作为例子,但是它完全可以对运算符适用同样的方式,客户将以中缀或前缀而不是圆点符号来使用调用。)

 

Restricting client access

限制客户访问

 

To restrict the set of clients that can call a certain feature h, we will use the possibility for a class to have two or more feature clauses. The class will then be of the form

要限制那些能够调用一个特定特性h的客户,我们将对一个类使用两个或是更多个feature子句。那么类将呈现出这个形式

class S2 feature

f ¼

g ¼

feature {A, B}

h ¼

¼

End

 

Features f and g have the same status as before: available to all clients. Feature h is available only to A and B, and to their descendants (the classes that inherit directly or indirectly from A or B). This means that with x declared of type S2 a call of the form

x.h ¼

is invalid unless it appears in the text of A, B, or one of their descendants.

特性fg有着和以往一样的状态:对所有的客户开放。特性h只是对AB有效,也对它们的后代(从AB直接或间接继承的类)有效。这意味着x声明成S2类型,x.h ¼形式的调用是无效的,除非它出现在AB或是后代的代码中。

 

As a special case, if you want to hide a feature i from all clients, you may declare it as exported to an empty list of clients:

作为一个特殊的情况,如果您想对所有的客户隐藏特性i,您可以把它声明成导出一个空的客户列表:

class S3 feature { }

i ¼

end

 

In this case a call of the form x.i (¼) is always invalid. The only permitted calls to i are unqualified calls of the form

i (¼)

appearing in the text of a routine of S3 itself, or one of its descendants. This mechanism ensures full information hiding.

在这种情况下,x.i (¼)形式的调用总是无效的。唯一被允许的i调用是非限定调用形式i (¼),这出现在S3自身的例程代码中,或是其后代中。这种机制确保了完全的信息隐藏。

 

The possibility of hiding a feature from all clients, as illustrated by i, is present in many O-O languages. But most do not offer the selective mechanism illustrated by h: exporting a feature to certain designated clients and their proper descendants. This is regrettable since many applications will need this degree of fine control.

正如i所描述的那样,对所有的客户隐藏一个特性的可能性出现在许多OO语言中。但是其中大部分并不提供h所表现出的那种选择性机制:导出一个特性给某个指定的客户和它们拥有的后代。由于许多的应用需要这种细微控制的程度,所以有些遗憾。

 

The discussion section of the present chapter explains why selective exports are a critical part of the architectural mechanisms of the object-oriented approach, avoiding the need for “super-modules” that would hamper the simplicity of the method.

本章的讨论解释了为什么选择性的导出是面向对象方法的架构机制中一个至关重要的部分,这避免了对破坏方法简单性的“超级模块”的需要。

 

We will encounter various examples of selective exports in subsequent chapters, and will study their methodological role in the design of good modular interfaces.

我们将后面的章节中遇到各种选择性导出的例子,并会了解到它们在良好模块接口设计中所对应的方法角色。

 

Style for declaring secret features

声明秘密特性的式样

 

A small point of style. A feature declared in the form used above for i is secret, but perhaps this property does not stand out strongly enough from the syntax. In particular, the difference with a public feature may not be visible enough, as in

式样上的一个小小的要点。在上面i使用的形式中有一个特性声明是秘密的,但是也许这个属性没有足够地从语法中突出出来。尤其,与公共特性的不同之处并没有充分地表现出来:

class S4 feature

exported¼

feature { }

secret ¼

end

where feature exported is available to all clients whereas secret is available to no client. The difference between feature { }, with an empty list in braces, and feature, with no braces, is a little weak. For that reason, the recommended notation uses not an empty list but a list consisting of the single class NONE, as in

其中,特性exported是对所有的客户有效,而secret不对客户开放。空大括号的feature { }和不带大括号的feature,它们之间的不同并不明显。基于这个原因,所介绍的符号不使用一个空的列表,而是一个包含一个单一类NONE的列表:

class S5 feature

¼ Exported ¼

feature {NONE}

¼ Secret ¼

End

 

Class NONE, which will be studied in a later chapter in connection with inheritance, is a Base library class which is so defined as to have no instances and no descendants. So exporting a feature to NONE only is, for all practical purposes, the same as keeping it secret. As a result there is no meaningful difference between the forms illustrated by S4 and S5; for reasons of clarity and readability, however, the second form is preferred, and will be employed in the rest of this book whenever we need to introduce a secret feature.

NONE将在后面连同继承一起学习。它是一个基础库类,没有实例也没有后代。因此,实际上只导出一个特性给NONE是和保持其秘密性一样的。结果在S4S5所描绘的形式之间并没有什么意义上的不同;然而,因为清晰度和可读性的原因,第二种形式更好,只要在本书中我们需要使用一个秘密特性的时候我们都将采用这种形式。

 

Exporting to yourself

导出给自己

 

A consequence of the rules seen so far is that a class may have to export a secret feature.

Assume the declaration

到目前为止,规则的结论是一个类可以导出一个秘密特性。假设声明如下

indexing

note: "Invalid as it stands (see explanations below)"

class S6 feature

x: S6

my_routine is do ¼ print (x.secret) ¼ end

feature {NONE}

secret: INTEGER

end -- class S6

 

By declaring x of type S6 and making the call x.secret, the class becomes its own client. But this call is invalid, since secret is exported to no class! That the unauthorized client is S6 itself does not make any difference: the {NONE} export status of secret makes any call x.secret invalid. Permitting exceptions would damage the simplicity of the rule.

声明类型为S6x并调用x.secret,类变成了它自己本身的客户。但是由于secret并不对任何类导出,所以这个调用是无效的!非授权的客户是S6本身并不能使之有所不同:secret{NONE}导出状态使得任何的x.secret调用都无效。允许特例将破坏规则的简单性。

 

The solution is simple: instead of feature {NONE} the header of the second feature clause should read feature {S6}, exporting the feature to the class itself and its descendants.

解决方案很简单:第二feature子句的头信息用feature {S6}来取代feature {NONE}它输出特性到类的本身和后代中。

 

Be sure to note that this is only needed if you want to use the feature in a qualified call such as appears in print (x.secret). If you are simply using secret by itself, as in the instruction print (secret), you of course do not need to export it at all. Features declared in a class must be usable by the routines of the class and its descendants; otherwise we could never do anything with a secret feature! Only if you use the feature indirectly in a qualified call do you need to export it to yourself.

要注意的是,这只有在您想使用限定性调用的特性,如print (x.secret)显示的那样时才需要。如果您只是简单地使用secret本身,如指令print (secret),您当然根本不需要导出。在类中声明的特性必须能被类和后代的例程所使用;否则,我们根本不能处理秘密特性!只有您在限定性调用中间接地使用一个特性,您才需要导出到类本身中去。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值