面向对象软件构造(第2版)-第7章 静态结构: 类 (下)
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.
·如果C是E,或C直接需要类D而D需要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.
当您读到这里的时候,您必须要考虑所有的可能性。如果后面有一系列的参数,如
那么您要知道g是一个例程;它还可能是一个函数或过程。下面要讨论的一个类型是:
f is a function. Yet another variant is:
which defines f as a constant attribute of value some_value.
但是,如果有一个is,随后是一个例程体(do或是变体once和external,后面会了解),如
这里把f定义成有着some_value值的常量属性(constant attribute)。
让我们进一步探索统一存取原则的结果,并在一个共同的标题——特性——之下对属性和例程分组。
对客户来说,这种把属性和没有参数的函数认为是等同的决定有着两个重要的后果,随后的章节中将会介绍:
·第一个后果影响了软件的文档。类的标准客户文档,即大家所知的类的简易格式(short form),将被设计成不再揭示一个给定的特性是否是一个属性或是一个函数(在这种情况下,可能是任何的一种)。
输出属性的能力在许多OO语言中的约定并不相同。其中典型的是Smalltalk,只有例程(叫做“函数”)可以被类输出;属性(“实例变量”)不可以直接被客户使用。
class POINT feature -- Public features:
¼ Other features as in the earlier version ¼
feature {NONE} -- Features inaccessible to clients:
This approach has two drawbacks:
这个技术协调了统一存取(客户端的要素),易于编写类代码(提供者的要素),和效率(两者的要素)的目标。
The client’s privileges on an attribute
[注] 这段英文无法理解。可能是电子文档有所疏漏。我现在手边没有纸本书籍,所以如果您能核对,请告之,谢谢。
要使attrib在修改模式中可以访问,您必须编写并输出一个正确的过程:
-- Set to v the value of attrib.
Instead of this convention, one could imagine a syntax for specifying access rights, such as
这里的A代表访问,M代表修改。(A的指定是可选的:如果您有输出,那么您必须至少允许客户在只读的模式下访问)。这种方式避免了不断地编写类似于set_attrib这种过程的需要。
除了不能证明额外的语言复杂性之外,这种解决方案并不十分灵活。在许多情况下,您想要输出一些特殊的方法来修改属性。例如,下面的类输出一个记数器,您并不能任意的直接修改它,而只能是加一或减一:
同样的,在本章中所用的类POINT不允许其客户直接设置点的x和y;客户能够改变这些属性的值,但是只能通过专门为此目的而输出的特别机制,过程translate和scale。
set_polygon_size (new_size: INTEGER) is
-- Set the number of polygon vertices to new_size.
set_polygon_size (new_size: INTEGER) is
-- Set the number of polygon vertices to new_size.
要求任何实参必须大于等于3。直接赋值将不可能执行这样的约束;如此一个调用就会产生一个错误的对象。
这些考虑结果显示了一个类的作者在其自己的处理范围之内对于每一个属性必须有5种可能的级别来赋予客户访问特权。
This solution is an improvement over the ones commonly found in O-O languages:
本次关于一个相当具体的语言特征的讨论说明了二个语言设计上的一般原则: 不要无端地干扰程序员;知道在接近收益递减(饱和)的时候去停止引进新的语言构造。
ISE的编译器通过一个通用的内联展开机制来达到这个目的,这个机制把带有适当参数的例程体的代码直接插入到调用程序的代码中来消除某些例程的调用。
在一些程序设计语言中,特别是Ada和C++,开发者可以指定什么样的例程需要内联展开。我发现基于下列这些原因把这项工作设成一个自动优化过程更可取:
·对一个调用展开内联并不总是正确的;由于编译器为了正确性必须检查最优化的申请,它正好可以省去开发者首先检查它的不便。
·随着软件中的变化,特别是通过继承,一个能够内联的例程也许会变成不需要内联。一个软件工具要比人更容易检测到这种情况。
• Software developers have better things to do with their time.
The architectural role of selective exports
超级模块能使它们自己变成类,而不是使用分开的包结构;这是Simula的方法,其允许类嵌套。它带来了部分的额外复杂性,并没有实际的好处。
在结构化集合中对类分组依然有着这种需求。在后面的章节中将通过群集概念来描述。但是群集是一个管理和组织性的概念;使它成为一个语言构造将会危害面向对象方法和它所支持的模块化的简单性。
在feature子句的题头中,每一个类列出了对其它类开放的特性。为什么不同时列出从其它类中获得的特性呢?封装语言Modula-2实际上提供了一个import子句。
where, since our notation is typed, a must have been declared:
So there is no need to bother developers with import clauses.
然而,这里却有用输入文档帮助 开发者的需求。一个优秀的图形开发环境应该包括使您能够通过点击按钮看到类的提供者和祖先,并且通过探索它们自己的提供者和祖先进一步跟随输入链的机制。
Denoting the result of a function
在本章早期提出的一个有趣的语言议题是如何指明函数的返回值。虽然它适用于非OO语言,但也值得进一步地研究。
考虑一个函数——一个有返回值的例程。因为所有调用函数的目的是要计算出某个特定的结果并返回给调用者,所以出现的问题是怎样表示那个出现在函数代码中返回值,尤其是在初始化并更新返回值的指令中。
if some_condition then Result := 10 end
if some_condition then Result := 10 end
将返回10如果some_condition在调用的时候满足条件,否则返回0(INTEGER的缺省初始值)。
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)。
惯例A依靠一个return e形式的指令,指令终止当前外围函数的执行,返回结果e。这项技术的好处是清楚明了,由于它使得返回值在函数代码中引人注目。但是这得忍受着几个缺点:
A1·常常,实际上返回值很可能在某个计算过程中得出:一个初始化和几个随后的更新。这意味着您必须引进和定义一个额外变量(在本章的术语中是一个实体),只是为了保持计算的中间结果。
A2·此方法倾向于发扬多点退出模块,这与良好的程序结构原则相违背。
要注意的是有可能解决最后两个问题,只要不把return看成一条指令,而要把它当作一条语法子句,这需要作为函数代码的一部分:
function name (arguments): TYPE is
这个解决方案保留了与return指令思想精髓的兼容性,同时致力于它的最大不足。然而,没有当前流行的语言使用它,当然也没有解决A1。
第二个通常的技术B,把函数名作为函数代码内的一个变量来对待。一个调用的返回值是那个变量的最终值。(这避免了引进一个在A1中提出的特别变量。)
which is valid only if f has no arguments. But then an assignment of the form
if some_condition then Result := “Some specific value” end
infix "|_" (x: REAL): INTEGER is
smallest_possible: Result + 1 > x
if some_condition then Result := “Some specific value” end
infix "|_" (x: REAL): INTEGER is
smallest_possible: Result + 1 > x
后置条件是ensure子句,声明两个返回值的属性:不大于参数;加1后产生的结果大于参数。
Complement: a precise definition of entities
当我们考虑符号问题的时候,澄清一个上面一再使用了的但还没有精确定义的概念还是有用的:实体。这是一个简单的技术概念,从传统的变量概念归纳出,而不是一个至关重要的对象技术观念;我们需要一个精确的定义。
在本书中用到的实体包括了运行时的值所代表的名字,它们本身附属在可能的对象上。我们现在看到了所有三种可能的情况:
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·一个例程的形式参数。 |
E2的情况显示了在所有的场合下实体Result被作为一个局部实体对待;其它的局部实体在local子句中引进。一个例程的Result和其它局部实体在例程每一次调用的时候都被重新初始化。
除了形参(E3)之外的所有实体都是可写的,也就是说,可以作为赋值语句x := some_value的目标x。
7.11 KEY CONCEPTS INTRODUCED IN THIS CHAPTER
·面向对象技术的基本概念是类的概念。一个类是一个部分或完全实现的抽象数据类型。
• A class may have instances, called objects.
·不要把对象(动态元素)和类(一组运行时对象的共同属性的静态描述)搞混。
• In a consistent approach to object technology, every object is an instance of a class.
·在一个符合对象技术的方法中,每个对象都是一个类的一个实例。
·类应用于模块和类型两者。OO模型的独创性和力量部分地来自于这两个概念的融合。
·类的特点是特性,包括属性(代表类实例的字段)和例程(代表这些实例上的计算)。一个例程也许是一个返回一个结果的函数,或者一个没有结果的过程。
·面向对象计算方式的基本机制是特性调用。一个特性调用把类的一个特性应用到类的实例上,这个特性可能有参数。
·特性调用使用圆点符号(为标识符特性)或是运算符符号,前缀或是中缀(为运算符特性)。
• Every operation is relative to a “current instance” of a class.
·对于一个类的客户(使用这个特性的其它类)来说,根据统一存取原则是无法从一个无参数的函数中区分出一个属性的。
·一个可执行的类的程序集称为系统。一个系统包含一个根类和根直接地或间接地需要的所有类(通过客户和继承关系)。执行系统就是创建根类的实例,并调用实例上的过程。
·系统应该具有一个分散的架构。在运算之间的次序关系对设计来说无关紧要。
·一个小型系统描述语言,Lace,能够指定一个系统应该如何装配。Lace的规格或者Ace,表明根类和系统群集所在的目录集合。
·系统的装配过程应该是自动的,过程并不需要Make files或者Include指令。
·信息隐藏机制需要灵活性: 除了完全掩藏或完全有效以外,一个特性也许需要只对有些客户输出;一个属性也许需要把输出设为只读,访问和有限修改,或者完全修改。
·输出一个属性并赋予客户权力访问它。修改它的话需要调用适当的输出过程。
·选择性输出是必要的,它能够对紧密相关的类分组,使得能对彼此的特性获得特别的访问权限。
·这里不需要建立在类之上的超级模块构造。类应该是保持着独立的软件组件。
·面向对象的发展促进了模块样式,导致了许多小的例程。内联,一个编译器的优化,消除了所有潜在的效率问题。检测内联调用应该是编译器的责任,而不是软件开发者的责任。
面向对象软件构造(第2版)-第7章 静态结构: 类 (下)相关推荐
- c#面向对象与程序设计第三版第三章例题代码_C#程序设计教程 | 教与学(教学大纲)...
<C#程序设计教程>课程教学大纲 执笔人:xxx,xxx,xxx 编写日期:年 月 一.课程基本信息 1.课程名称:C#程序设计教程 2.课程编号: 3.课程体系/类别: 4.课程性质: ...
- C++面向对象程序设计陈维兴版第四章所有例题
本博文源于<C++面向对象程序设计陈维兴第三版>,第四章所有例题进行汇总! 文章目录 1.自引用指针this 2. 对象数组与对象指针 2.1 对象数组 2.2 对象指针 2.2.1 用对 ...
- 《Java面向对象程序设计》(第2版)第七章课后习题及答案
1."程序中凡是可能出现异常的地方必须进行捕获或拋出",这句话对吗? 异常分两类,runtime异常和非runtime异常. runtime异常,比如ArithmeticExcep ...
- 哈工大软件构造第一章总结
软件构造第一章名为软件构造的多维视图和质量目标,作为整个课程的开篇,阐释了软件构造的对象是什么,以及软件系统构成的维度和指标. 1.软件构造的多维视图 第一章的第一部分内容可以由下面这幅图来概括: 那 ...
- 第三章 软件构造过程与配置管理
第三章 软件构造过程与配置管理 第三章 软件构造过程与配置管理 Software Development Lifecycle(SDLC)软件开发生命周期 From 0 to 1 从无到有 From 1 ...
- 软件构造课程面向对象编程学习心得
面向对象的编程 本章纲要是: 面向对象的标准 OOP的基本概念 OOP的主要特性:封装与信息隐藏,继承与重写,多态,子类型,重载,静态与动态分派 Java中一些重要的Object方法 设计良好的类 # ...
- 好书整理系列之-设计模式:可复用面向对象软件的基础 4
第4章结构型模式 结构型模式涉及到如何组合类和对象以获得更大的结构.结构型类模式采用继承机制来 组合接口或实现.一个简单的例子是采用多重继承方法将两个以上的类组合成一个类,结果 这个类包含了所有父类的 ...
- 哈工大2021软件构造lab1总结
哈工大2021软件构造lab1总结 作为软件构造的第一次实验,感觉内容本身不是很难,里面功能的实现用上学期在数据结构和算法分析两门课里学到的知识就可以解决(尽管其实已经忘没了).这次实验主要目的还是准 ...
- 2021哈工大软件构造期末考点复习笔记
第一节 多维视图和质量目标 软件构造多维度视图 红色标注为重点(考试会考选择题) Moment 特定时刻的软件形态 Period 软件形态随时间的变化 AST (Abstract Syntax Tre ...
最新文章
- Java之内存模型的基础、重排序、顺序一致性、volatile、锁、final
- 如何做好网站开发项目需求分析(转)
- spring boot启用tomcat ssl
- You C.A.N.大赛 解锁7大行业智能硬件创新密码
- web 项目集成福昕_项目学生:Web服务集成
- 腾讯人均每月薪酬成本超8万元,员工总数首次超10万
- thinkphp3.0 php7,tp3.1 for php7
- 阿里云负载均衡器(SLB)的配置方法
- jmxtrans安装使用
- 使用Win7时,出现无法切换电视墙
- AndroidStudio_你的主机中的软件中止了一个已建立的连接---Android原生开发工作笔记123
- cdate在java中_Java Calendar.add方法代码示例
- 《Python核心编程》第二版第三版高清PDF 中文
- ABC类IP地址划分_wuli大世界_新浪博客
- 《勿忘初心,勿忘前行》——2016年度总结
- XMLConstants.FEATURE_SECURE_PROCESSING错误
- 斗鱼直播与熊猫直播竞品分析
- java中,判断当前时间是否处于某个一个时间段内
- 基于Trie树进行拆分字符串变成拼音音节(二):字符串拼音拆分
- springboot+vue旅游景点酒店预订系统网站
热门文章
- iBypasser完美支持iOS9-14系统·支持ID登录消息推送
- Object C 实现简单打卡APP
- OIS输入系统-1_OIS简介与使用
- uniapp实现上拉加载更多
- c语言字符串二维数组如何赋值,C语言二维数组字符串的赋值
- react10_状态提升
- 3.5 模板模式(Template Pattern) -《SSM深入解析与项目实战》
- html隔几秒显示当前时间,用js实现每隔一秒刷新时间的实例(含年月日时分秒)
- 使用计算机上级考试系统时显示注册键值hkey-路径无效,计算机组装于维护上级考试题解.doc...
- led屏背后线路安装图解_文登推荐led透明屏安装成本低_文笔峰装饰