面向对象软件构造(第2版)-第5章 接近对象技术 (上)

Extendibility, reusability and reliability, our principal goals, require a set of conditions defined in the preceding chapters. To achieve these conditions, we need a systematic method for decomposing systems into modules.

扩充性,复用性和可靠性,要达到这些主要目标需要一系列的条件,这些条件在前面的章节中已定义过了.为了要完成这些条件,我们需要一个系统的方法来把系统分解成模块.

 

This chapter presents the basic elements of such a method, based on a simple but far-reaching idea: build every module on the basis of some object type. It explains the idea, develops the rationale for it, and explores some of the immediate consequences.

本章介绍有关这种方法的基本元素,这基于一个简单而深远的思想: 以一些对象类型为基础建立每个模块.本章解释了这个思想,逐步展开其原理,并研究了当前的一些结论.

 

A word of warning. Given today’s apparent prominence of object technology, some readers might think that the battle has been won and that no further rationale is necessary. This would be a mistake: we need to understand the basis for the method, if only to avoid common misuses and pitfalls. It is in fact frequent to see the word “object-oriented” (like “structured” in an earlier era) used as mere veneer over the most conventional techniques. Only by carefully building the case for object technology can we learn to detect improper uses of the buzzword, and stay away from common mistakes reviewed later in this chapter.

注意.今天的对象技术已经有着显然的地位,一些读者可能认为已经是赢得了竞争, 进一步的理论已经没有必要了.这是一个错误:我们需要理解方法的基本原理只是为了要避免常见的误用和陷阱.事实上频繁地看到面向对象的(object-oriented)这个词(就如早期的"结构化(structured)")仅仅被用于装饰最传统的技术.只有通过使用对象技术仔细地构建案例,我们才能学会发现这些术语的不当使用,并且能够避免常见的错误,这在这章稍后讨论.

 

5.1 THE INGREDIENTS OF COMPUTATION

5.1 计算的要素

 

The crucial question in our search for proper software architectures is modularization: what criteria should we use to find the modules of our software?

在我们研究恰当的软件结构中,决定性的问题是模块化: 我们应该使用什么样的标准得到我们的软件模块?

 

To obtain the proper answer we must first examine the contending candidates.

要得到正确的回答,我们先要分析这些备选答案.

 

The basic triangle

基础三角

 

Three forces are at play when we use software to perform some computations:

当我们利用软件来完成某些计算时,三种力量在起作用:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

To execute a software system is to use certain processors to apply certain actions to certain objects.

要执行一个软件系统,就要使用特定的处理器(processors)来应用特定的动作(actions)到特定的对象(objects).

 

The processors are the computation devices, physical or virtual, that execute instructions. A processor can be an actual processing unit (the CPU of a computer), a process on a conventional operating system, or a “thread” if the OS is multi-threaded.

处理器是物理的或虚拟的,能执行指令的计算装置.一个处理器可以是实际的处理单元(计算机的CPU),一个常规操作系统上的进程,或多线程操作系统上的一个线程.

 

The actions are the operations making up the computation. The exact form of the actions that we consider will depend on the level of granularity of our analysis: at the hardware level, actions are machine language operations; at the level of the hardware-software machine, they are instructions of the programming language; at the level of a software system, we can treat each major step of a complex algorithm as a single action.

动作是构成计算过程的运算.我们所考虑的动作的精确形态取决于我们的分析间隔的层次:在硬件层次,动作是机器语言的运算;在硬件-软件机器的层次,其是程序语言的指令;在软件系统的层次,我们把一个复杂算法的每个主要的步骤视为一个单一的动作.

 

The objects are the data structures to which the actions apply. Some of these objects, the data structures built by a computation for its own purposes, are internal and exist only while the computation proceeds; others (contained in the files, databases and other persistent repositories) are external and may outlive individual computations.

对象是动作应用的数据结构.其中的一些对象,计算过程构建的数据结构是出于其自身的目的,它们是内部的并且只存在于计算发生的时候;其它的(包含在文件,数据库和其它永久储存容器中)是外部的,并能永久存活于不同的计算过程中.

 

Processors will become important when we discuss concurrent forms of computation, in which several sub-computations can proceed in parallel; then we will need to consider two or more processors, physical or virtual. But that is the topic of a later chapter; for the moment we can limit our attention to non-concurrent, or sequential computations, relying on a single processor which will remain implicit.

当我们讨论计算过程的并行(concurrent)形式的时候,处理器会变得很重要,一些子计算过程能在其中平行处理;接下来我们将会考虑二个或更多的,物理的或虚拟的处理器.但那是后续章节的主题;此时,要把我们的注意力集中在非并行(non-concurrent),循序计算(sequential computations),它运行在一个保持固定的单一处理器.

 

This leaves us with actions and objects. The duality between actions and objects — what a system does vs. what it does it to — is a pervasive theme in software engineering.

现在剩下了动作和对象.在动作和对象之间的二元性-一个系统所做的如何做-是软件工程的一个普遍的主题.

 

A note of terminology. Synonyms are available to denote each of the two aspects: the word data will be used here as a synonym for objects; for action the discussion will often follow common practice and talk about the functions of a system.

术语注解.同义字可用来表示二个不同角度的任何一方: 单词data这里会用作objects的同义字;对于action讨论通常按照惯例,同时谈论系统的functions.

 

The term “function” is not without disadvantages, since software discussions also use it in at least two other meanings: the mathematical sense, and the programming sense of subprogram returning a result. But we can use it without ambiguity in the phrase the functions of a system, which is what we need here.

由于软件讨论过程中使用至少其它的个含,术语function有这样的问题: 数学的意义,带有返回值的子程序的编程意义.但是,这里我们所需要用到的一个短语,一个系统的函数(the functions of a system),却没有什么歧义.

 

The reason for using this word rather than “action” is the mere grammatical convenience of having an associated adjective, used in the phrase functional decomposition. “Action” has no comparable derivation. Another term whose meaning is equivalent to that of “action” for the purpose of this discussion is operation.

使用这个单词而不用动作的理由仅仅是语法上的便利,它有一个关联的形容词用在短语函数分解(functional decomposition).动作没有相关的词源.在这个讨论中,另外一个意义和动作相同之术语是运算(operation).

 

Any discussion of software issues must account for both the object and function aspects; so must the design of any software system. But there is one question for which we must choose — the question of this chapter: what is the appropriate criterion for finding the modules of a system? Here we must decide whether modules will be built as units of functional decomposition, or around major types of objects.

软件议题的任何讨论必须对对象及函数两方面进行阐述;任何软件系统的设计也一样. 但是这里有一个我们一定要作出选择的问题,也是本章的问题:什么是决定一个系统模块的适当准则?在这里我们必须决定是否模块作为函数分解的单元而被构建,还是围绕主要的对象类型被构建.

 

From the answer will follow the difference between the object-oriented approach and other methods. Traditional approaches build each module around some unit of functional decomposition — a certain piece of the action. The object-oriented method, instead, builds each module around some type of objects.

从回答中将会发觉面向对象的方式和其它方法之间的不同.传统的方法围绕着一些函数分解的单元-特定的活动片段-构建每个模块.不同的是,面向对象的方法围绕着一些对象类型建立每个模块.

 

This book, predictably, develops the latter approach. But we should not just embrace O-O decomposition because the title of the book so implies, or because it is the “in” thing to do. The next few sections will carefully examine the arguments that justify using object types as the basis for modularization — starting with an exploration of the merits and limitations of traditional, non-O-O methods. Then we will try to get a clearer understanding of what the word “object” really means for software development, although the full answer, requiring a little theoretical detour, will only emerge in the next chapter.

可以看出,本书介绍后者.但是我们不应该仅仅包含OO分解,只是因为书名有所暗示,或因为它正是我们要做的事情.下面的几个部分将会仔细地分析这些论证,它们验证了使用对象类型作为模块化的基础-从探索其价值和传统的,非OO方法的限制开始.然后,对于对象这个单词对软件开发而言真正的意义是什么,我们将会试着得到一个更清晰的理解,虽然完整的答案会在下一章中出现,这需要稍微的理论变换.

 

We will also have to wait until the next chapter for the final settlement of the formidable and ancient fight that provides the theme for the rest of the present discussion: the War of the Objects and the Functions. As we prepare ourselves for a campaign of slander against the functions as a basis for system decomposition, and of corresponding praise for the objects, we must not forget the observation made above: in the end, our solution to the software structuring problem must provide space for both functions and objects — although not necessarily on an equal basis. To discover this new world order, we will need to define the respective roles of its first-class and second-class citizens.

我们也将不得不等待,直到下一章中那强大的和古老的争斗的最终结束:对象和函数之间的战争,这也是当前讨论中其它部分的主题.当我们为自己准备一个对抗的借口来反对以函数作为系统分解的基础,并且对应地赞美对象的时候,我们不能忘记上述的结果: 最终, 我们对软件结构问题的解决方案必须对函数和对象两者都要提供空间-虽然不必相等.为了获得这个新世界的次序,我们将分别定义一等和二等公民的不同角色.

 

5.2 FUNCTIONAL DECOMPOSITION

5.2 函数分解

 

We should first examine the merits and limitations of the traditional approach: using functions as a basis for the architecture of software systems. This will not only lead us to appreciate why we need something else — object technology — but also help us avoid, when we do move into the object world, certain methodological pitfalls such as premature operation ordering, which have been known to fool even experienced O-O developers.

使用函数作为软件系统架构的一种基础,我们应该首先调查这种传统方式的价值和限制.这不但会引导我们去重视我们需要其它的技术-对象技术-的原因,而且,当我们进入到对象世界的时候,这会帮助我们避免某些方法论的陷阱,诸如不成熟的运算顺序,它愚弄富有经验的OO开发者已是世人皆知.

 

Continuity

连续性

 

A key element in answering the question “should we structure systems around functions or around data?” is the problem of extendibility, and more precisely the goal called continuity in our earlier discussions. As you will recall, a design method satisfies this criterion if it yields stable architectures, keeping the amount of design change commensurate with the size of the specification change.

"我们应该是围绕函数还是围绕数据来构成系统?",回答这个提问的一个关键元素是扩充性的问题, 在我们先前的讨论中更明确的目标应该是连续性.正如你所知,如果一个设计方法产生稳定的结构并且保持规格变化的大小等同于设计变化的数量,那么它就满足这一个准则.

 

Continuity is a crucial concern if we consider the real lifecycle of software systems, including not just the production of an acceptable initial version, but a system’s long-term evolution. Most systems undergo numerous changes after their first delivery. Any model of software development that only considers the period leading to that delivery and ignores the subsequent era of change and revision is as remote from real life as those novels which end when the hero marries the heroine — the time which, as everyone knows, marks the beginning of the really interesting part.

如果我们考虑软件系统的真正生命周期,不但包括产品的可接受的初始版本,还包括系统的长期演化,那么连续性是一个至关重大的要点.绝大多数的系统在它们的第一次发布之后经历了很多的变化.软件发展的任何模型,如果只考虑了发布之前的时期而忽略了随后的变化和修改的阶段,那么就和那些英雄与女主角一旦结合就结束的小说一样,远离了真正的生活-正如世人所知,这时候才是真正有趣部份的开始.

[译注]:是地狱的开始吧.

 

To evaluate the quality of an architecture (and of the method that produced it), we should not just consider how easy it was to obtain this architecture initially: it is just as important to ascertain how well the architecture will weather change.

为了要评估架构(和它的产生方法)的品质,我们不应该仅仅考虑最初获得这个架构的便利性:真正重要的是确定架构将会如何合理地应对变化.

 

The traditional answer to the question of modularization has been top-down functional decomposition, briefly introduced in an earlier chapter. How well does top-down design respond to the requirements of modularity?

有关模块化的问题,传统的答案是由上而下的函数分解,在之前的内容中曾简短地介绍过.由上而下的设计如何能较好地应对模块化的需求呢?

 

Top-down development

由上而下的开发

 

There was a most ingenious architect who had contrived a new method for building houses, by beginning at the roof, and working downwards to the foundation, which he justified to me by the like practice of those two prudent insects, the bee and the spider.

Jonathan Swift: Gulliver’s Travels, Part III, A

Voyage to Laputa, etc., Chapter 5.

 

有一位极聪明的建筑师,为建筑房子发明了一个新的方法,从屋顶开始,向下工作到地基,他向我证实了,蜜蜂和蝙蝠,这两种谨慎的昆虫也有类似的习性.

Jonathan Swift: 格列佛游记, Part III, A

Voyage to Laputa, etc., Chapter 5.

 

The top-down approach builds a system by stepwise refinement, starting with a definition of its abstract function. You start the process by expressing a topmost statement of this function, such as

 [C0]

“Translate a C program to machine code”

or:

[P0]

“Process a user command”

and continue with a sequence of refinement steps. Each step must decrease the level of abstraction of the elements obtained; it decomposes every operation into a combination of one or more simpler operations. For example, the next step in the first example (the C compiler) could produce the decomposition

[C1]

“Read program and produce sequence of tokens”

“Parse sequence of tokens into abstract syntax tree”

“Decorate tree with semantic information”

“Generate code from decorated tree”

or, using an alternative structure (and making the simplifying assumption that a C program is a sequence of function definitions):

[C'1]

from

“Initialize data structures”

until

“All function definitions processed”

loop

“Read in next function definition”

“Generate partial code”

end

“Fill in cross references”

由上而下的方法通过逐步求精法(stepwise refinement)构建一个系统,起始于一个抽象函数的定义.通过表达这个函数的一个最顶层的陈述开始这个过程:

[C0]

“Translate a C program to machine code”

:

[P0]

“Process a user command”

下面是一个连续的细化步骤.每一个步骤必须降低所得元素的抽象层次;把每一个运算分解成一个或多个更简化的运算组合.例如,在第一个例子中(C 编译器)的下一步能产生如下分解

[C1]

“Read program and produce sequence of tokens”

“Parse sequence of tokens into abstract syntax tree”

“Decorate tree with semantic information”

“Generate code from decorated tree”

或者,使用一个选择性结构(并且做出简单假定:一个C程序是一个连续的函数定义):

[C'1]

from

“Initialize data structures”

until

“All function definitions processed”

loop

“Read in next function definition”

“Generate partial code”

end

“Fill in cross references”

 

In either case, the developer must at each step examine the remaining incompletely expanded elements (such as “Read program ¼and “All function definitions processed”) and expand them, using the same refinement process, until everything is at a level of abstraction low enough to allow direct implementation.

在任一情况,开发者必须在每个步骤中检查所得到的并没完全扩展的(“Read program ¼“All function definitions processed”),同时使用相同的细化过程展它,直到每个元素抽象层次上足够到能允许其直接实现.

 

We may picture the process of top-down refinement as the development of a tree. Nodes represent elements of the decomposition; branches show the relation “B is part of the refinement of A”.

我们可以像树的展开一样描绘由上而下的细化过程.节点表示分解的元素;分支显示关系BA细化的一部份".

 

 


The top-down approach has a number of advantages. It is a logical, well-organized thought discipline; it can be taught effectively; it encourages orderly development of systems; it helps the designer find a way through the apparent complexity that systems often present at the initial stages of their design.

由上而下的方式有许多的优点.它是一种合乎逻辑的,组织优异的思维训练;它能有效地传授;它能鼓励系统的有序开发;它帮助设计者寻找一个方法来克服明显的复杂性,系统时常在开始设计阶段就呈现出这种复杂性.

 

The top-down approach can indeed be useful for developing individual algorithms. But it also suffers from limitations that make it questionable as a tool for the design of entire systems:

由上而下的方式对开发独立的算法是真正有效的.但是,用作一个设计整体系统的工具,它也遭受到了可疑性的限制:

 

• The very idea of characterizing a system by just one function is subject to doubt.

单单只是一个函数就能完整地表示一个系统的想法受到了人们的怀疑.

 

By using as a basis for modular decomposition the properties that tend to change the most, the method fails to account for the evolutionary nature of software systems.

通过使用模组分解为基础以试图最大幅的改变特性,此方法不能解释软件系统的演化本性.

 

Not just one function

不仅仅一个函数

 

In the evolution of a system, what may originally have been perceived as the system’s main function may become less important over time.

在系统的化中,那些原本被认为是系统的主要函数,会随着时间的流逝变得不再重要.

 

Consider a typical payroll system. When stating his initial requirement, the customer may have envisioned just what the name suggests: a system to produce paychecks from the appropriate data. His view of the system, implicit or explicit, may have been a more ambitious version of this:

考虑一个典型的薪资系统.当描述原始需求的时候,客户可以想象得到系统名字所描述的含义:一个在恰当的数据中计算工资单的系统.他对系统的看法,不管是隐含的还是明确的,都可以要求一个更高的版本:

 


The system takes some inputs (such as record of hours worked and employee information) and produces some outputs (paychecks and so on). This is a simple enough functional specification, in the strict sense of the word functional: it defines the program as a mechanism to perform one function — pay the employees. The top-down functional method is meant precisely for such well-defined problems, where the task is to perform a single function — the “top” of the system to be built.

系统使用一些输入(如按小时的工作记录和职员信息)并产生一些输出(工资单等等). 在单词functional的严格的含义中,这是一个简单充分的函数规格: 它以一种细述程序的方法来完成一个支付员工薪水的函数.对这种定义明确的问题而言,由上而下的函数分解方法意味着精确性,这里的任务是要完成一个单一的函数―构建系统的顶端.

 

Assume, however, that the development of our payroll program is a success: the program does the requisite job. Most likely, the development will not stop there. Good systems have the detestable habit of giving their users plenty of ideas about all the other things they could do. As the system’s developer, you may initially have been told that all you had to do was to generate paychecks and a few auxiliary outputs. But now the requests for extensions start landing on your desk: Could the program gather some statistics on the side? I did tell you that next quarter we are going to start paying some employees monthly and others biweekly, did I not? And, by the way, I need a summary every month for management, and one every quarter for the shareholders. The accountants want their own output for tax preparation purposes. Also, you are keeping all this salary information, right? It would be really nifty to let Personnel access it interactively. I cannot imagine why that would be a difficult functionality to add.

然而,假定我们薪资程序的开发是成功的: 程序各尽其职.但最有可能的是开发并没有就此结束.优秀的系统带给用户可恶的习惯,又让他们产生了许多额外工作的想法.身为系统的开发者,您最初可能已经被告之您必须做的全部工作就是要生成工资单和一些附加的输出.但是现在在您的书桌上开始摆放着扩充的需求了:程序可以采集另外的一些统计数据吗?我得告诉您下个季度我们开始支付一些职员月薪而其他的是双周薪,可以吗?并且,顺便提一下,我需要为管理层提供月报表,并为股东提供季报.为纳税准备的会计需要他们自己的输出格式.您也能保存全部的薪水信息,对吗? 让员工交互地使用软件将会是真正的好主意,我不能够想象为什么那个功能加上去会很困难.

 

This phenomenon of having to add unanticipated functions to successful systems occurs in all application areas. A nuclear code that initially just applied some algorithm to produce tables of numbers from batch input will be extended to handle graphical input and output or to maintain a database of previous results. A compiler that just translated valid source into object code will after a while double up as a syntax verifier, a static analyzer, a pretty-printer, even a programming environment.

这种不得不把事先未预料到的功能加入成功的系统中的现象在所有的应用领域中都会发生.一个核心代码,最初只是提供算法用来从批输入中产生数目表,将会扩展至处理图像的输入和输出,或维护以前结果的数据库.一个仅仅是把有效的源代码编译成对象码的编译器,不久将会塞进一个语法验证器,一个静态分析器,一个漂亮的打印机程序,更甚至一个编程环境.

 

This change process is often incremental. The new requirements evolve from the initial ones in a continuous way. The new system is still, in many respects, “the same system” as the old one: still a payroll system, a nuclear code, a compiler. But the original “main function”, which may have seemed so important at first, often becomes just one of many functions; sometimes, it just vanishes, having outlived its usefulness.

这种变化过程经常是渐进式的.新需求用一种连续的方式从初始需求就开始了.新系统在许多方面和以前相比一直是同一个系统:仍旧是一个薪资系统,一个核心代码,一个编译器.但是最初的主要功能,开始时看上去相当的重要,但常常只是变成许多功能中的一个;有时,在事过境迁后就无影无踪了.

 

If analysis and design have used a decomposition method based on the function, the system structure will follow from the designers’ original understanding of the system’s main function. As the system evolves, the designers may feel sorry (or its maintainers, if different people, may feel angry) about that original assessment. Each addition of a new function, however incremental it seems to the customer, risks invalidating the entire structure.

如果分析和设计已经使用了基于函数的分解方法,那么系统结构将反映了设计者对系统主要函数的最初理解.随着系统的进展,设计者可能对那个最初的看法感到不甚满意(或者,如果它的维护者是另外的人,可能也会觉得不满).然而,每一个新功能的加入,似乎满足了客户,却冒着使整个结构变得糟糕的风险。

 

It is crucial to find, as a criterion for decomposition, properties less volatile than the system’s main function.

作为一条系统分解的准则,关键是寻找比系统的主函数更稳定的属性.

 

Finding the top

寻找顶端

 

Top-down methods assume that every system is characterized, at the most abstract level, by its main function. Although it is indeed easy to specify textbook examples of algorithmic problems — the Tower of Hanoi, the Eight Queens and the like — through their functional “tops”, a more useful description of practical software systems considers each of them as offering a number of services. Defining such a system by a single function is usually possible, but yields a rather artificial view.

由上而下的方法假定,在最抽象的层次上,系统的主函数表现了系统的主要特征.尽管教科书上的算法例子-汉内塔,八皇后和类似的-通过它们的函数的顶端很容易的就能指明这些特征,但是一个更实用的实际软件系统的描述会考虑其中的每一个特征都要提供许多的服务.通过一个单一的函数定义这样的一个系统通常是可能的,但是却让人认为相当的不真实.

 

Take an operating system. It is best understood as a system that provides certain services: allocating CPU time, managing memory, handling input and output devices, decoding and carrying out users’ commands. The modules of a well-structured OS will tend to organize themselves around these groups of functions. But this is not the architecture that you will get from top-down functional decomposition; the method forces you, as the designer, to answer the artificial question “what is the topmost function?”, and then to use the successive refinements of the answer as a basis for the structure. If hard pressed you could probably come up with an initial answer of the form

“Process all user requests”

which you could then refine into something like

from

boot

until

halted or crashed

loop

“Read in a user’s request and put it into input queue”

“Get a request r from input queue”

“Process r

“Put result into output queue”

“Get a result o from output queue”

“Output o to its recipient”

end

就拿一个操作系统来说.最能想到的是系统提供的下列服务:分派处理器时间,管理内存,操作输入和输出设备,解码和执行用户的指令.一个具有优良结构的操作系统的模块将会倾向于在这些功能群体的周围组织它们自己.但是,您并不能从由上而下的函数分解中获得这样的架构;作为设计者,此方法强迫您回答虚伪的问题最顶端的函数是什么?,然后将答案连续细化作为一种结构的基础.如果在巨大的压力下,您可能会提出“Process all user requests”形式的一个起始回答,接着您能细化成下列这样:

from

boot

until

halted or crashed

loop

“Read in a user’s request and put it into input queue”

“Get a request r from input queue”

“Process r

“Put result into output queue”

“Get a result o from output queue”

“Output o to its recipient”

end

 

Refinements can go on. From such premises, however, it is unlikely that anyone can ever develop a reasonably structured operating system.

细化能继续进行.然而,在这样的前提之下,开发出一个合理结构的操作系统是不可能的.

 

Even systems which may at first seem to belong to the “one input, one abstract function, one output” category reveal, on closer examination, a more diverse picture. Consider the earlier example of a compiler. Reduced to its bare essentials, or to the view of older textbooks, a compiler is the implementation of one input-to-output function: transforming source text in some programming language into machine code for a certain platform. But that is not a sufficient view of a modern compiler. Among its many services, a compiler will perform error detection, program formatting, some configuration management, logging, report generation.

更甚至,在进一步的检验中,起先看上去属于一个输入,一个抽象函数,一个输出 之类的系统,却显示了一个完全不同的景象.考虑先前的编译器例子.拨开外表看本质,或究其根源,一个编译器是一个输入到输出功能的实现: 把一些程序语言的源代码转换成某个平台的机器码.但在现代的编译器观点中这并不充份.在众多的服务之中,一个编译器要完成错误诊测,程序格式化,一些配置管理,记录日志,生成报表.

 

Another example is a typesetting program, taking input in some text processing format — TEX, Microsoft Word, FrameMaker ¼ — and generating output in HTML, Postscript or Adobe Acrobat format. Again we may view it at first as just an input-to-output filter. But most likely it will perform a number of other services as well, so it seems more interesting, when we are trying to characterize the system in the most general way, to consider the various types of data it manipulates: documents, chapters, sections, paragraphs, lines, words, characters, fonts, running heads, titles, figures and others.

另外的一个例子是个排版程序,输入一些文字处理格式-TEX,Microsoft Word, FrameMaker同时产生HTML,Postscript或Adobe Acrobat格式的输出.开始我们可以再次把它看成是一个输入输出过滤器.但是很有可能它也完成了众多的其它服务, 因此,当我们试图用通常的方法来刻画系统特点的时候,会考虑它操纵的各种不同的数据类型:文件,章节,段落,小节,一行,单词,字符,字体,页头,标题,插图和其它,这看上去更加有趣.

 

The seemingly obvious starting point of top-down design — the view that each new development fulfills a request for a specific function — is subject to doubt:

很明显的,由上而下设计的出发点-对每个新的开发实现了特定功能的需求之观点-是值得怀疑的:

Real systems have no top.

真正的系统没有顶端.

 

 

 

 

Functions and evolution

函数演化

 

Not only is the main function often not the best criterion to characterize a system initially: it may also, as the system evolves, be among the first properties to change, forcing the top-down designer into frequent redesign and defeating our attempts to satisfy the continuity requirement.

对于表示一个系统的初始特征,主函数不但通常不是最好的准则,而且它很可能在系统进展之中是第一个被改变的属性,这强迫由上而下的设计者频繁地重新设计,并让我们在满足连续性需求的尝试中遭到失败.

 

Consider the example of a program that has two versions, a “batch” one which handles every session as a single big run over the problem, and an interactive one in which a session is a sequence of transactions, with a much finer grain of user-system communication. This is typical of large scientific programs, which often have a “let it run a big chunk of computation for the whole night” version and a “let me try out a few things and see the results at once then continue with something else” version.

考虑一个例子,一个程序有二个版本,一个批处理(batch)版本,把每一个会话作为一个单一的巨大的超限问题来处理;一个交互版本,其中一个会话就是一个顺序的事务,对用户-系统之间沟通具有清楚的条理.这是典型的大型科学系统的程序,通常有让它整晚地运行一个大计算的版本和让我试试一些东西并立刻看到结果然后继续的版本。

 

The top-down refinement of the batch version might begin as

批处理版本的由上而下的细化可能开始于如下:

 

[B0] -- Top-level abstraction

“Solve a complete instance of the problem”

[B1] -- First refinement

“Read input values”

“Compute results”

“Output results”

 

and so on. The top-down development of the interactive version, for its part, could proceed in the following style:

等等.交互版本的由上而下的开发部分过程如下列形式:

 [I1]

“Process one transaction”

[I2]

if  “New information provided by the user” then

“Input information”

“Store it”

elseif  “Request for information previously given” then

“Retrieve requested information”

“Output it”

elseif  “Request for result” then

if “Necessary information available” then

“Retrieve requested result”

“Output it”

else

“Ask for confirmation of the request”

if Yes then

“Obtain required information”

“Compute requested result”

“Output result”

end

end

else

(Etc.)

 

Started this way, the development will yield an entirely different result. The top-down approach fails to account for the property that the final programs are but two different versions of the same software system — whether they are developed concurrently or one has evolved from the other.

这样开始的话,开发将会产生一个完全不同的结果.由上而下的方式没有解释这个特性,既最终的程序是同一个软件系统的二个不同版本-是否它们同时开发,或是一个从另一个演化而来.

 

This example brings to light two of the most unpleasant consequences of the top-down approach: its focus on the external interface (implying here an early choice between batch and interactive) and its premature binding of temporal relations (the order in which actions will be executed).

这个例子揭露了由上而下方式中最讨厌的两种结果:它着眼于外部接口(在这里是指在批处理和交互式之间的过早选择)和它在临时关系上(运行动作的顺序)草率的绑定.

 

Interfaces and software design

接口和软件设计

 

System architecture should be based on substance, not form. But top-down development tends to use the most superficial aspect of the system — its external interface — as a basis for its structure.

系统架构应该以实质而不是以形式为基础.但是由上而下开发倾向使用系统最表面的方面-其外部接口-当作结构的基础.

 

The focus on external interfaces is inevitable in a method that asks “What will the system do for the end user?” as the key question: the answer will tend to emphasize the most external aspects.

在一个把系统将会为最终用户做什么?作为关键问题的方法中,焦点集中在外部接口上是不可避免的: 答案倾向于强调最外部的方面.

 

The user interface is only one of the components of a system. Often, it is also among the most volatile, if only because of the difficulty of getting it right the first time; initial versions may be of the mark, requiring experimentation and user feedback to obtain a satisfactory solution. A healthy design method will try to separate the interface from the rest of the system, using more stable properties as the basis for system structuring.

用户接口只是系统的组件之一.如果只因为开始时理解有困难,那么它也常常是最不稳定的因素之一;初始版本可能是样品,需要实验和用户反馈以获得满意的解决方案. 一个健壮的设计方法会设法把接口与系统的其余部分分开,使用更加稳定的属性作为系统结构的基础.

 

It is in fact often possible to build the interface separately from the rest of the system, using one of the many tools available nowadays to produce elegant and user-friendly interfaces, often based on object-oriented techniques. The user interface then becomes almost irrelevant to the overall system design.

事实上,接口系统的其余部分中分离,并使用当前有效的工具产生精致的用户友好的接口,基于面向对象技术来说是可行的.那么用户接口几乎和整个系统设计无关了.

 

Premature ordering

早期次序

 

The preceding examples illustrate another drawback of top-down functional decomposition: premature emphasis on temporal constraints. Each refinement expands a piece of the abstract structure into a more detailed control architecture, specifying the order in which various functions (various pieces of the action) will be executed. Such ordering constraints become essential properties of the system architecture; but they too are subject to change.

先前的例子说明了由上而下的函数分解中的另外一个缺点:过早的强调了时间约束. 每个细化从一小块抽象结构扩展到一个较为详细的控制结构,同时指定了各种不同的函数(各种不同的动作)将会执行的顺序.这样的次序约束形成了系统架构的基本属性;但是它们也受制于变化.

 

Recall the two alternative candidate structures for the first refinement of a compiler:

对一个编译器的首次细化,调用二种可能的候选结构:

 

[C1]

“Read program and produce sequence of tokens”

“Parse sequence of tokens into abstract syntax tree”

“Decorate tree with semantic information”

“Generate code from decorated tree”

[C'1]

from

“Initialize data structures”

until

“All function definitions processed”

loop

“Read in next function definition”

“Generate partial code”

end

“Fill in cross references”

 

As in the preceding example we start with two completely different architectures. Each is defined by a control structure (a sequence of instructions in the first case, a loop followed by an instruction in the second), implying strict ordering constraints between the elements of the structure. But freezing such ordering relations at the earliest stages of design is not reasonable. Issues such as the number of passes in a compiler and the sequencing of various activities (lexical analysis, parsing, semantic processing, optimization) have many possible solutions, which the designers must devise by considering space-time tradeoffs and other criteria which they do not necessarily master at the beginning of a project. They can perform fruitful design and implementation work on the components long before freezing their temporal ordering, and will want to retain this sequencing freedom for as long as possible. Top-down functional design does not provide such flexibility: you must specify the order of executing operations before you have had a chance to understand properly what these operations will do.

在刚才的例子中我们以二个完全地不同的架构开始.每个都被控制结构(在第一种情况中是一个顺序的指令,第二种情况中是一个循环指令)所定义,在结构元素之间包含着严格的次序约束.但在早期的设计阶段就固定这样的次序关系是不太合理的. 诸如一个编译器的路径数目和各种不同动作(词汇分析,解析,语义处理,最佳化)的顺序性,这样的议题有着许多可能的解决方案,设计者必须通过考虑时间空间的折衷和其它的标准来设计这些方案,在项目的早期阶段不必掌握这些折衷和标准.在冻结它们的时序(temporal ordering)之前,设计者能够在组件的基础上完成卓有成效的设计和实现工作,并将会尽可能长的保持这个次序自由.由上而下的函数设计不能提供如此的灵活性:在你有机会正确理解这些运算动作之前,你必须指定执行运算的顺序.

 

Some design methods that attempt to correct some of the deficiencies of functional top-down design also suffer from this premature binding of temporal relationships. This is the case, among others, with the dataflow-directed method known as structured analysis and with Merise (a method popular in some European countries).

一些设计方法尝试去改正由上而下的函数设计中的一些缺点,也受困于这个时间关系的早期绑定.其中,作为结构化分析的直接数据流方法和Merise方法(物流流程分析法,流行于一些欧洲国家),就是这种情况.

 

Object-oriented development, for its part, stays away from premature ordering. The designer studies the various operations applicable to a certain kind of data, and specifies the effect of each, but defers for as long as possible specifying the operations’ order of execution. This may be called the shopping list approach: list needed operations — all the operations that you may need; ignore their ordering constraints until as late as possible in the software construction process. The result is much more extendible architectures.

在这些当中,面向对象的开发避免了早期顺序.设计者考虑各种不同的运算应用在一个特定的数据类型上,并详细说明每一种运算的效果,但是尽可能迟的描述运算的执行顺序.这可以称之为购物清单(shopping list)方式: 列出了你可能需要所有运算;在软件构造过程中,尽可能迟的考虑其次序约束.这样的结果将是个更易于扩充的架构.

 

Ordering and O-O development

次序和OO开发

 

The observations on the risks of premature ordering deserve a little more amplification because even object-oriented designers are not immune. The shopping list approach is one of the least understood parts of the method and it is not infrequent to see O-O projects fall into the old trap, with damaging effects on quality. This can result in particular from misuse of the use case idea, which we will encounter in the study of O-O methodology.

即使面向对象的设计者也不是不受影响的,所以在早期次序的风险方面的调查有着一点点的夸大.购物清单方式是方法中最难理解的部份之一,而且它常常使OO项目陷入原来问题的沼泽之中,同时破坏了在品质上所作的努力.特别的,从用例(use case)的概念上误用就能看到这种现象,我们将会在OO方法学的研究中遇到用例.

 

The problem is that the order of operations may seem so obvious a property of a system that it will weasel itself into the earliest stages of its design, with dire consequences if it later turns out to be not so final after all. The alternative technique (under the “shopping list” approach), perhaps less natural at first but much more flexible, uses logical rather than temporal constraints. It relies on the assertion concept developed later in this book; we can get the basic idea now through a simple non-software example.

问题是操作次序可能似乎是系统的一个明显特性,以至于不把它投入到最早的设计阶段之中就会产生可怕的后果,但之后证实根本就没有那么重要.另一个可选的技术(购物清单方式之后)使用了逻辑限制而非时间上的,也许起先并不出色但却更加灵活.它依靠在本书中稍后介绍的断言观念;现在我们通过一个简单的非软件上的例子来理解其基本的思想.

 

Consider the problem of buying a house, reduced (as a gross first approximation) to three operations: finding a house that suits you; getting a loan; signing the contract. With a method focusing on ordering we will describe the design as a simple sequence of steps:

[H1]

find_house

get_loan

sign_contract

 

考虑一个买房子的问题,简化(成一个大约的近似值)到三个步骤:发现一个适合您的房子;得到贷款;签署契约.用一个强调次序的方法,我们将会把设计描述成简单的一系列步骤:

[H1]

find_house

get_loan

sign_contract

 

In the shopping list approach of O-O development we will initially refuse to attach too much importance to this ordering property. But of course constraints exist between the operations: you cannot sign a contract unless (let us just avoid saying until for the time being!) you have a desired house and a loan. We can express these constraints in logical rather than temporal form:

在OO开发的购物清单方式中,我们最初会拒绝在次序属性上附加太多的重要东西. 当然在操作中约束依然存在: 你不能够签署一份契约除非(unless,让我们暂时避免说until!)你有一个渴望得到的房子和一份贷款.我们能表达这些约束在逻辑上的而非时间上的形式:

 [H'1]

find_property

ensure

property_found

get_loan

ensure

loan_approved

sign_contract

require

property_found and loan_approved

 

The notation will only be introduced formally in chapter 11, but it should be clear enough here: require states a precondition, a logical property that an operation requires for its execution; and ensure states a postcondition, a logical property that will follow from an operation’s execution. We have expressed that each of the first two operations achieves a certain property, and that the last operation requires both of these properties.

这些符号将会在第11章中被正式地介绍,但是这里表达应该足够清楚了: require声明一个前置条件,一个运算为自己的执行所需要的逻辑属性; ensure声明了一个后置条件,一个将从运算执行中所得出的逻辑属性.我们已经描绘了这些运算,头两个运算中的每一个都完成一个特定的属性,最后一个运算需要这两个属性.

 

Why is the logical form of stating the constraints, H'1, better than the temporal form, H1? The answer is clear: H'1 expresses the minimum requirements, avoiding the overspecification of H1. And indeed H1 is too strong, as it rules out the scheme in which you get the loan first and then worry about the property — not at all absurd for a particular buyer whose main problem is financing. Another buyer might prefer the reverse order; we should support both schemes as long as they observe the logical constraint.

为什么约束声明的逻辑形式H'1,胜于时间形式H1?答案很清楚: H'1表达了最小量的需求,避免了H1的过度规格.并且,H1的确过于强大,因为它脱离了计划,既您先获得了贷款然后再关心房子-对于一个主要问题是融资的特别买主一点也不荒谬.另外的一个买主可能更喜欢倒序;只要他们遵从逻辑的约束,我们就应该支持这两种方案.

 

Now imagine that we turn this example into a realistic model of the process with the many tasks involved — title search, termite inspection, pre-qualifying for the loan, finding a real estate agent, selling your previous house if applicable, inviting your friends to the house-warming party¼ It may be possible to express the ordering constraints, but the result will be complicated and probably fragile (you may have to reconsider everything if you later include another task). The logical constraint approach scales up much more smoothly; each operation simply states what it needs and what it guarantees, all in terms of abstract properties.

现在,设想我们把这个例子变成此过程的一个现实模型,模型中包含了许多项工作-地契查寻,白蚁检查,取得贷款资格,寻找一个不动产代理商,可能的话卖掉您的旧房子,邀请您的朋友参加乔迁宴会这尽可能的表达了那个次序约束,但是这样的结果会很复杂,并且可能会被打乱(如果稍后又包括了另外一项工作,那么您不得不重新考虑每件事).逻辑约束的方式增加了更多的平滑性;每个运算只是简单地表明它所需要的和它所保证的,全部这些都以抽象属性为根据.

 

These observations are particularly important for the would-be object designer, who may still be influenced by functional ideas, and might be tempted to rely on early identification of system usage scenarios (“use cases”) as a basis for analysis. This is incompatible with object-oriented principles, and often leads to top-down functional decomposition of the purest form — even when the team members are convinced that they are using an object-oriented method.

这些结论对未来的对象设计者特别地重要,函数的思维一直影响着他们,同时,作为一种分析基础的系统使用场景(用例)的早期识别也可能吸引着他们有所依赖.这是和面向对象的原则相矛盾的,并时常导致最纯粹形式的由上而下的函数分解-即使团队成员确信他们正在使用一个面向对象的方法.

 

We will examine, in our study of O-O methodological principles, what role can be found for use cases in object-oriented software construction.

在我们的OO方法论原则的研究中,我们将会分析在面向对象软件构造中用例是个什么样的角色.

 

Reusability

复用性

 

After this short advance incursion into the fringes of object territory, let us resume our analysis of the top-down method, considering it this time in relation to one of our principal goals, reusability.

短暂地了解对象领域的大概之后,让我们重新开始我们的由上而下方法的分析,这次考虑我们的主要目标之一,复用性.

 

Working top-down means that you develop software elements in response to particular subspecifications encountered in the tree-like development of a system. At a given point of the development, corresponding to the refinement of a certain node, you will detect the need for a specific function — such as analyzing an input command line — and write down its specification, which you or someone else will then implement.

用由上而下的方法工作意味着您所开发的软件元素反映了特殊的子规格,这会在一个系统的树状开发中遇到.在开发过程中一个给定的点上,相应地对某个特定的节点细化,您会发现对特定函数的需求-如分析一个输入的指令行-并记下它的规格,这些是您或其他人将要去实现的.

 

 


 

The figure, which shows part of a top-down refinement tree, illustrates this property: C2 is written to satisfy some sub-requirement of C; but the characteristics of C2 are entirely determined by its immediate context — the needs of C. For example, C could be a module in charge of analyzing some user input, and C2 could be the module in charge of analyzing one line (part of a longer input).

这个图形,显示了一个由上而下的细化树的一部份,它阐明了这个属性: 编写C2用来满足C的一些子需求;但是C2的特征完全取决于临近的环境-C的需求.例如,C可能是一个负责分析一些用户输入的模块,那么C2可能是负责分析其中一行(长输入的一部份)的模块.

 

This approach is good at ensuring that the design will meet the initial specification, but it does not promote reusability. Modules are developed in response to specific subproblems, and tend to be no more general than implied by their immediate context. Here if C is meant for input texts of a specific kind, it is unlikely that C2, which analyzes one line of those texts, will be applicable to any other kind of input.

这种方式有效地确保了设计符合初始的规格,但是它并不促进复用性.模块为反映特定的子问题而开发,并且试图比它们的临近的环境所暗示的功能更通用.在这里,如果C被定义为一个特定类型的输入文本,那它则不太可能像C2那样分析那些本文中的单行,而是会应用于任何的其它类型的输入.

 

One can in principle include the concern for extendibility and generality in a top-down design process, and encourage developers to write modules that transcend the immediate needs which led to their development. But nothing in the method encourages generalization, and in practice it tends to produce modules with narrow specifications.

大体而言,在由上而下的设计过程中,此类方法能考虑扩充性和一般性,并鼓励开发者编写超越引导他们开发的直接需求的模块.但是方法并不鼓励泛化,并且实际上它趋向产生狭义规格的模块.

 

The very notion of top-down design suggests the reverse of reusability. Designing for reusability means building components that are as general as possible, then combining them into systems. This is a bottom-up process, at the opposite of the top-down idea of starting with the definition of “the problem” and deriving a solution through successive refinements.

由上而下设计的真正的观念暗含着复用性的相反面.为复用性而设计意味着构建尽可能通用的组件,然后在系统中组合它们.由上而下的概念则是以问题的定义开始并经过连续的细化得到解决方案,与此相反,这是一个颠倒的过程.

 

This discussion makes top-down design appear as a byproduct of what we can call the project culture in software engineering: the view that the unit of discourse is the individual project, independently of earlier and later projects. The reality is less simple: project n in a company is usually a variation on project n – 1, and a preview of project n + 1. By focusing on just one project, top-down design ignores this property of practical software construction.

这个讨论使得由上而下的设计看起来就像是在软件工程中称为项目文化(project culture)的一个副产品: 认为会话单元是单独的项目,独立于之前和之后的项目.事实却不那么简单: 一家公司的项目n通常是在项目n–1上的一种变化,并且是项目n+1的一个预演.由于只着眼于一个项目上,由上而下的设计忽略了实际软件构造中的这个属性.

 

Production and description

产品和描述

 

One of the reasons for the original attraction of top-down ideas is that a top-down style may be convenient to explain a design once it is in place. But what is good to document an existing design is not necessarily the best way to produce designs. This point was eloquently argued by Michael Jackson in System Development:

由上而下的思想最初吸引人的理由之一是,在适当的情况下,由上而下的风格可以方便地说明一个设计.但是用之描述一个已存在的方法,并不证明也能用之产生设计.这点被Michael Jackson系统开发中强有力地指出了:

 

Top-down is a reasonable way of describing things which are already fully understood... But top-down is not a reasonable way of developing, designing, or discovering anything. There is a close parallel with mathematics. A mathematical textbook describes a branch of mathematics in a logical order: each theorem stated and proved is used in the proofs of subsequent theorems. But the theorems were not developed or discovered in this way, or in this order...

由上而下是描述一个已经是完全了解的事物的合理方法 但是它并不是开发,设计,或研究一切事务之合理方法.在数学上有一个相似的说法.一本数学的教科书以一个逻辑的顺序描述一个数学分支: 每个被证明过的定理可被用于证明后来的定理.但是定理并不是以这样的方式或顺序被证明和发现的

 

When the developer of a system, or of a program, already has a clear idea of the completed result in his mind, he can use top-down to describe on paper what is in his head. This is why people can believe that they are performing top-down design or development, and doing so successfully: they confuse the method of description with the method of development... When the top-down phase begins, the problem is already solved, and only details remain to be solved.

一个系统的或程序的开发者,当在他的头脑中对完整的结果已经有一个清楚的认识时,他能使用由上而下的方式书面地描述出他的思维.这正是人们为什么能相信他们一直在进行由上而下的设计或开发,而且做如此的成功: 他们把描述的方法和开发的方法弄混了当由上而下的阶段开始的时候,问题其实已经被解决了,只剩下了细节.

 

Top-down design: an assessment

由上而下设计的评估

 

This discussion of top-down functional design shows the method to be poorly adapted to the development of significant systems. It remains a useful paradigm for small programs and individual algorithms; it is certainly a helpful technique to describe well-understood algorithms, especially in programming courses. But it does not scale up to large practical software. By developing a system top-down you trade short-term convenience for long-term inflexibility; you unduly privilege one function over the others; you may be led to devoting your attention to interface characteristics at the expense of more fundamental properties; you lose sight of the data aspect; and you risk sacrificing reusability.

有关由上而下的函数设计的讨论表明了此方法不能适应重要的系统开发.它对于小型的程序和独立的算法倒是一个有用的样例;对于描述易于理解的算法,尤其在程序设计课程方面,它的确是有效的技术.但是它不能应用到大型的应用软件中去.通过由上而下的方法来开发一个系统,您会用短期的方便换取长期的稳定性;您会错误地给于一个函数超过其它函数的特权;这也许导致您专心于接口特征,而损害更多的基本属性;您会对数据视而不见;并且,您会冒着牺牲复用性的风险.

 



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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值