7.9 PUTTING EVERYTHING TOGETHER

7.9 组合一切

The previous discussions have introduced the basic mechanisms of object-oriented computation, but we are still missing the big picture: how does anything ever get executed?

之前的讨论介绍了面向对象计算方法的基本机制,但是我们一直忽略了重要部分:所有这一切如何执行?

Answering this question will help us piece everything together and understand how to build executable systems from individual classes.

回答这个问题将有助于我们组合所有的一切,并理解如何从各个独立的类中构建出执行系统。

General relativity

一般相关性

What is a little mind-boggling is that every description given so far of what happens at run time has been relative. The effect of a routine such as translate is relative to the current instance; within the class text, as noted, the current instance is not known. So we can only try to understand the effect of a call with respect to a specific target, such as p1 in

p1.translate (u, v)

有点难以置信的是,迄今为止每一个给出的在运行时刻所发生的描述都有着关联。象translate这样的一个例程的结果关系到当前的实例;在类代码里,当前的实例并不知晓。因此,我们只能够试着理解一个具体目标的调用结果,如在p1.translate (u, v)里面的p1

But this brings the next question: what does p1 actually denote? Here again the answer is relative. The above call must appear in the text of some class such as GRAPHICS. Assume that p1 is an attribute of class GRAPHICS. Then the occurrence of p1 in the call, as noted above, may be viewed as a call: p1 stands for Current.p1. So we have only pushed the problem further, as we must know what object Current stood for at the time of the above call! In other words, we must look at the client that called the routine of class GRAPHICS containing that call.

但是这带来了下一个问题:p1实际上代表了什么?这里的答案再一次是相对的。上面的调用必须出现在某个类的代码中,例如GRAPHICS。假定p1是类GRAPHICS的一个属性。那么在调用过程中出现的p1可以看做一个调用:p1代表了Current.p1。由于我们必须知道在上述调用的时刻对象Current代表了什么,因此我们只是把问题推进了一步。换句话说,我们必须要查看包含那个调用的类GRAPHICS的例程所要调用的客户。

So this attempt at understanding a feature call starts off a chain of reasoning, which we will not be able to follow to the end unless we know where execution started.

因此,这个要理解一个特性调用的尝试从一连串的推理中开始了,我们不能够一直跟到最后除非我们知道执行从什么地方开始。

The Big Bang

创世纪

To understand what is going on let us generalize the above example to an arbitrary call. If we do understand that arbitrary call, we will indeed understand all of O-O computation, thanks to the Feature Call principle which stated that

为了知道所发生的一切,就让我们把上述的例子扩展到一个任意的调用上。如果我们理解了那个任意的调用,我们就能真正地理解所有的OO计算方式了,这是由于特性调用原则的描述

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

F2 • Every call has a target.

F1·没有软件元素能单独执行除非是作为例程调用的一部分。

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

Any call will be of one of the following two forms (the argument list may be absent in either case):

• Unqualified: f (a, b, ¼)

• Qualified: x.g (u, v, ¼)

任何调用都将是下面两个形式中的一个(可能没有参数列表):

·非限定:f (a, b, ¼)

·限定:x.g (u, v, ¼)

The call appears in the body of a routine r. It can only get executed as part of a call to r. Assume we know the target of that call, some object OBJ. Then the target t is easy to determine in each case:

调用出现在一个例程r的代码体中。它只能作为调用r的代码部分来执行。假设我们知道了调用的目标是某个对象OBJ。那么,在下列的每一个情况中目标t能很容易地确定:

T1 • For the unqualified form, t is simply OBJ. Cases T2, T3 and T4 will apply to the qualified form.

T1·对于非限定的形式,t就是OBJ。T2,T3和T4的情况应用于限定形式。

T2 • If x is an attribute, the x field of OBJ has a value which must be attached to some object; t is that object.

T2·如果x是一个属性,OBJ的x字段有一个必须要附属在某个对象的值;t就是那个对象。

T3 • If x is a function, we must first execute the (unqualified) call to x; the result gives us t.

T3·如果x是一个函数,我们就要首先执行x的(非限定)调用;得到的结果就是t

T4 • If x is a local entity of r, earlier instructions will have given x a value, which at the time of the call must be attached to a certain object; t is that object.

T4·如果x是一个r的局部实体,之前的指令将赋予x一个值,这个值在调用期间会附属在一个特定的对象上;t就是那个对象。

The only problem with these answers is of course that they are relative: they only help us if we know the current instance OBJ. What is OBJ? Why, the target of the current call, of course! As in the traditional song (the kid was eaten by the cat, the cat was bitten by the dog, the dog was beaten by the stick¼), we do not see the end of the chain.

当然,这些答案的唯一的问题是它们是相对的:只有在我们知道当前的实例OBJ的情况下,它们才能有所帮助。OBJ是什么?哦?当然是当前调用的目标!就象那支民谣(猫吃小羊,狗吃猫,棍子吃掉狗),我们并不知道食物链的尽头。

To transform these relative answers into absolute ones, then, we must know what happened when everything started — at Big Bang time. Here is the rule:

那么,要把这些相对的答案变成绝对的,我们就一定要知道当一切开始的时候——在创世纪的时候——发生了什么。规则是:

Definition: system execution

Execution of an object-oriented software system consists of the following two steps:

• Create a certain object, called the root object for the execution.

• Apply a certain procedure, called a creation procedure, to that object.

定义:系统执行

一个面向对象软件系统的执行由下面的两个步骤组成:

·创建一个特定的对象,其称之为执行中的根对象root object)。

·应用一个叫做创建过程(creation procedure的特定过程到那个对象上。

At Big Bang time, an object gets created, and a creation procedure gets started. The root object is an instance of a certain class, the system’s root class; the creation procedure is one of the procedures of the root class. In all but trivial systems, the creation procedure will itself create new objects and call routines on them, triggering more object creations and more routine calls. System execution as a whole is the successive deployment of all the pieces in a giant and complex firework, all resulting directly or indirectly from the initial lighting of a minuscule spark.

在创世纪的时候,对象得到创造,并且开始了一个创建过程。 根对象是一个特定类的实例,这个类是系统的类(root class;创建过程是根类的过程之一。在所有小型的系统中,创建过程自己创造新的对象并调用它们的例程,触发更多的对象创建和更多的例程调用。系统的执行整体上就象是在一个巨型和复杂的烟花上的所有片断的连续部署,所有的结果直接或间接的起因于一个最初的微小火花。

Once we know where everything starts, it is not difficult to trace the fate of Current throughout this chain reaction. The first current object, at the start of everything (Big Bang time, when the root’s creation procedure is called), is the root object. Then at any stage during system execution let r be the latest routine to have been called; if OBJ was the current object at the time of the call to r, here is what becomes of Current during the execution of r:

一旦我们知道了源头,那么就很容易地从它的连锁反应中跟踪Current的命运。源头的(创世纪时期,当根创建过程被调用的时候)第一个当前对象是根对象。接着,在系统实行的任何阶段,设r是被调用的最近一个例程;如果OBJ是在调用r时候的当前对象,那么在r的执行期间Current是:

C1 • If r executes an instruction which does not call a routine (for example an assignment), we keep the same object as current object.

C1·如果r执行一个并不调用例程的指令(如一个赋值),那么我们保持同样的对象为当前的对象。

C2 • Starting an unqualified call also keeps the same object as current object.

C2·开始一个非限定调用保持同样的对象为当前的对象。

C3 • Starting a qualified call x.f ¼ causes the target object of that call, which is the object attached to x (determined from OBJ through the rules called T1 to T4 at the top of the previous page), to become the new current object. When the call terminates, OBJ resumes its role as current object.

C3·开始一个限定调用x.f ¼,将对象附属到x上(经在上一页的T1到T4调用规则由OBJ决定),这会引发调用的目标对象成为一个新的当前对象。当调用结束时,OBJ恢复成当前的对象。

In cases C2 and C3 the call may be to a routine that itself includes further calls, qualified or not; so this rule must be understood recursively.

在C2和C3的情况中,也许调用了一个自身也包含着更进一步的限定或是非限定的调用的例程;因此,规则必须通晓递归。

There is nothing mysterious or confusing, then, in the rule for determining the target of any call, even though that rule is relative and in fact recursive. What is mind-boggling is the power of computers, the power we use to play sorcerer’s apprentice by writing a deceptively small software text and then executing it to create objects and perform computations on them in numbers so large — number of objects, number of computations — as to appear almost infinite when measured on the scale of human understanding.

那么,在决定任何调用目标的规则中就没有什么神秘的或是混淆的了,即使规则有着关联和递归。令人难以置信的是计算机的能力,我们使用这个能力 编写了一个虚拟的小型软件代码来玩魔法学徒的游戏。接着,执行程序创建对象并完成海量计算——大规模的对象,大量的计算——根据几乎无穷大的出现来测量人类理解的范围。

Systems

系统

The emphasis in this chapter is on classes: the individual components of object-oriented software construction. To obtain executable code, we must assemble classes into systems.

本章的重点是在类上:面向对象软件构造的独立的组件。要获得可执行代码,我们必须要把类装配成系统。

The definition of a system follows from the previous discussion. To make up a system we need three things:

从先前的讨论中得出了系统的定义。要组成一个系统,我们需要三件事情:

• A set CS of classes, called the system’s class set.

·类的组合CS,称之为系统的类集(class set

• The indication of which class in CS is the root class.

·CS中的根类(root class的标记。

• The indication of which procedure of the root class is the root creation procedure.

·根类过程中的根创建过程(root creation procedure的标记。

To yield a meaningful system, these elements must satisfy a consistency condition, system closure: any class needed directly or indirectly by the root class must be part of CS.

要产生一个有意义的系统,这些元素必须要满足一个一致性条件,系统闭合(system closure:根类直接或间接需要的任何类必须是CS的一部分。

Let us be a little more precise:

让我们更精确一些:

• A class C needs directly a class D if the text of C refers to D. There are two basic ways in which C may need directly D: C may be a client of D, as defined earlier in this chapter, and C may inherit from D, according to the inheritance relation which we will study later.

·如果C的代码引用了D,那么说类C直接需要needs directly)类D。直接需要有着两种基本的方法:C可以是D的一个客户,正如本章开头所定义的那样;C可以从D中继承,这依照继承关系,后面我们将会学习到。

• A class C needs a class E, with no further qualification, if C is E or C needs directly a class D which (recursively) needs E.

·如果CE,或C直接需要类DD需要E(递归),那么说在没有进一步的限制下,类C需要needs)类E

With these definitions we may state the closure requirement as follows:

有着这些定义,我们可以如下规定闭合的需求:

Definition: system closure

A system is closed if and only if its class set contains all classes needed by the root class.

定义:系统闭合

一个系统是闭合的,有且只有它的类集包含了所有被根类需要的类。

If the system is closed, a language-processing tool, such as a compiler, will be able to process all its classes, starting with the root class, and recursively handling needed classes as it encounters their names. If the tool is a compiler, it will then produce the executable code corresponding to the entire system.

如果系统是闭合的,那么编译器之类的语言处理工具将能够从根类开始处理所有的类,并当遇到类的名字时能递归处理所需的类。如果工具是一个编译器,那么它将产生符合整个系统的可执行代码。

This act of tying together the set of classes of a system, to generate an executable result, is called assembly and is the last step in the software construction process.

连接系统的类集,生产一个可执行结果的动作,叫做装配assembly)。在软件构造过程中这是最后一个步骤。

Not a main program

不是一个主程序

The discussions in the previous chapters repeatedly emphasized that systems developed with the object-oriented method have no notion of main program. By introducing the notion of root class, and requiring the system specification to indicate a particular creation procedure, have we not brought main programs back through a side door?

在之前章节中的讨论一再强调用面向对象方法的系统开发没有主程序的概念。 通过介绍根类的概念,并要求系统规格表明一个特殊的创建过程,我们不是换了一种方法又带回了主程序吗?

Not quite. What is wrong with the traditional notion of main program is that it merges two unrelated concepts:

并不完全这样。主程序的传统概念的不足之处是它融合了两个并不相干的概念:

• The place where execution begins.

·执行开始的地方。

• The top, or fundamental component of the system’s architecture.

·系统架构的顶端,或基本组件。

The first of these is obviously necessary: every system will begin its execution somewhere, so we must have a way to let developers specify the starting point; here they will do so by specifying a root class and a creation procedure. (In the case of concurrent rather than sequential computation we may have to specify several starting points, one per independent thread of computation.)

第一条显然是必须的:每个系统都将从某点开始执行,因此我们必须用一种方式让开发者指定起点;这里,他们可以通过指定根类和一个创建过程来做到。(在并发而不是顺序计算的情况下,我们也许要指定几个起点,每条独立的计算线程指定一个。)

On the concept of top, enough abuse has been heaped in earlier chapters to make further comments unnecessary.

在顶端的概念上,在之前的章节中介绍的误用已经足够多了,进一步的解释没什么必要了。

But regardless of the intrinsic merit of each of the two notions, there is no reason to merge them: no reason to assume that the starting point of a computation will play a particularly important role in the architecture of the corresponding system. Initialization is just one of many aspects of a system. To take a typical example, the initialization of an operating system is its booting procedure, usually a small and relatively marginal component of the OS; using it as the top of the system’s design would not lead to an elegant or useful architecture. The notion of system, and object technology in general, rely in fact on the reverse assumption: that the most important property of a system is the set of classes that it contains, the individual capabilities of these classes, and their relationships. In this view the choice of a root class is a secondary property, and should be easy to change as the system evolves.

尽管两者有着内在的优点,但是没有什么理由来合并它们:没有理由认为,一个计算的起点在对应的系统架构中将扮演着一个特别重要的角色。初始化只是一个系统的诸多方面之一。用一个典型的例子,一个操作系统的初始化是它的引导过程,通常只是一个小规模的和相对不重要的OS组件;使用它作为系统设计的顶端不会导致一个优秀而有效的架构出现。系统的概念,和通常的对象技术实际上却依靠相反的假设:一个系统中的最重要的属性是它所包含的类集,以及这些类的各自的能力和彼此之间的关系。在这个观念中,根类的选择是次要属性,并且在系统演变中应该很容易被改变。

As discussed extensively in an earlier chapter, the quest for extendibility and reusability requires that we shed the practice of asking “what is the main function?” at an early stage of the system’s design and of organizing the architecture around the answer. Instead, the approach promotes the development of reusable software components, built as abstract data type implementations — classes. Systems are then built as reconfigurable assemblies of such components.

正如在之前被广泛地谈论过,对扩充性和复用性的追求要求我们在系统设计和围绕答案组织架构的早期阶段放弃询问“什么是主函数?”的习惯。反而,方法提倡可复用的软件组件的开发,建立抽象数据类型的实现——类。然后系统被构建成这种组件的可配置的程序集。

In fact, you will not always build systems in the practice of O-O software development. An important application of the method is to develop libraries of reusable components — classes. A library is not a system, and has no root class. When developing a library, you may of course need to produce, compile and execute one or more systems along the way, but such systems are a means, not an end: they help test the components, and will usually not be part of the library as finally delivered. The actual delivered product is the set of classes making up the library, which other developers will then use to produce their own systems — or again their own libraries.

实际上,您不总是在OO软件开发的实践中建立系统。方法的一种重要应用是开发可复用的组件库(libraries,可复用的组件就是类。库不是系统,也没有根类。当开发库的时候,您当然也需要用同样的方法创建,编译和执行一个或更多系统,但这样的系统是手段,不是目标:它们帮助测试组件,并且通常不会作为库的一部分被最终交付。实际被交付的产品是组成库的类集,然后其他的开发者将使用它们来创建自己的系统——或是他们自己的库。

Assembling a system

装配一个系统

The process of putting together a number of classes (one of which is designated as root) to produce an executable system was called “assembly” above. How in practice do we assemble a system?

上述汇集许多类(其中之一被选定为根)产生一个可执行系统的过程称为“装配”。在实践上我们如何装配一个系统?

Let us assume an operating system of the usual form, where we will keep our class texts stored in files. The language processing tool in charge of this task (compiler, interpreter) will need the following information:

让我们假设一个普通形式的操作系统,我们把我们的类代码存放在文件中。负责这项任务的语言处理工具(编译器,解释器)需要以下信息:

A1 • The name of the root class.

A1·根类的名字。

A2 • A universe, or set of files which may contain the text of classes needed by the root (in the above precise sense of “needed”).

A2·一个全域universe),或者文件集,其可以包含根类所需的类代码。

This information should not be included in the class texts themselves. Identifying a class as root in its own text (A1) would violate the “no main program” principle. Letting a class text include information about the files where the needed classes reside would tie the class to a particular location in the file system of a given installation; this would prevent use of the class by another installation and is clearly inappropriate.

在类代码的自身中不应该包括这个信息。在类自己的代码中把类看作为根类(A1)将违犯“无主程序”原则。让一个类代码包含所需要的类文件的信息,这会把类绑定到一个特定安装程序的文件系统的一个特殊地位。这将妨碍另一个安装程序使用这个类,明显不对。

These observations suggest that the system assembly process will need to rely on some information stored outside of the text of the classes themselves. To provide this information we will rely on a little control language called Lace. Let us observe the process, but not until we have noted that the details of Lace are not essential to the method; Lace is just an example of a control language, allowing us to keep the O-O components (the classes) autonomous and reusable, and to rely on a separate mechanism for their actual assembly into systems.

这些结果建议系统装配过程将需要依靠一些存储在类代码之外的信息。要提供这个信息,我们将使用一个小小的控制语言,Lace。让我们观察过程,我们已经注意到,Lace的细节对方法而言并不是必要的;Lace只是一个控制语言的例子,允许我们保持OO组件(类)自治和复用,并且依靠一个单独的机制把这些组件实际装配进系统。

A typical Lace document, known as an Ace file, might appear as follows:

一个典型的Lace文档显示如下,这是被人熟知的Ace file

system painting root

GRAPHICS ("painting_application")

cluster

base_library: "/ library/base";

graphical_library: "/library/graphics";

painting_application: "/user /application"

end -- system painting

The cluster clause defines the universe (the set of files containing class texts). It is organized as a list of clusters; a cluster is a group of related classes, representing a subsystem or a library.

cluster子句定义了全域(包含了类代码的文件集)。它被组织成一个集群列表;一个集群是一组关联类,代表了一个子系统或是一个库。

In practice, an operating system such as Windows, VMS or Unix provides a convenient mechanism to support the notion of cluster: directories. Its file system is structured as a tree, where only the terminal nodes (leaves), called “plain files”, contain directly usable information; the internal nodes, called directories, are sets of files (plain files or again directories).

在实践中,一个诸如Windows,VMS或者Unix之类的操作系统提供了一个方便的机制来支持集群的概念:目录(directories)。它的文件系统被构造成一棵树,其中,只有终端结点(叶),称为“无格式文件”,直接包含可用信息;中间的结点,称为目录,是文件集(无格式文件或次目录)。

We may associate each cluster with a directory. This convention is used in Lace as illustrated above: every cluster, with a Lace name such as base_library, has an associated directory, whose name is given as a string in double quotes, such as "/ library/base". This file name assumes Windows conventions (names of the form /dir1/dir2/¼), but this is just for the sake of the example. You can obtain the corresponding Unix names by replacing the backslash characters / by slashes /.

我们可以把每一个集群关联到一个目录上。就象上面的图例,Lace使用这种习惯:每一个集群,带着一个Lace名字,如base_library,有着一个关联的目录,如"/ library/base"这样,在双引号中的字符串代表了目录的名字。这个文件名采取了Windows的约定(/dir1/dir2/¼形式的名字),但是这只是为了方便举例。您可以通过把反斜杠字符/置换成斜杠字符/来使用对应的Unix名字。

Although by default you may use the hierarchical structure of directories to represent cluster nesting, Lace has a notion of subcluster through which you can define the logical structure of the cluster hierarchy, regardless of the clusters’ physical locations in the file system.

虽然缺省的您可以使用目录的层次结构来表示集群嵌套,但是Lace有一个子集群的概念,通过它您能够定义集群层次的逻辑结构而不管集群在文件系统中的物理位置。

The directories listed in the cluster clause may contain files of all kinds. To determine the universe, the system assembly process will need to know which ones of these files may contain class texts. A simple convention is to require the text of any class of name NAME to be stored in a file of name name.e (lower case). Let us assume this convention (which can easily be extended for more flexibility) for the rest of this discussion. Then the universe is the set of files having names of the form name.e in the list of directories appearing in the cluster clause.

cluster子句中所列出的目录可以包含所有种类的文件。要决定全域,系统装配过程需要知道哪一个文件包含类代码。一个简单的约定是要求有着代码的任何类的名字NAME存储在命名为name.e(小写字母)的文件中。让我们在余下的讨论中也采用这个约定(这个约定能够很容易地为了更多的灵活性而扩展)。那么,全域是在cluster子句中的目录列表中有着name.e形式名字的文件集。

The root clause of Lace serves to designate the root class of the system. Here the root class is GRAPHICS and, as indicated in parentheses, it appears in the painting_application cluster. If there is only one class called GRAPHICS in the universe, it is not necessary to specify the cluster.

Lace的root子句用于指定系统的根类。这里的根类是GRAPHICS,并在圆括号内指明它出现painting_application集群中。如果在全域内只有一个类叫做GRAPHICS,那么没必要指定集群。

Assume that you start a language processing tool, for example a compiler, to process the system described by the above Ace. Assume further that none of the classes in the system has been compiled yet. The compiler finds the text of the root class, GRAPHICS, in the file graphics.e of the cluster painting_application; that file appears in the directory /user /application. By analyzing the text of class GRAPHICS, the compiler will find the names of the classes needed by GRAPHICS and will look for files with the corresponding .e names in the three cluster directories. It will then apply the same search to the classes needed by these new classes, repeating the process until it has located all the classes needed directly or indirectly by the root.

假定您着手一个语言处理工具处理上面Ace所描述的系统,比如一个编译器。更进一步地假定所有的类还没有被编译。编译器在集群painting_application的文件graphics.e中查找根类GRAPHICS的代码;此文件出现在/user /application目录中。经过分析类GRAPHICS的代码,编译器将找到GRAPHICS所需要的类的名字,并且在三个集群目录中寻找有着.e后缀的文件。接着,它将在这些随后发现的类中运用同样的查询查找所需要的类,重复这个过程直到找到所有根类直接或间接需要的类。

An important property of this process is that it should be automatic. As a software developer, you should not have to write lists of dependencies between modules (known as “Make files”), or to indicate in each file the names of the files that will be needed for its compilation (through what is known in C and C++ as “Include directives”). Not only is it tedious to have to create and maintain such dependency information manually; this process also raises the possibility of errors when the software evolves. All that the Ace requires you to provide is the information that no tool can find by itself: the name of the root class, and the list of locations in the file system where needed classes — what earlier was called the class set of the system — may appear.

此过程中的一个重要属性是它应该是动化的(automatic。作为一个软件开发者,您不必写出模块之间的关联名单(“Make files”),也不必在每一个文件中指明编译过程所需要的文件的名字(通过在C和C++中所熟知的“包括指令Include”)。不但手工产生并维护这样的关联信息令人厌烦;而且当软件演变时,这个过程也会增加错误的可能性。Ace要求您提供的是工具不可能单独发现的信息:根类的名字,和在文件系统中所需要的类——早期称为系统的类集(class set——的地点名单。

To simplify the work of developers further, a good compiler will, when called in a directory where no Ace is present, construct a template Ace whose cluster clause includes the basic libraries (kernel, fundamental data structures and algorithms, graphics etc.) and the current directory, so that you will only have to fill in the name of the system and of its root class, avoiding the need to remember the syntax of Lace.

要进一步地简化开发者的工作,当调用一个没有Ace存在的目录时,一个优秀的编译器需要构建一个模板Ace,其cluster子句包含基本的库(内核,基本的数据结构和算法,图形等等)和当前的目录,这样一来,您将只需要填入系统和根类的名字,而不需要去记住Lace的语法。

The end result of the compilation process is an executable file, whose name is the one given after system in the Ace — painting in the example.

编译过程的最终结果是一个可执行文件,它的名字是在Ace中system之后给定的名字——在例子中是painting

The Lace language includes a few other simple constructs, used to control the actions of language processing tools, in particular compiler options and assertion monitoring levels. We will encounter some of them as we explore further O-O techniques. Lace, as noted, also supports the notion of logical subcluster, so that you can use it to describe complex system structures, including the notions of subsystem and multi-level libraries.

Lace语言包括了一些其它的简单结构,用于在特殊的编译器选项和断言监控层次中控制语言处理工具的动作。当我们深入探索OO技术时将会遇到其中一些。Lace,也支持逻辑子集群的概念,因此您能使用它描述复杂的系统结构,包括子系统和多重库的概念。

Using a system description language such as Lace, separate from the development language, allows classes to remain independent from the system or systems in which they intervene. Classes are software components, similar to chips in electronic design; a system is one particular assembly of classes, similar to a board or a computer made by assembling a certain set of chips.

使用象Lace这样从开发语言中分离出来的系统说明语言,允许类从系统或是调用它们的系统中保持独立性。类是软件组件,相似与电子设计中的芯片;一个系统是类的一个特殊集合,相似与主板或是由芯片组所装配成的计算机。

Printing your name

打印您的名字

Reusable software components are great, but sometimes all you want to do is just a simple task, such as printing a string. You may have been wondering how to write a “program” that will do it. Having introduced the notion of system, we can answer this burning question. (Some people tend to be nervous about the whole approach until they see how to do this, hence this little digression.)

可复用的软件组件是强大的,但有时您想要做的只是一项简单的任务,例如打印字符串。您也许想知道如何写一个能完成任务的“程序”。凭借介绍过的系统概念,我们可以回答这个急切的问题。(一些人对于整体方法于总感到有些紧张,一直到他们了解了如何实现,因此这有些离题。)

The following little class has a procedure which will print a string:

下面的这个类有着一个打印字符串的过程:

class SIMPLE creation

make

feature

make is

-- Print an example string.

do

print_line ("Hello Sarah!")

end

end

The procedure print_line can take an argument of any type; it prints a default representation of the corresponding object, here a string, on a line. Also available is print which does not go to a new line after printing. Both procedures are available to all classes, coming from a universal ancestor, GENERAL, as explained in a later chapter.

过程print_line能够获取一个任意类型的参数;它在一行之内打印一个相应对象的缺省表示,这里是字符串。print也一样有效,它不能在打印之后换行。这两个过程都对所有的类有效,它们派生于一个共同的祖先GENERAL,这在后面解释。

To obtain a system that will print the given string, do the following:

要获得一个能打印给定字符串的系统,要做下列这些:

E1 • Put the above class text in a file called simple.e in some directory.

E1·把上面的类代码放入某个目录下的一个simple.e文件中。

E2 • Start the compiler.

E2·编译。

E3 • If you have not provided an Ace, you will be prompted to edit a new one, automatically generated from a template; just fill in the name of the root class, SIMPLE, the name of the system — say my_ first — and the cluster directory.

E3·如果您没有提供一个Ace,模板将自动生成一个新的并提示您编辑它;只需要填入根类的名字SIMPLE,系统的名字my_ first,和集群的路径。

E4 • Exit from the editor; the compiler will assemble the system and produce an executable file called my_ first.

E4·关闭编辑器;编译器将装配系统,并生成一个可执行文件my_ first

E5 • Execute the result. On platforms such as Unix with a notion of command-line execution a command will have been generated, of name my_ first; simply type that name. On graphical platforms such as Windows and OS/2, a new icon will have appeared, labeled my_ first; just double-click on that icon.

E5·执行结果。在有着命令行执行工具如Unix的平台上会生成命令my_ first;简单地键入这个名字。在如Windows和OS/2的图形平台上,会出现一个新的带有my_ first标签的图标;双击图标。

The result of the last step will be, as desired, to print on your console the message

如所期望的那样,最后一个步骤将在控制台上打印出消息

Hello Sarah!

Structure and order: the software developer as arsonist

结构和顺序:和纵火犯一样的软件开发者

We now have an overall picture of the software construction process in the object-oriented method — assembling classes into systems. We also know how to reconstruct the chain of events that will lead to the execution of a particular operation. Assume this operation is

[A]

x.g (u, v, ¼)

appearing in the text of a routine r of a class C, of which we assume x to be an attribute. How does it ever get executed? Let us recapitulate. You must have included C in a system, and assembled that system with the help of an appropriate Ace. Then you must have started an execution of that system by creating an instance of its root class. The root’s creation procedure must have executed one or more operations which, directly or indirectly, caused the creation of an instance C_OBJ of C, and the execution of a call of the form

[B]

a.r (¼)

where a was at the time attached to C_OBJ. Then the call shown as [A] will execute g, with the arguments given, using as target the object attached to the x field of C_OBJ.

对于在面向对象方法上的软件构造过程——把类装配成系统,我们现在有了一个全面的印象。如果一个事件链将导致一个特别操作的执行,我们也知道了如何重新构造它。假设这个操作是

[A]

x.g (u, v, ¼)

出现在类C的一个例程r的代码中,其中我们假定x是一个属性。它究竟如何执行呢?让我们概括一下。您必须在系统中包含C,并且在一个正确的Ace帮助下装配系统。接着,您应当创建根类的一个实例来开始系统的执行。根的创建过程一定要执行一个或多个操作,直接或间接地引发C的一个实例C_OBJ的创建,并且执行下面形式的调用

[B]

a.r (¼)

这里的a附属在C_OBJ上。然后,在[A]中的调用将传递指定的参数并执行g,使用赋在C_OBJ的字段x上的目标对象。

So by now we know (as well we should) how to find out the exact sequence of events that will occur during the execution of a system. But this assumes we look at the entire system. In general we will not be able, just by examining the text of a given class, to determine the order in which clients will call its various routines. The only ordering property that is immediately visible is the order in which a given routine executes the instructions of its body.

那么现在我们知道(我们应该)如何找出将发生在系统执行过程中的事件的确切顺序。但是这假设我们考虑整个系统。一般来说只通过审查一个特定类的代码,我们往往不能确定客户端调用其各种例程的顺序。立刻可见的唯一的顺序属性是一个给定例程执行它的代码的指令的顺序。

Even at the system level, the structure is so decentralized that the task of predicting the precise order of operations, although possible in principle, is often difficult. More importantly, it is usually not very interesting. Remember that we treat the root class as a somewhat superficial property of the system — a particular choice, made late in the development process, of how we are going to combine a set of individual components and schedule their available operations.

甚至在系统层次上,结构如此分散以至于预先设置操作的精确顺序往往也比较困难,虽然原则上这是有可能的。更加重要地是,它通常枯燥乏味。切记我们把根类看成是系统的一个比较不太重要的属性——一个在开发后期所做的特别的选择,其中我们将结合一套单独的组件并预定它们有效的操作。

This downplaying of ordering constraints is part of object technology’s constant push for decentralization in system architectures. The emphasis is not on “the” execution of “the” program (as in Pascal or C programming and many design methods) but on the services provided by a set of classes through their features. The order in which the services will be exercised, during the execution of a particular system built from these classes, is a secondary property.

对顺序约束的忽视是对象技术的永远不变的一部分,这推动了系统架构的分散性。 重点不在程序的执行上(如同在Pascal或C程序中和许多的设计方法中一样)而在类集通过它们的属性所提供的服务上。在由这些类所建立的一个特殊系统的执行期间,在那些即将行使的服务中的顺序是一个次要属性。

The method goes in fact further by prescribing that even if you know the order of execution you should not base any serious system design decision on it. The reason for this rule was explored in earlier chapters: it is a consequence of the concern for extendibility and reusability. It is much easier to add or change services in a decentralized structure than to change the order of operations if that order was one of the properties used to build the architecture. This reluctance of the object-oriented method to consider the order of operations as a fundamental property of software systems — what an earlier discussion called the shopping list approach — is one of its major differences with most of the other popular software design methods.

即使您知道 执行的顺序,您也不应该据此决定任何重要的系统设计,通过这样的规定,方法事实上更进了一步。这个规则的原因在之前的章节中讨论过了:它是考虑扩充性和复用性的结果。如果一个顺序是用于生成架构的属性之一,那么在一个分散结构中增加或改变服务要比改变运算顺序更加容易。这种面向对象方法并不考虑把运算顺序作为软件系统的一个基本属性——在之前的讨论中称作购物清单方法——是和大多数其它流行的软件设计方法的主要区别之一。

These observations once again evoke the picture of the software developer as firework expert or perhaps arsonist. He prepares a giant conflagration, making sure that all the needed components are ready for assembly and all the needed connections present. He then lights up a match and watches the blaze. But if the structure has been properly set up and every component is properly attached to its neighbors, there is no need to follow or even try to predict the exact sequence of lightings; it suffices to know that every part that must burn will burn, and will not do so before its time has come.

这些结果再一次使人想到了把软件开发者当做烟花专家或是纵火犯的景象。他准备着一个大型的火灾,确信所有需要的部分已经装配完毕并连接好了。然后他点燃了火柴并观看着火焰。但是如果结构已经正确地设定好,并且每个部分都正确地与周边相连,那么没有什么需要注意的甚至不必猜想点火的确切后果;对于要知道必须要烧的每个部分将会被点燃,未到时间不会触发,这已经足够了。

7.10 DISCUSSION

7.10 讨论

As a conclusion to this chapter, let us consider the rationale behind some of the decisions made in the design of the method and notation, exploring along the way a few alternative paths. Similar discussion sections will appear at the end of most chapters introducing new constructs; their aim is to spur the reader’s own thinking by presenting a candid, uncensored view of a few delicate issues.

作为本章的结论,让我们考虑在方法和符号的设计中做出的某些决定背后的理论基础,并探索几个供选择的方法。相似的讨论部分将出现在大多数介绍新构造的章节的结尾;它们的目的是通过对几个微妙的问题提出一个公正公开的看法来激励读者自己思考。

Form of declarations

声明的形式

To hone our critical skills on something that is not too life-threatening, let us start with a syntactical property. One point worth noting is the notation for feature declarations. For routines, there are none of the keywords procedure or function such as they appear in many languages; the form of a feature determines whether it is an attribute, a procedure or a function. The beginning of a feature declaration is just the feature name, say

f ¼

要磨练我们在不太重要的事情上的判断技能,让我们以一个语法属性开始。 一点值得注意是特性声明的符号。对于例程,就如在许多语言中出现的那样并没有关键字procedure(过程)function(函数);一个特性的形式决定了它是否是一个属性,一个过程或是一个函数。特性声明的开始只是特性的名字f ¼

When you have read this, you must still keep all possibilities open. If a list of arguments comes next, as in

g (a1: A; b1: B; ¼) ¼

then you know g is a routine; it could still be either a function or a procedure. Next a type may come:

f: T ¼

g (a1: A; b1: B; ¼): T ¼

当您读到这里的时候,您必须要考虑所有的可能性。如果后面有一系列的参数,如

g (a1: A; b1: B; ¼) ¼

那么您要知道g是一个例程;它还可能是一个函数或过程。下面要讨论的一个类型是:

f: T ¼

g (a1: A; b1: B; ¼): T ¼

In the first example, f can still be either an attribute or a function without arguments; in the second, however, the suspense stops, as g can only be a function. Coming back to f, the ambiguity will be resolved by what appears after T: if nothing, f is an attribute, as in

my_ file: FILE

在第一个举例f: T ¼中,f依然能够是一个没有参数的属性或函数;然而,在第二个例子g (a1: A; b1: B; ¼): T ¼中,毫无疑问的是g只能是一个函数。回到f,在T之后出现的内容将决定类型:如果什么也没有,f是一个属性,如my_ file: FILE

But if an is is present, followed by a routine body (do or the variants once and external to be seen later), as in

f: T is

-- ¼

do ¼ end

f is a function. Yet another variant is:

f: T is some_value

which defines f as a constant attribute of value some_value.

但是,如果有一个is,随后是一个例程体(do或是变体onceexternal,后面会了解),如

f: T is

-- ¼

do ¼ end

那么,f是一个函数。另外一个变体是:

f: T is some_value

这里把f定义成有着some_value值的常量属性constant attribute)。

The syntax is designed to allow easy recognition of the various kinds of feature, while emphasizing the fundamental similarities. The very notion of feature, covering routines as well as attributes, is in line with the Uniform Access principle — the goal of providing clients with abstract facilities and downplaying their representation differences. The similarity between feature declarations follows from the same ideas.

当强调基本相似性的时候,会把语法设计成易于辨认各种特性的形式。包括例程和属性,特性的真正概念是根据统一存取原则——目标是给客户提供抽象便利性和降低它们的表示差异。在特性声明之间的相似性是根据同样的思想而来。

Attributes vs. functions

属性对函数

Let us explore further the consequences of the Uniform Access principle and of grouping attributes and routines under a common heading — features.

让我们进一步探索统一存取原则的结果,并在一个共同的标题——特性——之下对属性和例程分组。

The principle stated that clients of a module should be able to use any service provided by the module in a uniform way, regardless of how the service is implemented — through storage or through computation. Here the services are the features of the class; what is meaningful for clients is the availability of certain features and their properties. Whether a given feature is implemented by storing appropriate data or by computing the result on demand is, for most purposes, irrelevant.

原则阐明了一个模块的客户应该能用一个统一的方式使用模块提供的所有服务,而不管服务是怎样被实现的——通过存贮或是通过计算。这里的服务是类的特性;对客户而言有意义的是某些特性和其属性的有效性。在大多数的场合下,一个给定的特性是否是通过存放适当的数据得到或是在要求时通过计算而得到结果并不相关。

Assume for example a class PERSON containing a feature age of type INTEGER, with no arguments. If the author of a client class writes the expression

Isabelle.age

the only important information is that age will return an integer, the age field of an instance of PERSON attached, at run-time, to the entity Isabelle. Internally, age may be either an attribute, stored with each object, or a function, computed by subtracting the value of a birth_date attribute from the current year. But the author of the client class does not need to know which one of these solutions was chosen by the author of PERSON.

例如,假设一个类PERSON包含了类型INTEGER的一个特性age,它不带参数。如果客户类的作者写下表达式Isabelle.age,唯一重要的信息是age将返回一个整数,在运行的时候,PERSON实例的年龄字段附属在实体Isabelle上。在内部,age可以是一个属性,存储在每一个对象中,或是一个函数,从当前年份减去一个birth_date属性的值中计算得到。但是,客户类的作者并不需要知道PERSON的作者选择了哪一个方案。

The notation for accessing an attribute, then, is the same as for calling a routine; and the notations for declaring these two kinds of feature are as similar as conceptually possible. Then if the author of a supplier class reverses an implementation decision (implementing as a function a feature that was initially an attribute, or conversely) clients will not be affected; they will require neither change, possibly not even recompilation.

那么,访问accessing)一个属性的符号和调用一个例程是一样的;并且,声明(declaring这二种属性的符号和概念上的可能性也相似。因而,如果类的供应者推翻实现的决定(把最初是属性的特性实现成一个函数,或者相反),客户不应该受到影响;它们既不会要求改变,甚至也不可能要求重新编译。

The contrast between the supplier’s and client’s view of the features of a module was apparent in the two figures which helped introduce the notion of feature earlier in this chapter. The first used as its primary criterion the distinction between routines and attributes, reflecting the internal (implementation) view, which is also the supplier’s view. In the second figure, the primary distinction was between commands and queries, the latter further subdivided into queries with and without arguments. This is the external view — the client’s view.

在本章的前面有二幅介绍特性概念的图,供应者和客户对模块特性的观点之间的对比在这两幅图中很明显。把例程和属性之间的区别作为首要标准而使用的第一幅图反映了内部(实现)观点,也是供应者的观点。在第二幅图中,主要区别是命令和查询之间的,后者又进一步被细分为带有参数和没有参数的查询。这是外部观点——客户的观点。

The decision to treat attributes and functions without arguments as equivalent for clients has two important consequences, which later chapters will develop:

对客户来说,这种把属性和没有参数的函数认为是等同的决定有着两个重要的后果,随后的章节中将会介绍:

• The first consequence affects software documentation. The standard client documentation for a class, known as the short form of the class, will be devised so as not to reveal whether a given feature is an attribute or a function (in cases for which it could be either).

·第一个后果影响了软件的文档。类的标准客户文档,即大家所知的类的简易格式short form),将被设计成不再揭示一个给定的特性是否是一个属性或是一个函数(在这种情况下,可能是任何的一种)。

• The second consequence affects inheritance, the major technique for adapting software components to new circumstances without disrupting existing software. If a certain class introduces a feature as a function without arguments, descendant classes will be permitted to redefine the feature as an attribute, substituting memory for computation.

·第二种后果影响了继承,这是使软件元素适应新的环境而不破坏现存软件的主要技术。如果某个类定义了一个特性作为一个没有参数的函数,那么后代类将被允许重定义redefine)这个特性为属性,用内存存储的方式取代计算方式。

Exporting attributes

输出属性

A consequence of the preceding observations is that classes may export attributes. For example, class POINT, in the cartesian implementation introduced earlier, has attributes x and y, and exports them to clients in exactly the same way as the functions rho and theta. To obtain the value of an attribute for a certain object, you simply use feature call notation, as in my_point.x or my_ point.theta.

上述结果的一个后果是类可以输出属性。比如,在先前介绍过的笛卡尔实现中,类POINT有属性xy,输出它们到客户端,并用同样的方法输出函数rhotheta。要获得某个对象的一个属性值,您只要简单地使用特性调用符号,如my_point.x或者my_ point.theta

This ability to export attributes differs from the conventions that exist in many O-O languages. Typical of these is Smalltalk, where only routines (called “methods”) may be exported by a class; attributes (“instance variables”) are not directly accessible to clients.

输出属性的能力在许多OO语言中的约定并不相同。其中典型的是Smalltalk,只有例程(叫做“函数”)可以被类输出;属性(“实例变量”)不可以直接被客户使用。

A consequence of the Smalltalk approach is that if you want to obtain the effect of exporting an attribute you have to write a small exported function whose only purpose is to return the attribute’s value. So in the POINT example we could call the attributes internal_x and internal_y, and write the class as follows (using the notation of this book rather than the exact Smalltalk syntax, and calling the functions abscissa and ordinate rather than x and y to avoid any confusion):

Smalltalk方法的后果是如果您想要获得输出一个属性的效果,您必须要写一个小的输出函数,其作用只是返回属性的值。因此,在POINT的例子中,我们能调用属性internal_xinternal_y,并且编写如下的类(使用本书的符号而不是Smalltalk的语法,调用函数abscissaordinate而不是xy,以避免任何的混淆):

class POINT feature -- Public features:

abscissa: REAL is

-- Horizontal coordinate

do Result := internal_x end

ordinate: REAL is

-- Vertical coordinate

do Result := internal_y end

¼ Other features as in the earlier version ¼

feature {NONE} -- Features inaccessible to clients:

internal_x, internal_y: REAL

end

This approach has two drawbacks:

这种方法有着两个缺点:

• It forces authors of supplier classes to write many small functions such as abscissa and ordinate. Although in practice such functions will be short (since the syntax of Smalltalk is terse, and makes it possible to give the same name to an attribute and a function, avoiding the need to devise special attribute names such as internal_x and internal_y), writing them is still a waste of effort on the part of the class author, and reading them is a useless distraction for the class reader.

·它迫使提供类的作者编写许多的小型函数,如abscissaordinate。虽然在实践中这样的函数很短小(由于Smalltalk语法比较简洁,并且可以对属性和函数赋予同样的名字,这避免了象internal_xinternal_y这种发明特别的属性名字的需求),但是编写它们对于类作者来说做了些无用功,对于类读者来说分散了注意力。

• The method entails a significant performance penalty: every access to a field of an object now requires a routine call. No wonder object technology has developed a reputation for inefficiency in some circles. (It is possible to develop an optimizing compiler which will expand calls to abscissa-style functions in-line, but then what is the role of these functions?)

·方法蒙受了显著的性能损失:每次访问一个对象的字段都需要一个例程调用。对象技术在某些范围之内有着低效的名声,这不足为奇。(可以开发一个最优化的编译器,这个编译器将把调用扩展成abscissa式样的内联函数,不过这些函数的作用又是什么呢?)

The technique discussed in this chapter seems preferable. It avoids the need for cluttering class texts with numerous little extra functions, and instead lets the class designers export attributes as needed. Contrary to what a superficial examination might suggest, this policy does not violate information hiding; it is in fact a direct implementation of this principle and of the associated principle of Uniform Access. To satisfy these requirements it suffices to make sure that attributes, as seen by clients, are indistinguishable from functions without arguments, and that they have the same properties for inheritance and class documentation.

在本章中所谈论的技术似乎更可取一些。对有着许多小型的额外函数之类的凌乱的类代码,它避免了这样的情况取而代之的是让类设计者输出需要的属性。与那些肤浅的研究所建议的相反,这项政策并不违犯信息隐藏;它实际上是此项原则和统一存取的有关原则的直接实现。要满足这些需求,它需要确保那些客户端能看见的属性和不带参数的函数相同,同时确保继承和类文档有着同样的属性。

This technique reconciles the goals of Uniform Access (essential for the clients), ease of writing class texts (essential for the suppliers), and efficiency (essential for everyone).

这个技术协调了统一存取(客户端的要素),易于编写类代码(提供者的要素),和效率(两者的要素)的目标。

The client’s privileges on an attribute

在属性上的客户端特权

Exporting an attribute, using the techniques just discussed, allows clients to access the value of an attribute for a certain object, as in my_point.x. It does not allow clients to modify that value. You may not assign to an attribute; the assignment

my_point.x := 3.7

is syntactically illegal. The syntax rule is simple: a.attrib, if attrib is an attribute (or for that matter a function) is an expression, not an entity, so you cannot assign to it, any more than you can assign to the expression a + b.

使用刚刚讨论的技术输出一个属性,允许客户端对一个特定的对象访问一个属性的值,就象my_point.x。它不允许客户端修改这个值。您不可以对一个属性赋值;象my_point.x := 3.7这样的赋值动作在语法上是非法的。语法规则很简单:a.attrib,如果attrib是一个属性(或者是一个函数)或是一个表达式,而不是一个实体,因此,您不能赋值给它,更多的是您能赋值给表达式a + b

[注] 这段英文无法理解。可能是电子文档有所疏漏。我现在手边没有纸本书籍,所以如果您能核对,请告之,谢谢。

To make attrib accessible in modification mode, you must write and export an appropriate procedure, of the form:

要使attrib在修改模式中可以访问,您必须编写并输出一个正确的过程:

set_attrib (v: G) is

-- Set to v the value of attrib.

do

attrib := v

end

Instead of this convention, one could imagine a syntax for specifying access rights, such as

class C feature [AM]

¼

feature [A] {D, E}

¼

where A would mean access and M modification. (Specifying A could be optional: if you export something you must at least allow clients to access it in read mode). This would avoid the frequent need for writing procedures similar to set_attrib.

不用这种约定,我们可以想象另一种指定访问权限的语法,如:

class C feature [AM]

¼

feature [A] {D, E}

¼

这里的A代表访问,M代表修改。(A的指定是可选的:如果您有输出,那么您必须至少允许客户在只读的模式下访问)。这种方式避免了不断地编写类似于set_attrib这种过程的需要。

Besides not justifying the extra language complication, this solution is not flexible enough. In many cases, you will want to export specific ways of modifying an attribute. For example, the following class exports a counter, and the right to modify it not arbitrarily but only by increments of +1 or –1:

除了不能证明额外的语言复杂性之外,这种解决方案并不十分灵活。在许多情况下,您想要输出一些特殊的方法来修改属性。例如,下面的类输出一个记数器,您并不能任意的直接修改它,而只能是加一或减一:

class COUNTING feature

counter: INTEGER

increment is

-- Increment counter

do

count := count + 1

end

decrement is

-- Decrement counter

do

count := count — 1

end

end

Similarly, class POINT as developed in this chapter does not let its clients set the x and y of a point directly; clients can change the values of these attributes, but only by going through the specific mechanisms that have been exported for that purpose, procedures translate and scale.

同样的,在本章中所用的类POINT不允许其客户直接设置点的xy;客户能够改变这些属性的值,但是只能通过专门为此目的而输出的特别机制,过程translatescale

When we study assertions we will see another fundamental reason why it is inappropriate to let clients perform direct assignments of the a.attrib := some_value form: not all some_value are acceptable. You may define a procedure such as

set_polygon_size (new_size: INTEGER) is

-- Set the number of polygon vertices to new_size.

require

new_size >= 3

do

size := new_size

end

requiring any actual argument to be 3 or more. Direct assignments would make it impossible to enforce this constraint; a call could then produce an incorrect object.

当我们学习断言的时候,我们将会了解到另外一种基本的原因,为什么让客户直接以a.attrib := some_value这样的形式进行直接赋值是不恰当的:不是所有的some_value都可以访问。您可以定义一个如下的过程

set_polygon_size (new_size: INTEGER) is

-- Set the number of polygon vertices to new_size.

require

new_size >= 3

do

size := new_size

end

要求任何实参必须大于等于3。直接赋值将不可能执行这样的约束;如此一个调用就会产生一个错误的对象。

These considerations show that a class writer must have at his disposal, for each attribute, five possible levels for granting access privileges to clients:

这些考虑结果显示了一个类的作者在其自己的处理范围之内对于每一个属性必须有5种可能的级别来赋予客户访问特权。

Level 0 is total protection: clients have no way of accessing the attribute. At level 1 and above, you make the attribute available for access, but at level 1 you do not grant any modification right. At level 2, you let clients modify the attribute through specific algorithms. At level 3, you let them set the value, but only if it satisfies certain constraints, as in the polygon size example. Level 4 removes the constraints.

第0级是完全保护:客户不能够访问属性。在第1级及以上,您开放属性访问,但在第1级您不授予任何修改的权利。在第2级,您让客户通过指定的算法修改属性。在第3级,您让他们设置值,但是只有当它满足某些限制的时候才可以赋值,和在多角形尺寸的例子中一样。第4级没有任何限制。

The solution described in this chapter is a consequence of this analysis. Exporting an attribute only gives clients access permission (level 1); permission to modify is specified by writing and exporting appropriate procedures, which give clients restricted rights as in the counter and point examples (level 2), direct modification rights under some constraints (3) or unrestricted rights (4).

在本章描述的解决方案是此分析的结果。输出一个属性只给客户端访问权限(第1级);通过编写和输出适当的过程指定修改许可,如在计数器和点的例子中(第2级)一样过程赋予客户有限的权力,在某些约束(3)或无限制的权利(4)之下直接修改的权限。

This solution is an improvement over the ones commonly found in O-O languages:

这个解决方案比通常在OO语言中所能发现的要好:

• In Smalltalk, as noted, you have to write special encapsulation functions, such as the earlier abscissa and ordinate, just to let clients access an attribute at level 1; this may mean both extra work for the developer and a performance overhead. Here there is no need to write routines for attribute access; only for attribute modifications (levels 2 and above) do we require writing a routine, since it is conceptually necessary for the reasons just seen.

·在Smalltalk中,就象在abscissaordinate一样,您不得不编写特别的封装函数,仅仅是让客户在第1级中访问一个属性;这也许意味着开发者的额外工作和性能开销。这里并不需要对属性访问编写例程;只是对于属性修改(第2级和以上)我们才需要例程,这是由于概念上的需要,其原因我们已经看到了。

• C++ and Java are the other extreme: if you export an attribute then it is up for grabs at level 4: clients can set it through direct assignments in the my_point.x := 3.7 style as well as access its value. The only way to achieve level 2 (not 3 in the absence of an O-O assertion mechanism in these languages) is to hide the attribute altogether, and then write exported routines, both procedures for modification (levels 2 or 4) and functions for access (level 1). But then you get the same behavior as with the Smalltalk approach.

·C++和Java是另一种极端:如果我们输出一个属性,那么在第4级是很容易的:通过象my_point.x := 3.7这样直接赋值客户能够设置这个属性,也能访问它的值。在第2级要完成这样的目标唯一的方法是完全隐藏属性,并对修改过程(第2或第4级)和访问函数(第1级)两者都需要编写输出例程。但是您用Smalltalk方法的话您能得到相同的结果。

This discussion of a fairly specific language trait illustrates two of the general principles of language design: do not needlessly bother the programmer; know when to stop introducing new language constructs at the point of diminishing returns.

本次关于一个相当具体的语言特征的讨论说明了二个语言设计上的一般原则: 不要无端地干扰程序员;知道在接近收益递减(饱和)的时候去停止引进新的语言构造。

Optimizing calls

最优化调用

At levels 2 and 3 of the preceding discussion, the use of explicit procedure calls such as my_polygon.set_size (5) to change an attribute value is inevitable. At level 4, one could fear the effect on performance of using the set_attrib-style. The compiler, however, can generate the same code for my_point.set_x (3.7) as it would for my_point.x := 3.7 had this last phrasing been legal.

在上述讨论的第2和第3级中,使用象my_polygon.set_size (5)这样的显式过程的调用去改变一个属性的值是必然的。在第4级中,一个可能会影响性能的是set_attrib式样的使用。然而对于my_point.set_x (3.7),编译器能够和对于my_point.x := 3.7一样最终生成相同的代码。

ISE’s compiler achieves this through a general in-line expansion mechanism, which eliminates certain routine calls by inserting the routine body directly, with appropriate argument substitutions, into the caller’s code.

ISE的编译器通过一个通用的内联展开机制来达到这个目的,这个机制把带有适当参数的例程体的代码直接插入到调用程序的代码中来消除某些例程的调用。

In-line expansion is indeed one of the transformations that we may expect from an optimizing compiler for an object-oriented language. The modular style of development fostered by object technology produces many small routines. It would be unacceptable for developers to have to worry about the effect of the corresponding calls on performance. They should just use the clearest and most robust architecture they can devise, according to the modularity principles studied in this book, and expect the compiler to get rid of any calls which may be relevant to the design but not necessary for the execution.

内联展开(in-line expansion)的确是我们所期望的面向对象语言的最优化编译器的一项变革。对象技术所促进的开发模块风格导致了许多小型的例程。开发者不能接受的是不得不担心相关调用在性能上的影响。他们应该只是根据本书所学到的模块化原则使用他们所能构想到的最清晰和最健壮的架构,同时期待编译器去掉任何也许和设计相关但和执行无关的调用。

In some programming languages, notably Ada and C++, developers specify what routines they want expanded in-line. I find it preferable to treat this task as an automatic optimization, for several reasons:

在一些程序设计语言中,特别是Ada和C++,开发者可以指定什么样的例程需要内联展开。我发现基于下列这些原因把这项工作设成一个自动优化过程更可取:

• It is not always correct to expand a call in-line; since the compiler must, for correctness, check that the optimization applies, it may just as well spare developers the trouble of requesting it in the first place.

·对一个调用展开内联并不总是正确的;由于编译器为了正确性必须检查最优化的申请,它正好可以省去开发者首先检查它的不便。

• With changes in the software, in particular through inheritance, a routine which was inlinable may become non-inlinable. A software tool is better than a human at detecting such cases.

·随着软件中的变化,特别是通过继承,一个能够内联的例程也许会变成不需要内联。一个软件工具要比人更容易检测到这种情况。

• On a large system, compilers will always be more effective. They are better equipped to apply the proper heuristics — based on routine size and number of calls — to decide what routines should be inlined. This is again especially critical as the software changes; we cannot expect a human to track the evolution of every piece.

·在一个大型的系统中,编译器总是更有效率。它们能更好的应用正确的拭探法——基于例程的大小和调用的次数——来决定什么样的例程应该被内联。这再一次证实了软件变化特别地危险;我们不能期待人能够跟踪每一个细小的演变。

• Software developers have better things to do with their time.

·软件开发者在他们有限的时间内有着更重要的事情要做。

The modern software engineering view is that such tedious, automatable and delicate optimizations should be handled by software tools, not people. The policy of leaving them to the responsibility of developers is one of the principal criticisms that have been leveled at C++ and Ada . We will encounter this debate again in studying two other key mechanisms of object technology: memory management, and dynamic binding.

现代软件工程的观点是应该由软件工具来处理这种繁琐的, 可自动的和精细的优化过程,而不是人。把它们当成开发者责任的政策是对C++和Ada指责中的主要批评之一。在学习对象技术其它的二个关键机制:内存管理和动态绑定的时候,我们将会再遇到这种辩论。

The architectural role of selective exports

选择性输出的架构角色

The selective export facility is not just a convenience; it is essential to object-oriented architecture. It enables a set of conceptually related classes to make some of their features accessible to each other without releasing them to the rest of the world, that is to say, without violating the rule of Information Hiding. It also helps us understand a frequently debated issue: whether we need modules above the level of classes.

选择性输出的设置不仅仅是便利;它对面向对象架构是必要的。它使一套概念上相关的类能够让部分特性彼此间互相访问,而无需公开发布,也就是说,不需要违犯信息隐藏规则。它也帮助我们了解了一个频繁争论的问题: 我们是否需要在类层次之上的模块。

Without selective exports, the only solution (other than renouncing Information Hiding altogether) would be to introduce a new modular structure to group classes. Such super-modules, similar to Ada ’s or Java’s packages, would have their own rules for hiding and exporting. By adding a completely new and partly incompatible module level to the elegant framework defined by classes, they would yield a bigger, hard-to-learn language.

没有选择性输出,唯一的解决方案(除了完全放弃信息隐藏之外)是引进一个新的模块结构对类编组。这样的超级模块,相似与Ada或Java的包(package),具有它们自己的隐藏和输出规则。增加一个全新的和不完全相容的模块层到由类定义的精美的框架中,它们将会产生一个更加巨大的,难以学会的语言。

Rather than using a separate package construct, the super-modules could themselves be classes; this is the approach of Simula, which permits class nesting. It too brings its share of extra complexity, for no clear benefit.

超级模块能使它们自己变成类,而不是使用分开的包结构;这是Simula的方法,其允许类嵌套。它带来了部分的额外复杂性,并没有实际的好处。

We have seen that the simplicity of object technology relies for a good part on the use of a single modular concept, the class; its support for reusability relies on our ability to extract a class from its context, keeping only its logical dependencies. With a super-module concept we run the risk of losing these advantages. In particular, if a class belongs to a package or an enclosing class we will not be able to reuse it by itself; if we want to include it in another super-module we will need either to import the entire original super-module, or to make a copy of the class — not an attractive form of reuse.

我们看到对象技术的简单性依靠对一个单一模块概念——类——的使用而成为一个优秀的实现;它对复用性的支持依靠我们从它的上下文中提取类,仅保留它的逻辑关联的能力。对于超级模块的概念,我们冒着丢失这些好处的危险。特别是如果类属于一个包或是一个外围类,我们不能够复用其本身;如果我们想要在另一个超级模块中包括它,我们将需要输入整个原始的超级模块,或者拷贝类——不再是一个有着吸引力的复用形式。

The need will remain to group classes in structured collections. This will be addressed in a later chapter through the notion of cluster. But the cluster is a management and organizational notion; making it a language construct would jeopardize the simplicity of the object-oriented approach and its support for modularity.

在结构化集合中对类分组依然有着这种需求。在后面的章节中将通过群集概念来描述。但是群集是一个管理和组织性的概念;使它成为一个语言构造将会危害面向对象方法和它所支持的模块化的简单性。

When we want to let a group of classes grant each other special privileges, we do not need a super-module; selective exports, a modest extension to basic information hiding, provide a straightforward solution, allowing classes to retain their status of free-standing software components. This is, in my opinion, a typical case of how a simple, low-tech idea can outperform the heavy artillery of a “powerful” mechanism.

当我们要让一组类互相授予特权的时候,我们不需要超级模块;选择性输出,一个基本信息隐藏的适度引伸,提供了一个直截了当的解决办法,并允许类保留它们的独立软件组件的状态。依我看来,这是一个简单的低科技含量的想法能够胜过一个“强有力”机制的重武器(以少胜多,由繁入简)的典型事例。

Listing imports

输入列表

Each class lists, in the headers of its feature clauses, the features that it makes available to others. Why not, one might ask, also list features obtained from other classes? The encapsulation language Modula-2 indeed provides an import clause.

feature子句的题头中,每一个类列出了对其它类开放的特性。为什么不同时列出从其它类中获得的特性呢?封装语言Modula-2实际上提供了一个import子句。

In a typed approach to O-O software construction, however, such a clause would not serve any purpose other than documentation. To use a feature f from another class C, you must be a client or (through inheritance) a descendant of that class. In the first case, the only one seen so far, this means that every use of f is of the form

a.f

where, since our notation is typed, a must have been declared:

a: C

showing without any ambiguity that f came from the C. In the descendant case the information will be available from the official class documentation, its “flat-short form”.

然而,在一个OO软件构造的输入方法中,这样的一个子句并不比文档有着更好的效果。要使用一个其它类C的特性f,您必须是一个此类的客户或是后代(通过继承)。在第一种情况中,迄今为止唯一知道的是,这意味着每一个f的使用是a.f的形式,这里,由于我们的符号,a必须被声明成a: C,其毫无歧义地显示了fC中得出。在后代的情况中,信息将从正式的类文档中得到。

So there is no need to bother developers with import clauses.

因此,这里没有用输入子句打搅开发者的需要。

There is a need, however, to help developers with import documentation. A good graphical development environment should include mechanisms that enable you, by clicking a button, to see the suppliers and ancestors of a class, and follow the import chain further by exploring their own suppliers and ancestors.

然而,这里却有用输入文档帮助 开发者的需求。一个优秀的图形开发环境应该包括使您能够通过点击按钮看到类的提供者和祖先,并且通过探索它们自己的提供者和祖先进一步跟随输入链的机制。

Denoting the result of a function

指明函数的返回值

An interesting language issue broached earlier in this chapter is how to denote function results. It is worth exploring further although it applies to non-O-O languages as well.

在本章早期提出的一个有趣的语言议题是如何指明函数的返回值。虽然它适用于非OO语言,但也值得进一步地研究。

Consider a function — a value-returning routine. Since the purpose of any call to the function is to compute a certain result and return it to the caller, the question arises of how to denote that result in the text of the function itself, in particular in the instructions which initialize and update the result.

考虑一个函数——一个有返回值的例程。因为所有调用函数的目的是要计算出某个特定的结果并返回给调用者,所以出现的问题是怎样表示那个出现在函数代码中返回值,尤其是在初始化并更新返回值的指令中。

The convention introduced in this chapter uses a special entity, Result, treated as a local entity and initialized to the appropriate default value; the result returned by a call is the final value of Result. Because of the initialization rules, that value is always defined even if the routine body contains no assignment to Result. For example, the function

f: INTEGER is

do

if some_condition then Result := 10 end

end

will return the value 10 if some_condition is satisfied at the time of the call, and 0 (the default initialization value for INTEGER) otherwise.

本章介绍的约定使用了一个特殊的实体,Result,一个局部实体并用适当的缺省值初试化;调用的返回值是Result的最终值。因为初始化规则,返回值一直有着定义,即便例程体没有包含Result的赋值语句。譬如,函数

f: INTEGER is

do

if some_condition then Result := 10 end

end

将返回10如果some_condition在调用的时候满足条件,否则返回0(INTEGER的缺省初始值)。

The technique using Result originated, as far as I know, with the notation developed in this book. (Since the first edition it has found its way into at least one other language, Borland’s Delphi .) Note that it would not work in a language allowing functions to be declared within functions, as the name Result would then be ambiguous. Among the techniques used in earlier languages, the most common are:

具我所知,使用Result的技术起源自本书所开发的符号。(自从第一个版本之后,至少有一个其它的语言采用了这项技术,既Borland公司的Delphi。)注意,在一个允许函数内定义嵌套函数的语言中,这项技术不再有效,这是由于Result命名会产生歧义。在一些使用此技术的早期语言中,最普通的方式如下:

A • Explicit return instructions (C, C++/Java, Ada , Modula-2).

A·显式返回指令(C, C++/Java, Ada , Modula-2)。

B • Treating the function name as a variable (Fortran, Algol 60, Simula, Algol 68, Pascal).

B·把函数名看作一个变量(Fortran, Algol 60, Simula, Algol 68, Pascal)。

Convention A relies on an instruction of the form return e whose execution terminates the current execution of the enclosing function, returning e as the result. This technique has the benefit of clarity, since it makes the returned value stand out clearly from the function text. But it suffers from several drawbacks:

惯例A依靠一个return e形式的指令,指令终止当前外围函数的执行,返回结果e。这项技术的好处是清楚明了,由于它使得返回值在函数代码中引人注目。但是这得忍受着几个缺点:

A1 • Often, the result must in practice be obtained through some computation: an initialization and a few subsequent updates. This means you must introduce and declare an extraneous variable (an entity in the terminology of this chapter) just for the purpose of holding the intermediate results of the computation.

A1·常常,实际上返回值很可能在某个计算过程中得出:一个初始化和几个随后的更新。这意味着您必须引进和定义一个额外变量(在本章的术语中是一个实体),只是为了保持计算的中间结果。

A2 • The technique tends to promote multiple-exit modules, which are contrary to the principles of good program structuring.

A2·此方法倾向于发扬多点退出模块,这与良好的程序结构原则相违背。

A3 • The language definition must specify what will happen if the last instruction executed by a call to the function is not a return. The Ada result in this case is to raise ¼ a run-time exception! (This may be viewed as the ultimate in buck-passing, the language designers having transferred the responsibility for language design issues not just to software developers, but finally to the end-users of the programs developed in the language!)

A3·如果调用函数的最后一条指令执行并不是return,那么语言定义必须指明后果。在这种情况里,Ada是抛出一条运行时异常!(这可以视为推诿责任的极致,语言设计者把语言设计问题的责任不但推给了软件开发者,而且最后推给了用此语言开发的程序的最终用户end-users!)

Note that it is possible to solve the last two problems by treating return not as an instruction, but as a syntactic clause which would be a required part of any function text:

要注意的是有可能解决最后两个问题,只要不把return看成一条指令,而要把它当作一条语法子句,这需要作为函数代码的一部分:

function name (arguments): TYPE is

do

¼

return

expression

end

This solution remains compatible in spirit with the idea of a return instruction while addressing its most serious deficiencies. No common language, however, uses it, and of course it still leaves problem A1 open.

这个解决方案保留了与return指令思想精髓的兼容性,同时致力于它的最大不足。然而,没有当前流行的语言使用它,当然也没有解决A1。

The second common technique, B, treats a function’s name as a variable within the text of the function. The value returned by a call is the final value of that variable. (This avoids introducing a special variable as mentioned under A1.)

第二个通常的技术B,把函数名作为函数代码内的一个变量来对待。一个调用的返回值是那个变量的最终值。(这避免了引进一个在A1中提出的特别变量。)

The above three problems do not arise in this approach. But it raises other difficulties because the same name now ambiguously denotes both a function and a variable. This is particularly confusing in a language allowing recursion, where a function body may use the function’s name to denote a recursive call. Because an occurrence of the function’s name now has two possible meanings, the language must define precise conventions as to when it denotes the variable, and when it denotes a function call. Usually, in the body of a function f, an occurrence of the name f as the target of an assignment (or other contexts implying a value to be modified) denotes the variable, as in

f := x

and an occurrence of f in an expression (or other contexts implying a value to be accessed) denotes a recursive function call, as in

x := f

which is valid only if f has no arguments. But then an assignment of the form

f := f + 1

will be either rejected by the compiler (if f has arguments) or, worse, understood as containing a recursive call whose result gets assigned to f (the variable). The latter interpretation is almost certainly not what the developer had in mind: if f had been a normal variable, the instruction would simply have increased its value by one. Here the assignment will usually cause a non-terminating computation. To obtain the desired effect, the developer will have to introduce an extra variable; this takes us back to problem A1 above and defeats the whole purpose of using technique B.

上面的三个问题都没有出现在这个方法中。但是它引发了其它的麻烦,因为同样的名字同时代表着一个函数和一个变量。在一个允许递归的语言中这尤其让人困惑,一个函数体可以使用函数名来表示一个递归调用。因为现在一个函数名的出现有着两个不同的意义,所以语言必须定义精确的约定来表明什么时候代表着变量,什么时候代表着函数调用。通常,在一个函数f的代码中,作为赋值目标(或者其上下文暗示的一个被修改的值)而出现的名字f代表着变量,如f := x所示。在一个表达式(或者其上下文暗示的一个被访问的值)中出现的代表着一个递归调用,如x := f所示,只有在f没有参数的时候才有效。但是接着,一个f := f + 1形式的赋值语句将会既被编译器拒绝(如果f有参数),更糟的是,也会被理解成包含了一个把结果赋与f(变量)的递归调用。后者的解释当然不是开发者想要的:如果f只是一个普通的变量,那么指令只是简单的把值加1。这里的赋值语句通常引起一个无穷的计算。要获得希望的效果,开发者将不得不引进一个额外的变量;这又把我们带回到了问题A1,同时,使用技术B的全盘计划遭到否决。

The convention introduced in this chapter, relying on the predefined entity Result, avoids the drawbacks of both A and B. An extra advantage, in a language providing for default initialization of all entities including Result, is that it simplifies the writing of functions: if, as often happens, you want the result to be the default value except in specific cases, you can use the scheme

do

if some_condition then Result := “Some specific value” end

end

without worrying about an else clause. The language definition must, of course, specify all default values in an unambiguous and platform-independent way; the next chapter will introduce such conventions for our notation. A final benefit of the Result convention will become clear when we study Design by Contract: we can use Result to express an abstract property of a function’s result, independent of its implementation, in the routine’s postcondition. None of the other conventions would allow us to write

infix "|_" (x: REAL): INTEGER is

-- Integer part of x

do

¼ Implementation omitted ¼

ensure

no_greater: Result <= x

smallest_possible: Result + 1 > x

end

在本章中所介绍的约定依赖于预定义的实体Result,这避免了A和B两者的缺点。在一个对包括Result的所有实体提供缺省初始化的语言中,一个额外的优点是这简化了函数的编写:这经常会发生,如果除了特别的情况下您想要结果有着缺省值,您能够使用方案

do

if some_condition then Result := “Some specific value” end

end

而不需要担心else子句。当然,语言定义必须用一种明确的和平台无关的方法来指明所有的缺省值;下一章将会对于我们的符号引进这样的约定。当我们学习契约设计的时候,Result约定的一个最终的好处将浮出水面:在例程的后置条件中,我们能够使用Result来表达一个函数结果的抽象属性,而独立于它的实现。没有任何其它约定可以允许我们编写

infix "|_" (x: REAL): INTEGER is

-- Integer part of x

do

¼ Implementation omitted ¼

ensure

no_greater: Result <= x

smallest_possible: Result + 1 > x

end

The postcondition is the ensure clause, stating two properties of the result: that it is no greater than the argument; and that adding 1 to it yields a result greater than the argument.

后置条件是ensure子句,声明两个返回值的属性:不大于参数;加1后产生的结果大于参数。

Complement: a precise definition of entities

补充:实体的一个精确定义

It will be useful, while we are considering notational problems, to clarify a notion that has repeatedly been used above, but not yet defined precisely: entities. Rather than a critical concept of object technology, this is simply a technical notion, generalizing the traditional notion of variable; we need a precise definition.

当我们考虑符号问题的时候,澄清一个上面一再使用了的但还没有精确定义的概念还是有用的:实体。这是一个简单的技术概念,从传统的变量概念归纳出,而不是一个至关重要的对象技术观念;我们需要一个精确的定义。

Entities as used in this book cover names that denote run-time values, themselves attached to possible objects. We have now seen all three possible cases:

在本书中用到的实体包括了运行时的值所代表的名字,它们本身附属在可能的对象上。我们现在看到了所有三种可能的情况:

Definition: entity

An entity is one of the following:

E1 • An attribute of a class.

E2 • A routine’s local entity, including the predefined entity Result for a function.

E3 • A formal argument of a routine.

定义:实体

一个实体是下列之一:

E1·一个类的一个属性。

E2·一个例程的局部实体,包含预定义的函数实体Result

E3·一个例程的形式参数。

Case E2 indicates that the entity Result is treated, for all purposes, as a local entity; other local entities are introduced in the local clause. Result and other local entities of a routine are initialized anew each time the routine is called.

E2的情况显示了在所有的场合下实体Result被作为一个局部实体对待;其它的局部实体在local子句中引进。一个例程的Result和其它局部实体在例程每一次调用的时候都被重新初始化。

All entities except formal arguments (E3) are writable, that is to say may appear as the target x of an assignment x := some_value.

除了形参(E3)之外的所有实体都是可写的,也就是说,可以作为赋值语句x := some_value的目标x

7.11 KEY CONCEPTS INTRODUCED IN THIS CHAPTER

7.11摘要

• The fundamental concept of object technology is the notion of class. A class is an abstract data type, partially or fully implemented.

·面向对象技术的基本概念是类的概念。一个类是一个部分或完全实现的抽象数据类型。

• A class may have instances, called objects.

·一个类可以有实例,它们叫做对象。

• Do not confuse objects (dynamic items) with classes (the static description of the properties common to a set of run-time objects).

·不要把对象(动态元素)和类(一组运行时对象的共同属性的静态描述)搞混。

• In a consistent approach to object technology, every object is an instance of a class.

·在一个符合对象技术的方法中,每个对象都是一个类的一个实例。

• The class serves as both a module and a type. The originality and power of the O-O model come in part from the fusion of these two notions.

·类应用于模块和类型两者。OO模型的独创性和力量部分地来自于这两个概念的融合。

• A class is characterized by features, including attributes (representing fields of the instances of the class) and routines (representing computations on these instances). A routine may be a function, which returns a result, or a procedure, which does not.

·类的特点是特性,包括属性(代表类实例的字段)和例程(代表这些实例上的计算)。一个例程也许是一个返回一个结果的函数,或者一个没有结果的过程。

• The basic mechanism of object-oriented computation is feature call. A feature call applies a feature of a class to an instance of that class, possibly with arguments.

·面向对象计算方式的基本机制是特性调用。一个特性调用把类的一个特性应用到类的实例上,这个特性可能有参数。

• Feature call uses either dot notation (for identifier features) or operator notation, prefix or infix (for operator features).

·特性调用使用圆点符号(为标识符特性)或是运算符符号,前缀或是中缀(为运算符特性)。

• Every operation is relative to a “current instance” of a class.

·每一个运算都关系着类的一个“当前实例“。

• For clients of a class (other classes which use its features), an attribute is indistinguishable from a function without arguments, in accordance with the Uniform Access principle.

·对于一个类的客户(使用这个特性的其它类)来说,根据统一存取原则是无法从一个无参数的函数中区分出一个属性的。

• An executable assembly of classes is called a system. A system contains a root class and all the classes which the root needs directly or indirectly (through the client and inheritance relations). To execute the system is to create an instance of the root class and to call a creation procedure on that instance.

·一个可执行的类的程序集称为系统。一个系统包含一个根类和根直接地或间接地需要的所有类(通过客户和继承关系)。执行系统就是创建根类的实例,并调用实例上的过程。

• Systems should have a decentralized architecture. Ordering relations between the operations are inessential to the design.

·系统应该具有一个分散的架构。在运算之间的次序关系对设计来说无关紧要。

• A small system description language, Lace, makes it possible to specify how a system should be assembled. A Lace specification, or Ace, indicates the root class and the set of directories where the system’s clusters reside.

·一个小型系统描述语言,Lace,能够指定一个系统应该如何装配。Lace的规格或者Ace,表明根类和系统群集所在的目录集合。

• The system assembly process should be automatic, with no need for Make files or Include directives.

·系统的装配过程应该是自动的,过程并不需要Make files或者Include指令。

• The Information Hiding mechanism needs flexibility: besides being hidden or generally available, a feature may need to be exported to some clients only; and an attribute may need to be exported for access only, access and restricted modification, or full modification.

·信息隐藏机制需要灵活性: 除了完全掩藏或完全有效以外,一个特性也许需要只对有些客户输出;一个属性也许需要把输出设为只读,访问和有限修改,或者完全修改。

• Exporting an attribute gives clients the right to access it. Modifying it requires calling the appropriate exported procedure.

·输出一个属性并赋予客户权力访问它。修改它的话需要调用适当的输出过程。

• Selective exports are necessary to enable groups of closely related classes to gain special access to each other’s features.

·选择性输出是必要的,它能够对紧密相关的类分组,使得能对彼此的特性获得特别的访问权限。

• There is no need for a super-module construct above classes. Classes should remain independent software components.

·这里不需要建立在类之上的超级模块构造。类应该是保持着独立的软件组件。

• The modular style promoted by object-oriented development leads to many small routines. Inlining, a compiler optimization, removes any potential efficiency consequence. Detecting inlinable calls should be the responsibility of the compiler, not software developers.

·面向对象的发展促进了模块样式,导致了许多小的例程。内联,一个编译器的优化,消除了所有潜在的效率问题。检测内联调用应该是编译器的责任,而不是软件开发者的责任。

面向对象软件构造(第2版)-第7章 静态结构: 类 (下)相关推荐

  1. c#面向对象与程序设计第三版第三章例题代码_C#程序设计教程 | 教与学(教学大纲)...

    <C#程序设计教程>课程教学大纲 执笔人:xxx,xxx,xxx 编写日期:年 月 一.课程基本信息 1.课程名称:C#程序设计教程 2.课程编号: 3.课程体系/类别: 4.课程性质: ...

  2. C++面向对象程序设计陈维兴版第四章所有例题

    本博文源于<C++面向对象程序设计陈维兴第三版>,第四章所有例题进行汇总! 文章目录 1.自引用指针this 2. 对象数组与对象指针 2.1 对象数组 2.2 对象指针 2.2.1 用对 ...

  3. 《Java面向对象程序设计》(第2版)第七章课后习题及答案

    1."程序中凡是可能出现异常的地方必须进行捕获或拋出",这句话对吗? 异常分两类,runtime异常和非runtime异常. runtime异常,比如ArithmeticExcep ...

  4. 哈工大软件构造第一章总结

    软件构造第一章名为软件构造的多维视图和质量目标,作为整个课程的开篇,阐释了软件构造的对象是什么,以及软件系统构成的维度和指标. 1.软件构造的多维视图 第一章的第一部分内容可以由下面这幅图来概括: 那 ...

  5. 第三章 软件构造过程与配置管理

    第三章 软件构造过程与配置管理 第三章 软件构造过程与配置管理 Software Development Lifecycle(SDLC)软件开发生命周期 From 0 to 1 从无到有 From 1 ...

  6. 软件构造课程面向对象编程学习心得

    面向对象的编程 本章纲要是: 面向对象的标准 OOP的基本概念 OOP的主要特性:封装与信息隐藏,继承与重写,多态,子类型,重载,静态与动态分派 Java中一些重要的Object方法 设计良好的类 # ...

  7. 好书整理系列之-设计模式:可复用面向对象软件的基础 4

    第4章结构型模式 结构型模式涉及到如何组合类和对象以获得更大的结构.结构型类模式采用继承机制来 组合接口或实现.一个简单的例子是采用多重继承方法将两个以上的类组合成一个类,结果 这个类包含了所有父类的 ...

  8. 哈工大2021软件构造lab1总结

    哈工大2021软件构造lab1总结 作为软件构造的第一次实验,感觉内容本身不是很难,里面功能的实现用上学期在数据结构和算法分析两门课里学到的知识就可以解决(尽管其实已经忘没了).这次实验主要目的还是准 ...

  9. 2021哈工大软件构造期末考点复习笔记

    第一节 多维视图和质量目标 软件构造多维度视图 红色标注为重点(考试会考选择题) Moment 特定时刻的软件形态 Period 软件形态随时间的变化 AST (Abstract Syntax Tre ...

最新文章

  1. Java之内存模型的基础、重排序、顺序一致性、volatile、锁、final
  2. 如何做好网站开发项目需求分析(转)
  3. spring boot启用tomcat ssl
  4. You C.A.N.大赛 解锁7大行业智能硬件创新密码
  5. web 项目集成福昕_项目学生:Web服务集成
  6. 腾讯人均每月薪酬成本超8万元,员工总数首次超10万
  7. thinkphp3.0 php7,tp3.1 for php7
  8. 阿里云负载均衡器(SLB)的配置方法
  9. jmxtrans安装使用
  10. 使用Win7时,出现无法切换电视墙
  11. AndroidStudio_你的主机中的软件中止了一个已建立的连接---Android原生开发工作笔记123
  12. cdate在java中_Java Calendar.add方法代码示例
  13. 《Python核心编程》第二版第三版高清PDF 中文
  14. ABC类IP地址划分_wuli大世界_新浪博客
  15. 《勿忘初心,勿忘前行》——2016年度总结
  16. XMLConstants.FEATURE_SECURE_PROCESSING错误
  17. 斗鱼直播与熊猫直播竞品分析
  18. java中,判断当前时间是否处于某个一个时间段内
  19. 基于Trie树进行拆分字符串变成拼音音节(二):字符串拼音拆分
  20. springboot+vue旅游景点酒店预订系统网站

热门文章

  1. iBypasser完美支持iOS9-14系统·支持ID登录消息推送
  2. Object C 实现简单打卡APP
  3. OIS输入系统-1_OIS简介与使用
  4. uniapp实现上拉加载更多
  5. c语言字符串二维数组如何赋值,C语言二维数组字符串的赋值
  6. react10_状态提升
  7. 3.5 模板模式(Template Pattern) -《SSM深入解析与项目实战》
  8. html隔几秒显示当前时间,用js实现每隔一秒刷新时间的实例(含年月日时分秒)
  9. 使用计算机上级考试系统时显示注册键值hkey-路径无效,计算机组装于维护上级考试题解.doc...
  10. led屏背后线路安装图解_文登推荐led透明屏安装成本低_文笔峰装饰