From the goals of extendibility and reusability, two of the principal quality factors introduced in chapter 1, follows the need for flexible system architectures, made of autonomous software components. This is why chapter 1 also introduced the term modularity to cover the combination of these two quality factors.
扩充性和复用性这两个主要的品质因数在第1章介绍过了,从其目标来看,它们遵循了灵活的系统架构的需求,并由独立的软件组件组成.这也是第1章会介绍术语模块性来包括这二个品质因数组合的原因.
Modular programming was once taken to mean the construction of programs as assemblies of small pieces, usually subroutines. But such a technique cannot bring real extendibility and reusability benefits unless we have a better way of guaranteeing that the resulting pieces — the modules — are self-contained and organized in stable architectures. Any comprehensive definition of modularity must ensure these properties.
一旦模块编程被采用,就意味着程序的构造如同散件组装,这些散件通常是些子程序.但是这样的技术不能够带来真正的扩充性和复用性的利益,除非我们有更好的方法来保证最终的散件—模块(module)—在稳定的架构中是独立的和组织化的.任何完整的模快性定义都必须确保这些属性.
A software construction method is modular, then, if it helps designers produce software systems made of autonomous elements connected by a coherent, simple structure. The purpose of this chapter is to refine this informal definition by exploring what precise properties such a method must possess to deserve the “modular” label. The focus will be on design methods, but the ideas also apply to earlier stages of system construction (analysis, specification) and must of course be maintained at the implementation and maintenance stages.
那么,如果一个系统是由独立的元素以连贯简单的结构所连接而成,一个软件构造方法帮助设计者产生了这样的软件系统,那么这个方法就是模块化的.通过研究把一个方法进行”模块化”的一些精确属性,来改善这个非正式的定义,这是本章的目的.焦点会集中在设计方式上,但是其思想也适用于系统构造(分析,规格)的早期阶段,当然也必须在实现和维护阶段继续实现.
As it turns out, a single definition of modularity would be insufficient; as with software quality, we must look at modularity from more than one viewpoint. This chapter introduces a set of complementary properties: five criteria, five rules and five principles of modularity which, taken collectively, cover the most important requirements on a modular design method.
事实上,一个单一的模块性的定义是不够的;同时由于软件品质,我们必须从多方面来了解模块性. 这章介绍一组补充的属性: 模块性的五条标准(criteria),五件规则(rules)和五项原则(principles), 全部的这些涵盖了一个模块设计方式上的最重要的要求.
For the practicing software developer, the principles and the rules are just as important as the criteria. The difference is simply one of causality: the criteria are mutually independent — and it is indeed possible for a method to satisfy one of them while violating some of the others — whereas the rules follow from the criteria and the principles follow from the rules.
对于一个专业的软件开发者,原则和规则是同标准一样的重要.不同之处仅是一个因果关系: 标准是互相独立的— 对于一个方法而言,确实可能满足了其中的一个却违犯了其它的标准 —然而规则从标准中得出,原则从规则中而来.
You might expect this chapter to begin with a precise description of what a module looks like. This is not the case, and for a good reason: our goal for the exploration of modularity issues, in this chapter and the next two, is precisely to analyze the properties which a satisfactory module structure must satisfy; so the form of modules will be a conclusion of the discussion, not a premise. Until we reach that conclusion the word “module” will denote the basic unit of decomposition of our systems, whatever it actually is. If you are familiar with non-object-oriented methods you will probably think of the subroutines present in most programming and design languages, or perhaps of packages as present in Ada and (under a different name) in Modula. The discussion will lead in a later chapter to the O-O form of module — the class — which supersedes these ideas. If you have encountered classes and O-O techniques before, you should still read this chapter to understand the requirements that classes address, a prerequisite if you want to use them well.
您可能会认为,本章应该从模块特征之精确描述开始.但却不是这样的,并且有一个很好的理由:在这章和下两章中,为了探求模块性的议题,我们的目标是要精确地分析一个符合要求的模块结构所必须满足的属性;因此模块的形式将会是讨论的结论而不是前提.在我们得出结论之前,”模块”这个字将一直表示系统中可分解的基本单元,而无论实际上是什么.如果您熟悉非面向对象的方法,您可能会想到在大多数的程序和设计语言中的子程序的表现形式,如同是在Ada和(在不同的名字之下)Modula中所呈现的那样.在下面的章节中,讨论将会引导出模块的OO形式—类—来代替这些想法.如果您以前遇到过类和OO技术,您应该仍然阅读本章来了解类的作用,如果您想更好的使用它们,这是必须要看的.
3.1 FIVE CRITERIA
3.1 五条标准
A design method worthy of being called “modular” should satisfy five fundamental requirements, explored in the next few sections:
被称为”模块”的设计方式应该满足五条基本的条件,我们将在下面的部分中一一的讨论:
• Decomposability. 分解性
• Composability. 组合性
• Understandability. 理解性
• Continuity. 连续性
• Protection. 保护性
Modular decomposability
A software construction method satisfies Modular Decomposability if it helps in the task of decomposing a software problem into a small number of less complex subproblems, connected by a simple structure, and independent enough to allow further work to proceed separately on each of them
如果一个软件构造方法能把一个软件问题分解成很多较简单的子问题,并把这些子问题用一个简单的架构连接起来,而且能够完全独立的在每一个子问题上再更进一步的分解,那么,这个方法就满足模块的分解性.
模块分解性
The process will often be self-repeating since each subproblem may still be complex enough to require further decomposition.
由于每个子问题可能仍然十分复杂,需要进一步的分解,这个过程就将不断的循环往复.
A corollary of the decomposability requirement is division of labor: once you have decomposed a system into subsystems you should be able to distribute work on these subsystems among different people or groups. This is a difficult goal since it limits the dependencies that may exist between the subsystems:
分解性需求的必然结果是人工的分配: 一旦您已经把一个系统分解成子系统,您应该能够把这些子系统的工作分配给不同的人或组.由于在各个子系统之间存在的关联的限制,这会是一个困难的目标:
• You must keep such dependencies to the bare minimum; otherwise the development of each subsystem would be limited by the pace of the work on the other subsystems.
您必须把这样的关联尽可能的减小到最少;否则每个子系统的开发会受制于其它子系统的工作进度.
• The dependencies must be known: if you fail to list all the relations between subsystems, you may at the end of the project get a set of software elements that appear to work individually but cannot be put together to produce a complete system satisfying the overall requirements of the original problem.
必须要了解关联: 如果您不能列出子系统之间的所有关系,您可以在项目结束的时候得到一系列的软件元素,这些组件看上去能独立工作,但是却不能够被整合成一个完整的系统来满足最初问题的全部需求.
The most obvious example of a method meant to satisfy the decomposability criterion is top-down design. This method directs designers to start with a most abstract description of the system’s function, and then to refine this view through successive steps, decomposing each subsystem at each step into a small number of simpler subsystems, until all the remaining elements are of a sufficiently low level of abstraction to allow direct implementation. The process may be modeled as a tree.
符合分解性标准的方法中最明显的例子是由上而下设计(top-down design).这种方法指导设计者由系统函数的一个最抽象描述开始,然后经过连续的步骤精炼这个描述,在每个步骤中把每个子系统分解成几个更简单的子系统,直到所有的元素抽象化程度都足够低到可以直接实现.这个过程可以模拟成树状.
A typical counter-example is any method encouraging you to include, in each software system that you produce, a global initialization module. Many modules in a system will need some kind of initialization — actions such as the opening of certain files or the initialization of certain variables, which the module must execute before it performs its first directly useful tasks. It may seem a good idea to concentrate all such actions, for all modules of the system, in a module that initializes everything for everybody. Such a module will exhibit good “temporal cohesion” in that all its actions are executed at the same stage of the system’s execution. But to obtain this temporal cohesion the method would endanger the autonomy of modules: you will have to grant the initialization module authorization to access many separate data structures, belonging to the various modules of the system and requiring specific initialization actions. This means that the author of the initialization module will constantly have to peek into the internal data structures of the other modules, and interact with their authors. This is incompatible with the decomposability criterion.
一个典型的反例是,在您创造的每个软件系统中,所有的方法都鼓励您包括一个全局的初始化模块.一个系统中的许多模块需要多种多样的初始化—如打开某个文件或初始化某些变量之类的行为,在运行第一个直接有效的任务之前,模块必须要运行这些初始化.把所有的初始化行为都集中起来,在一个模块中为系统的所有部分做初始化,这看上去是个不错的主意.所有的这些动作都在系统执行的相同阶段执行,这样的模块将会展示良好的”时间性内聚(temporal cohesion)”.但是为了获得这种时间性内聚,这种方法会危及模块的自治:您将不得不授权初始化模块访问许多独立的数据结构,这些数据结构属于系统的各种不同的模块而且需要特定的初始化行为.这意谓初始化模块的作者将会经常地看到其它模块的内部数据结构,而且和它们的作者交互.这与分解性标准不相容.
In the object-oriented method, every module will be responsible for the initialization of its own data structures.
在面向对象的方法中,每个模块将会负责它自己数据结构的初始化.
Modular composability
模块组合性
A method satisfies Modular Composability if it favors the production of software elements which may then be freely combined with each other to produce new systems, possibly in an environment quite different from the one in which they were initially developed.
一些软件元素在不同于它们最初被开发的环境中,彼此之间可以自由组合以产生新的系统,如果一个方法支持这种编写方式,那么这种方法满足模块的组合性.
Where decomposability was concerned with the derivation of subsystems from overall systems, composability addresses the reverse process: extracting existing software elements from the context for which they were originally designed, so as to use them again in different contexts.
这里,分解性关注于把整个系统分解成子系统,而组合性从事相反的过程:从原始设计的环境中提取出现存的软件元素,使得在不同的环境中再一次使用它们.
A modular design method should facilitate this process by yielding software elements that will be sufficiently autonomous — sufficiently independent from the immediate goal that led to their existence — as to make the extraction possible.
一个模块的设计方法应该通过产生完全独立的软件元素促进这样的过程—从导致它们存在的直接目标中充份地独立—直到能够使它们提取出来.
Composability is directly connected with the goal of reusability: the aim is to find ways to design software elements performing well-defined tasks and usable in widely different contexts. This criterion reflects an old dream: transforming the software design process into a construction box activity, so that we would build programs by combining standard prefabricated elements.
组合性直接地与复用性的目标相关联:目标是要找到方法设计软件元素,来完成明确定义的任务和在广泛不同的环境中使用.这一个标准反映了一个旧梦:把软件设计过程转换成一个建筑箱体预制板的行为,这样我们就能组合标准预制元素来建造程序.
• Example 1: subprogram libraries. Subprogram libraries are designed as sets of composable elements. One of the areas where they have been successful is numerical computation, which commonly relies on carefully designed subroutine libraries to solve problems of linear algebra, finite elements, differential equations etc.
例1:子程序库,子程序库被设计成一组可组合的元素.一个成功的领域是数值计算,这通常建立在那些仔细设计的子程序库之上,这些库用来解决线性代数,有限元,微分方程等等之类的问题.
• Example 2: Unix Shell conventions. Basic Unix commands operate on an input viewed as a sequential character stream, and produce an output with the same standard structure. This makes them potentially composable through the | operator of the command language (“shell”): A | B represents a program which will take A’s input, have A process it, send the output to B as input, and have it processed by B. This systematic convention favors the composability of software tools.
例2: Unix命令行定义.基本的Unix指令把输入操作看成一个连续的字符流,而且用同样的标准结构产生一个输出.这使它们通过命令语言(“shell”)中的操作符| 来潜在的组合起来: A | B表示一个程序输入A,并输出B.这个系统的定义证实了软件工具的组合性.
• Counter-example: preprocessors. A popular way to extend the facilities of programming languages, and sometimes to correct some of their deficiencies, is to use “preprocessors” that accept an extended syntax as input and map it into the standard form of the language. Typical preprocessors for Fortran and C support graphical primitives, extended control structures or database operations. Usually, however, such extensions are not compatible; then you cannot combine two of the preprocessors, leading to such dilemmas as whether to use graphics or databases.
反例: 预处理程序.为了扩充程序语言的工具,有时改正一些它们的不足,一个流行的方法是使用"预处理程序",来接受一个扩充的语法作为输入,并把它映射进语言的标准形式之内.典型的FORTRAN和C的预处理程序支持基本的图形,延伸结构控制或数据库操作. 然而,通常这样的扩充并不相容;结果您不能够连接二个预处理程序,这导致了是使用图形还是数据库诸如此类的困境.
Composability is independent of decomposability. In fact, these criteria are often at odds. Top-down design, for example, which we saw as a technique favoring decomposability, tends to produce modules that are not easy to combine with modules coming from other sources. This is because the method suggests developing each module to fulfill a specific requirement, corresponding to a subproblem obtained at some point in the refinement process. Such modules tend to be closely linked to the immediate context that led to their development, and unfit for adaptation to other contexts. The method provides neither hints towards making modules more general than immediately required, nor any incentives to do so; it helps neither avoid nor even just detect commonalities or redundancies between modules obtained in different parts of the hierarchy.
组合性与分解性无关.事实上,这些标准时常不一致.举例来说,我们把由上而下的设计看作是一个支持分解性的技术,它试图产生的模块并不能轻易地连接来自其它源代码的模块.这是因为该方法建议开发各自的模块实现特定的需求,以符合在细化的过程中某些点上产生的子问题.这样的模块趋向于接近它们所开发的直接的上下文环境,并不能改编以适应其它的环境.此方法既没有提供使模块比直接需求更泛化的线索,也不能提供这么做的动机;它既不能帮助避免甚至不能发现在不同层次中所需模块之间的共性或冗余.
That composability and decomposability are both part of the requirements for a modular method reflects the inevitable mix of top-down and bottom-up reasoning — a complementarity that René Descartes had already noted almost four centuries ago, as shown by the contrasting two paragraphs of the Discourse extract at the beginning of part B.
对于一个反映了由上而下和由下而上的推论的必然混合的模块化方法,组合性和分解性两者皆是需求的部份—在B部份开头,两幅从Discourse截取的差别明显的图形,显示了René Descartes几乎在四个世纪以前就已经提出了一个补充.
Modular understandability
模块理解性
A method favors Modular Understandability if it helps produce software in which a human reader can understand each module without having to know the others, or, at worst, by having to examine only a few of the others.
如果用某个方法帮助编写软件,读者能在不了解其它模块的情况下理解每个模块,或者,最坏也只须了解少数其它的模块.那么,这个方法支持模块的理解性.
The importance of this criterion follows from its influence on the maintenance process. Most maintenance activities, whether of the noble or not-so-noble category, involve having to dig into existing software elements. A method can hardly be called modular if a reader of the software is unable to understand its elements separately.
这个标准的重要性是影响随后的维护过程.大多数的维护行为,不管是重要的或是不重要的,都不得不深入理解已存在的软件元素.如果软件读者(维护人员)不能够独立的理解这些元素,那么这个方法很难被称之为模块化的.
This criterion, like the others, applies to the modules of a system description at any level: analysis, design, implementation.
和其它的一样,这个标准适用于任何层次的系统描述模块:分析,设计,实现.
• Counter-example: sequential dependencies. Assume some modules have been so designed that they will only function correctly if activated in a certain prescribed order; for example, B can only work properly if you execute it after A and before C, perhaps because they are meant for use in “piped” form as in the Unix notation encountered earlier:
A | B | C
Then it is probably hard to understand B without understanding A and C too.
反例: 连续的关联. 假设一些模块由于设计的原因,以至于它们只能在特定的命令顺序下才正确地动作;举例来说,如果您在A之后C之前运行B,它才能正确地工作,也许因为这就是在先前遇到的Unix符号中”管道(piped)”的使用形式:
A | B | C
那么,不了解A和C的情况下,也不可能理解B.
In later chapters, the modular understandability criterion will help us address two important questions: how to document reusable components; and how to index reusable components so that software developers can retrieve them conveniently through queries. The criterion suggests that information about a component, useful for documentation or for retrieval, should whenever possible appear in the text of the component itself; tools for documentation, indexing or retrieval can then process the component to extract the needed pieces of information. Having the information included in each component is preferable to storing it elsewhere, for example in a database of information about components.
在稍后的章节中,模块的理解性标准将帮助我们处理二个重要的问题:如何编写可复用组件的文档;如何索引可复用组件以便软件开发者能够通过查询方便地得到它们.这个标准建议,对于文档或检索都有用的组件信息,应该尽可能的出现在组件本身的文本中;文档,索引或检索的工具能各取所需的信息.有些包含在各自组件中的信息储存在别处可能更好,如在数据库中about组件.
Modular continuity
模块连续性
A method satisfies Modular Continuity if, in the software architectures that it yields, a small change in a problem specification will trigger a change of just one module, or a small number of modules.
在使用某个方法产生的软件结构中,如果在一个问题规格方面的微小改变将只会引起一个模块的变化,或少量模块的变化.那么,这个方法满足模块的连续性.
This criterion is directly connected to the general goal of extendibility. As emphasized in an earlier chapter, change is an integral part of the software construction process. The requirements will almost inevitably change as the project progresses. Continuity means that small changes should affect individual modules in the structure of the system, rather than the structure itself.
这个标准直接地关联了扩充性的大致目标.如先前的章节所强调,变化是软件构造过程的主要部份.在项目开发的过程中这样变化是不可避免的.连续性意味着微小的变化应该影响系统结构中的个别的模块,而并非结构本身.
The term “continuity” is drawn from an analogy with the notion of a continuous function in mathematical analysis. A mathematical function is continuous if (informally) a small change in the argument will yield a proportionally small change in the result. Here the function considered is the software construction method, which you can view as a mechanism for obtaining systems from specifications:
术语”连续性”是从数学分析里的连续函数观念的类推中描绘出来的.如果(非正式地)参数的微小改变会引起结果中的等比例地变化,那么这个数学函数就是连续的.这里所考虑的函数是软件构造方法,您能观察到从规格中获取系统的机制:
software_construction_method: Specification ® System
This mathematical term only provides an analogy, since we lack formal notions of size for software. More precisely, it would be possible to define a generally acceptable measure of what constitutes a “small” or “large” change to a program; but doing the same for the specifications is more of a challenge. If we make no pretense of full rigor, however, the concepts should be intuitively clear and correspond to an essential requirement on any modular method.
由于我们缺乏软件尺寸大小的观念,因此这个数学术语只是提供一个类推的结果.更精确的,对于程序的一个”小的”或”大的”变化程度,它只是尽可能地定义一个一般的可接受的衡量标准;但是对规格来说做相同的事却有更多的挑战.然而,如果我们并没有要求那么严格的话,这个概念应该是直观清晰的并且符合在任何的模块方法上的实质需求.
• Example 1: symbolic constants. A sound style rule bars the instructions of a program from using any numerical or textual constant directly; instead, they rely on symbolic names, and the actual values only appear in a constant definition (constant in Pascal or Ada, preprocessor macros in C, PARAMETER in Fortran 77, constant attributes in the notation of this book). If the value changes, the only thing to update is the constant definition. This small but important rule is a wise precaution for continuity since constants, in spite of their name, are remarkably prone to change.
例1: 符号常数. 一条合理的规则禁止把任何数字或本文常数直接地使用在程序的指令中;而是依赖于符号名,并且真实的数值只在一个常数定义中出现(Pascal或Ada的常数(constant),C的预处理程序宏, Fortran 77的叁数(PARAMETER),在本书符号中的常数属性).如果数值变化,唯一要做的是更新其常数定义.尽管它们的名字(是常数),但常数都有显著地改变倾向,所以这条小的但重要的规则对于连续性是一种明智地预防.
• Example 2: the Uniform Access principle. Another rule states that a single notation should be available to obtain the features of an object, whether they are represented as data fields or computed on demand. This property is sufficiently important to warrant a separate discussion later in this chapter.
例2:统一存取原则. 另外的一条规则规定了一个单一符号应该能有效地获得对象的特性,不管是否它们被表现为数据字段或请求计算上.这个属性对于保证本章中稍后的另一个讨论非常重要.
• Counter-example 1: using physical representations. A method in which program designs are patterned after the physical implementation of data will yield designs that are very sensitive to slight changes in the environment.
反例1:使用实际表示法.一个方法,在其中程序设计会在数据实际实现之后被模式化,这将会产生对环境方面轻微的改变都非常敏感的设计.
• Counter-example 2: static arrays. Languages such as Fortran or standard Pascal, which do not allow the declaration of arrays whose bounds will only be known at run time, make program evolution much harder.
反例2: 静态数组.象Fortran或标准Pascal这样的语言,不允许只能在运行的时候才知道绑定的数组声明,这将使程序发展更加困难.
Modular protection
模块保护性
A method satisfies Modular Protection if it yields architectures in which the effect of an abnormal condition occurring at run time in a module will remain confined to that module, or at worst will only propagate to a few neighboring modules.
如果一个方法产生的结构,在其中一个运行的模块里发生的反常状态将保持限制在当前的模块中,或最坏也只将会影响到少数附近的模块,那么,这个方法满足模块的保护性.
The underlying issue, that of failures and errors, is central to software engineering. The errors considered here are run-time errors, resulting from hardware failures, erroneous input or exhaustion of needed resources (for example memory storage). The criterion does not address the avoidance or correction of errors, but the aspect that is directly relevant to modularity: their propagation.
在下面的议题中,失败和错误对软件工程来说都是重要的.在这里所考虑的错误是运行时错误,起因于硬件的失败,错误的输入或所需资源(例如内存储存)的耗尽.这个标准并没有专注于错误的避免或校正,但是却提出了直接与模块化有关的方面:它们的传播.
• Example: validating input at the source. A method requiring that you make every module that inputs data also responsible for checking their validity is good for modular protection.
例: 源输入的确认.一个方法要求您对可以输入数据的每个模块也要有责任检查数据的有效性,这有益于模块的保护.
• Counter-example: undisciplined exceptions. Languages such as PL/I, CLU, Ada
, C++ and Java support the notion of exception. An exception is a special signal that may be “raised” by a certain instruction and “handled” in another, possibly remote part of the system. When the exception is raised, control is transferred to the handler. (Details of the mechanism vary between languages; Ada
or CLU are more disciplined in this respect than PL/I.) Such facilities make it possible to decouple the algorithms for normal cases from the processing of erroneous cases. But they must be used carefully to avoid hindering modular protection. The chapter on exceptions will investigate how to design a disciplined exception mechanism satisfying the criterion.
反例:不健全的异常.像是PL/I, CLU, Ada, C++ 和Java这样的语言支持异常的观念.异常是一个特别的信号,可以被特定的指令”抛出”,并且在别处,有可能是系统的远程部份被”处理”.当异常被抛出的时候,控制被转移到处理程序.(在语言之间此机制的细节有所不同;Ada或CLU在这方面比PL/I.更科学.) 这样的方法尽可能的在错误情况处理中减轻对正常情况下算法的影响.但是必须小心地使用它们,避免妨碍模块的保护性.在介绍异常的章节中将会研究该如何设计一个健全的异常机制来满足此标准.
3.2 FIVE RULES
3.2 五件规则
From the preceding criteria, five rules follow which we must observe to ensure modularity:
在上述标准之后的是五件规则,我们必须遵守它以确保模块性.
• Direct Mapping. 直接映射
• Few Interfaces. 少量接口
• Small interfaces (weak coupling). 小型接口(弱耦合)
• Explicit Interfaces. 精确接口
• Information Hiding. 信息隐藏
The first rule addresses the connection between a software system and the external systems with which it is connected; the next four all address a common issue — how modules will communicate. Obtaining good modular architectures requires that communication occur in a controlled and disciplined way.
第一件规则描述了一个软件系统和外部系统之间的连结关系; 余下的全部四件描述了一个普通的议题—模块之间如何沟通.获得一个良好的模块架构需要一个易控制而且健全的通讯方式.
Direct Mapping
直接映射
Any software system attempts to address the needs of some problem domain. If you have a good model for describing that domain, you will find it desirable to keep a clear correspondence (mapping) between the structure of the solution, as provided by the software, and the structure of the problem, as described by the model. Hence the first rule:
任何的软件系统都尝试着描述一些问题域(problem domain)的需求.如果您有一个好的模型来描述那些领域,在由软件提供解决方案的结构和由模型所描述的问题结构之间,您将会发现保持清晰的通信(映射)是值得的.第一条规则由此而来:
The modular structure devised in the process of building a software system should remain compatible with any modular structure devised in the process of modeling the problem domain.
在构建一个软件系统的过程中所设计的模块结构应该与在问题域模型化的过程中所设计的模块结构保持兼容.
This advice follows in particular from two of the modularity criteria:
这条建议特别遵循模块化标准中的二条:
• Continuity: keeping a trace of the problem’s modular structure in the solution’s structure will make it easier to assess and limit the impact of changes.
连续性:在解决方案的结构中保存问题的模块结构的轨迹将使评估和限定变化所造成的影响变得更容易.
• Decomposability: if some work has already been done to analyze the modular structure of the problem domain, it may provide a good starting point for the modular decomposition of the software.
分解性:如果某些工作已经完成了分析问题域的模块结构,那么对于软件的模块分解,它可以提供一个良好的开端.
Few Interfaces
少量接口
The Few Interfaces rule restricts the overall number of communication channels between modules in a software architecture:
少量接口的规则限制了在一个软件架构中模块之间的通讯频道的全部数量:
Every module should communicate with as few others as possible.
每个模块应该尽可能少的与其它模块沟通.
Communication may occur between modules in a variety of ways. Modules may call each other (if they are procedures), share data structures etc. The Few Interfaces rule limits the number of such connections.
通讯可以以多种方式在模块之间发生.模块之间可以彼此调用(如果它们是程序),共享数据结构等等. 少量接口的规则限制了这样的连接数量.
More precisely, if a system is composed of n modules, then the number of intermodule connections should remain much closer to the minimum, n–1, shown as (A) in the figure, than to the maximum, n (n – 1) /2, shown as (B).
更精确的,如果一个系统由n个模块组成,那么模块之间的连接数目应该尽量保持接近最小量n–1,如(A)图所示,而不是如(B)所示的最大值n(n – 1)/2.
This rule follows in particular from the criteria of continuity and protection: if there are too many relations between modules, then the effect of a change or of an error may propagate to a large number of modules. It is also connected to composability (if you want a module to be usable by itself in a new environment, then it should not depend on too many others), understandability and decomposability.
这件规则特别遵循连续性和保护性的标准:如果在模块之间有太多关系,那么一个变化的结果或一个错误可能影响到大量的模块.它也关系到组合性(如果您想让一个模块在新的环境中仍然可用,那么它不应该依赖于太多其它的模块),理解性和分解性.
Case (A) on the last figure shows a way to reach the minimum number of links, n – 1, through an extremely centralized structure: one master module; everybody else talks to it and to it only. But there are also much more “egalitarian” structures, such as (C) which has almost the same number of links. In this scheme, every module just talks to its two immediate neighbors, but there is no central authority. Such a style of design is a little surprising at first since it does not conform to the traditional model of functional, top-down design. But it can yield robust, extendible architectures; this is the kind of structure that object-oriented techniques, properly applied, will tend to yield.
在上图中,情况(A)显示了通过极端集中的结构, 达到最小链接数目n–1的方法:一个主模块;每个模块只能和它对话.但是也有更加"平行"的结构,如(C)图,有几乎相同数目的链接.在这个方案中,每个模块仅仅和它的二个紧邻的邻居对话,而没有中央的授权.由于它并不符合功能性的,由上而下的传统设计模型,如此的设计风格起先稍微令人惊讶.但是它能产生健壮的,易扩充的架构;这种结构,适当地加工就可以产生面向对象技术的结构类型.
Small Interfaces
小型接口(弱耦合)
The Small Interfaces or “Weak Coupling” rule relates to the size of intermodule connections rather than to their number:
If two modules communicate, they should exchange as little information as possible
如果二个模块沟通,它们应该交换尽可能少的信息.
小型接口或”弱耦合”规则涉及到模块之间连接的大小,而不是它们的数目:
An electrical engineer would say that the channels of communication between modules must be of limited bandwidth:
一个电子工程师会说,在模块之间的通信频道一定会被限制带宽:
The Small Interfaces requirement follows in particular from the criteria of continuity and protection.
小型接口的需求特别的来自连续性和保护性标准.
An extreme counter-example is a Fortran practice which some readers will recognize: the “garbage common block”. A common block in Fortran is a directive of the form
COMMON /common_name/ variable1,¼ variablen
indicating that the variables listed are accessible not just to the enclosing module but also to any other module which includes a COMMON directive with the same common_name. It is not infrequent to see Fortran systems whose every module includes an identical gigantic COMMON directive, listing all significant variables and arrays so that every module may directly use every piece of data.
一个极端的反例是一个Fortran的习惯,一些读者将会了解到: “垃圾公用块”.在Fortran中一个公用块如下列指令:
COMMON /common_name/ variable1,¼ variablen
表明了可被接受的变量列表, 不但是在外覆的模块中,而且可以是包含在COMMON指令中common_name的其它模块.常常会见到在Fortran系统中的每一个模块包含了一样的巨大的COMMON指令,列出了所有重要的变量和数组,以至于各个模块可以直接使用每一个数据段.
The problem, of course, is that every module may also misuse the common data, and hence that modules are tightly coupled to each other; the problems of modular continuity (propagation of changes) and protection (propagation of errors) are particularly nasty. This time-honored technique has nevertheless remained a favorite, no doubt accounting for many a late-night debugging session.
当然,问题是每个模块也可能过分使用共用数据,并且导致了模块间彼此紧密耦合;(所产生的)模块连续性(变化的传播)和保护性(错误的传播)的问题特别地麻烦.这种由来已久的技术仍然被人乐于使用,毫无疑问地造成了许多除错上的艰难.
Developers using languages with nested structures can suffer from similar troubles. With block structure as introduced by Algol and retained in a more restricted form by Pascal, it is possible to include blocks, delimited by begin ¼ end pairs, within other blocks. In addition every block may introduce its own variables, which are only meaningful within the syntactic scope of the block. For example:
开发者使用具有嵌套结构的语言忍受着相似的麻烦.Algol引入了块结构而在Pascal保留了更多方面的限制形式,它可能在其它的块里面包括了被begin ¼ end分割的块.除此之外每个块可能声明自己的变量,这些变量只在块的语法范围内才有意义.举例来说:
local-- Beginning of block B1
x, y: INTEGER
do
¼ Instructions of B1 ¼
local -- Beginning of block B2
z: BOOLEAN
do
¼ Instructions of B2 ¼
end --- of block B2
local -- Beginning of block B3
y, z: INTEGER
do
¼ Instructions of B3 ¼
end -- of block B3
¼ Instructions of B1 (continued) ¼
end -- of block B1
Variable x is accessible to all instructions throughout this extract, whereas the two variables called z (one BOOLEAN, the other INTEGER) have scopes limited to B2 and B3 respectively. Like x, variable y is declared at the level of B1, but its scope does not include B3, where another variable of the same name (and also of type INTEGER) locally takes precedence over the outermost y. In Pascal this form of block structure exists only for blocks associated with routines (procedures and functions).
在这段代码中,所有的指令都可以访问变量x,而二个被定义为z(一个布尔类型BOOLEAN,另一个整数类型INTEGER)的变量分别地限制在B2和B3块的范围中.像x一样,变量y在B1层次被定义,但是其访问范围不包括B3,这里相同名字(也是整数类型)的另外一个变量比最上层的y在本块具有优先权.在Pascal中,这种块结构的形式只存在于和例程相关的区块(程序及函数)中.
With block structure, the equivalent of the Fortran garbage common block is the practice of declaring all variables at the topmost level. (The equivalent in C-based languages is to introduce all variables as external.)
和块结构一样,与Fortran垃圾公用块等效的是在最高的层次中声明所有变量的动作.(以C为基础的语言的相似之处是以外部形式(external)定义所有的变量).
Block structure, although an ingenious idea, introduces many opportunities to violate the Small Interfaces rule. For that reason we will refrain from using it in the objectoriented notation devised later in this book, especially since the experience of Simula, an object-oriented Algol derivative supporting block structure, shows that the ability to nest classes is redundant with some of the facilities provided by inheritance. The architecture of object-oriented software will involve three levels: a system is a set of clusters; a cluster is a set of classes; a class is a set of features (attributes and routines). Clusters, an organizational tool rather than a linguistic construct, can be nested to allow a project leader to structure a large system in as many levels as necessary; but classes as well as features have a flat structure, since nesting at either of those levels would cause unnecessary complication.
块结构,虽然是一个精巧的主意,但有许多地方违犯了小型接口的规则.出于这个理由,在本书后面的面向对象符号设计中,我们将尽量不使用它,尤其,由于从面向对象语言Algol派生出的支持块结构的语言Simula的经验,显示了嵌套类的能力对一些由继承提供的功能而言是多余的.面向对象的软件架构包括三个层次: 一个系统是一组群集;一个群集是一组类;一个类是一组特性(属性和例程).群集, 一个组织工具并非一个语言学结构,可以被嵌套以允许一位项目主管以尽可能多的层次构造大的系统;但是类和特性一样只有平坦的结构,是由于在其中任何一个层次上的嵌套都会引起不必要的复杂性.
Explicit Interfaces
精确接口
With the fourth rule, we go one step further in enforcing a totalitarian regime upon the society of modules: not only do we demand that any conversation be limited to few participants and consist of just a few words; we also require that such conversations must be held in public and loudly!
Whenever two modules A and B communicate, this must be obvious from the text of A or B or both.
只要A和B两个模块通讯, 在A或B或两者之间必须要有明确的文字.
有着这第四件规则,我们要更进一步,在模块的社区里强加上一个极权主义的政权: 我们不但要求任何会话被限制在少数的参加者之间同时只包含少量的单词;而且我们命令这样的会话必须保持公开和大声!
Behind this rule stand the criteria of decomposability and composability (if you need to decompose a module into several submodules or compose it with other modules, any outside connection should be clearly visible), continuity (it should be easy to find out what elements a potential change may affect) and understandability (how can you understand A by itself if B can influence its behavior in some devious way?).
在这一件规则后面由分解性和组合性(如果您需要把一个模块分解成一些子模块或与其它的模块合并,任何的外部连接应该清楚可见),连续性(应该很容易地发现什么元素会被潜在的变化所影响)和理解性(如果B能以一些迂回的方式影响A的行为,您如何能单独地理解A?)的标准支持着.
One of the problems in applying the Explicit Interfaces rule is that there is more to intermodule coupling than procedure call; data sharing, in particular, is a source of indirect coupling:
在应用精确接口的规则上,有一个问题是相比程序调用而言,存在着更多的模块间偶合; 尤其,数据共享就是间接偶合(indirect coupling)的一个来源:
Assume that module A modifies and module B uses the same data item x. Then A and B are in fact strongly coupled through x even though there may be no apparent connection, such as a procedure call, between them.
假设模块A和模块B使用相同的数据项目x.那么,事实上A和B是通过x的强偶合,即使它们之间可能没有像程序调用那样的明显的连接.
Information Hiding
信息隐藏
The rule of Information Hiding may be stated as follows:
信息隐藏的规则可以陈述如下:
The designer of every module must select a subset of the module’s properties as the official information about the module, to be made available to authors of client modules.
每个模块的设计者必须选择模块属性的一个子集作为有关此模块的正式信息,其可以让客户端模块的作者有效使用.
Application of this rule assumes that every module is known to the rest of the world (that is to say, to designers of other modules) through some official description, or public properties.
这条规则的应用设想了每个模块通过一些正式的描述,或公共的(public)属性为其它的世界(也就是说,对其它的模块设计者)所知.
Of course, the whole text of the module itself (program text, design text) could serve as the description: it provides a correct view of the module since it is the module! The Information Hiding rule states that this should not in general be the case: the description should only include some of the module’s properties. The rest should remain non-public, or secret. Instead of public and secret properties, one may also talk of exported and private properties. The public properties of a module are also known as the interface of the module (not to be confused with the user interface of a software system).
当然,模块的整个代码(程序代码,设计代码)本身可以做为模块的描述:由于它就是模块,所以它提供了正确的模块视图! 通常,信息隐藏规则表示并非如此:描述应该只包括模块属性中的一些.其余的应该是非公共的,或是秘密的.取代公共和秘密的属性,一种可以是输出的和私有的属性.一个模块的公共属性也即是模块的接口(不要和软件系统的用户界面搞混[英文皆interface]).
The fundamental reason behind the rule of Information Hiding is the continuity criterion. Assume a module changes, but the changes apply only to its secret elements, leaving the public ones untouched; then other modules who use it, called its clients, will not be affected. The smaller the public part, the higher the chances that changes to the module will indeed be in the secret part.
在信息隐藏规则背后的基本理由是连续性标准.假设一个模块变化了,但是变化仅应用于秘密元素,公共元素并未涉及;然后是使用它的其它模块,称之为它的客户端,也不会被影响.公共部份愈小,改变模块的秘密部份的机会也就愈高.
We may picture a module supporting Information Hiding as an iceberg; only the tip — the interface — is visible to the clients.
我们可以把一个支持信息隐藏的模块画成像一座冰山;只有尖端—接口—客户端能看得到.
As a typical example, consider a procedure for retrieving the attributes associated with a key in a certain table, such as a personnel file or the symbol table of a compiler. The procedure will internally be very different depending on how the table is stored (sequential array or file, hash table, binary or B-Tree etc.). Information hiding implies that uses of this procedure should be independent of the particular implementation chosen. That way client modules will not suffer from any change in implementation.
作为一个典型的例子,考虑一段程序,在一张指定的表中取回与一关键字关联的属性,就像是在一份人事文件或编译器的符号表中.对于不同的表储存结构(顺序数组或文件,哈希表,二进制或B树等等.),程序在内部是不同的.信息隐藏意味着这个程序的使用应该与所选择的特殊实现无关.那样的话,客户端模块将不会受到实现方面任何改变的影响.
Information hiding emphasizes separation of function from implementation. Besides continuity, this rule is also related to the criteria of decomposability, composability and understandability. You cannot develop the modules of a system separately, combine various existing modules, or understand individual modules, unless you know precisely what each of them may and may not expect from the others.
信息隐藏强调从实现之中的函数分离.除了连续性之外,这一件规则也关系到分解性,组合性和理解性标准.您不能彼此分离地开发一个系统的模块,不联合各种不同的已存在的模块,或不用理解单独的模块,除非您精确地知道它们中的每一个可能需要其它模块的什么或不可能需要什么.
Which properties of a module should be public, and which ones secret? As a general guideline, the public part should include the specification of the module’s functionality; anything that relates to the implementation of that functionality should be kept secret, so as to preserve other modules from later reversals of implementation decisions.
一个模块中哪些属性应该是公共的,哪些是秘密的?作为一个通用的方法,公共部份应该包括模块的功能性规格;任何与功能性实现有关的应该被保持为秘密的,这样保护了其它模块不会受到以后实现变化的影响.
This first answer is still fairly vague, however, as it does not tell us what is the specification and what is the implementation; in fact, one might be tempted to reverse the definition by stating that the specification consists of whatever public properties the module has, and the implementation of its secrets! The object-oriented approach will give us a much more precise guideline thanks to the theory of abstract data types.
然而,这最初的答案仍然是相当的模糊,它不能告诉我们什么是规格,什么是实现;事实上,规格由模块的任何公共特性和秘密实现所组成的,这样的描述可能试图颠倒定义!由于抽象的数据类型理论,面向对象的方式将会给我们一个更加精确的引导.
To understand information hiding and apply the rule properly, it is important to avoid a common misunderstanding. In spite of its name, information hiding does not imply protection in the sense of security restrictions — physically prohibiting authors of client modules from accessing the internal text of a supplier module. Client authors may well be permitted to read all the details they want: preventing them from doing so may be reasonable in some circumstances, but it is a project management decision which does not necessarily follow from the information hiding rule. As a technical requirement, information hiding means that client modules (whether or not their authors are permitted to read the secret properties of suppliers) should only rely on the suppliers’ public properties. More precisely, it should be impossible to write client modules whose correct functioning depends on secret information.
为了理解信息隐藏而且正确地应用此规则,避免一个常见的误解是很重要的.尽管它的名字,但信息隐藏并不意味着在安全限制意义上的保护性—物理上禁止客户端模块的作者访问所提供之模块的内部代码.有充分的理由可以让客户端作者被允许了解它们想要的所有细节: 阻止它们这么做在一些环境中可能是合理的,但是它是一个不必遵从信息隐藏规则的项目管理决定.正如一个技术上的需求, 信息隐藏意味着客户端模块(无论它们的作者被允许了解供应者的秘密特性)应该只依靠供应者的公共特性的方法.更精确的说法是,依靠秘密的信息来编写正确函数的客户端模块是不可能的.
In a completely formal approach to software construction, this definition would be stated as follows. To prove the correctness of a module, you will need to assume some properties about its suppliers. Information hiding means that such proofs are only permitted to rely on public properties of the suppliers, never on their secret properties.
在一个软件架构的完全正式的方法中,这个定义如下列所述.为了要验证模块的正确性,您需要假定供应者的一些特性.信息隐藏意味着如此的证明只能依赖供应者的公共特性,而不是在它们的秘密特性上.
Consider again the example of a module providing a table searching mechanism. Some client module, which might belong to a spreadsheet program, uses a table, and relies on the table module to look for a certain element in the table. Assume further that the algorithm uses a binary search tree implementation, but that this property is secret — not part of the interface. Then you may or may not allow the author of the table searching module to tell the author of the spreadsheet program what implementation he has used for tables. This is a project management decision, or perhaps (for commercially released software) a marketing decision; in either case it is irrelevant to the question of information hiding. Information hiding means something else: that even if the author of the spreadsheet program knows that the implementation uses a binary search tree, he should be unable to write a client module which will only function correctly with this implementation — and would not work any more if the table implementation was changed to something else, such as hash coding.
再次考虑一个提供了表查询机制的模块的例子.一些客户端模块,可能属于一个电子表格的程序,使用一个表,而且依赖表模块去找查询表中的某个元素.更进一步假定算法使用一个二进制查询树来实现,但是这个特性是秘密的--不是接口的一部份.然后您可能或不可能被表查询模块的作者允许,来告诉电子表格的作者他对表所用了什么样的实现.这是一个项目管理决定,或也许(为商业发布的软件)是一个行销决定;在任何一种情况下,它对信息隐藏的问题都是不相关的.信息隐藏意味着其它的东西: 即使电子表格程序的作者知道实现使用了二进制查询树,他也不能够写一个只能正确地运行这种实现的客户端模块--如果表的实现改变成别的方法时,如哈希码,就不能工作.
One of the reasons for the misunderstanding mentioned above is the very term “information hiding”, which tends to suggest physical protection. “Encapsulation”, sometimes used as a synonym for information hiding, is probably preferable in this respect, although this discussion will retain the more common term.
上面所提及的误解原因之一恰恰是术语”信息隐藏”,它试图暗示物理保护."封装",有时被当作信息隐藏的同义字,或许在这种情况下更好,虽然这个讨论将保持较通用的术语.
As a summary of this discussion: the key to information hiding is not management or marketing policies as to who may or may not access the source text of a module, but strict language rules to define what access rights a module has on properties of its suppliers. As explained in the next chapter, “encapsulation languages” such as Ada
and Modula-2 made the first steps in the right direction. Object technology will bring a more complete solution.
讨论总结:对于谁可以或不可以存取模块的源文本,信息隐藏的关键不是管理或市场政策,但是严格的语言规则(language rules)定义了一个模块在其供应者的属性上有什么样的存取权限.如下章所解释,象Ada和Modula-2这样的"封装语言",做出了正确方向上的第一步.对象技术将会带来更完整的解决方案.
3.3 FIVE PRINCIPLES
3.3 五项原则
From the preceding rules, and indirectly from the criteria, five principles of software construction follow:
软件构造的五项原则从前面的规则中,并间接地从标准中而来:
• The Linguistic Modular Units principle. 语义模块单元原则
• The Self-Documentation principle. 自包含文档原则
• The Uniform Access principle. 统一存取原则
• The Open-Closed principle. 开闭原则
• The Single Choice principle. 单选原则
Linguistic Modular Units
语义模块单元
The Linguistic Modular Units principle expresses that the formalism used to describe software at various levels (specifications, designs, implementations) must support the view of modularity retained:
语义模块单元原则表达了形式方法应该支持模块化所持的观点,这种形式方法常常在各种不同的层次(规格,设计,实现)中用于描述软件:
Linguistic Modular Units principle
Modules must correspond to syntactic units in the language used.
在所使用的程序设计语言中, 模块应该符合语法单元.
The language mentioned may be a programming language, a design language, a specification language etc. In the case of programming languages, modules should be separately compilable.
被提到的语言可以是程序语言,设计语言,规格语言等等.在程序语言的情况中,模块应该可以单独编译.
What this principle excludes at any level — analysis, design, implementation — is combining a method that suggests a certain module concept and a language that does not offer the corresponding modular construct, forcing software developers to perform manual translation or restructuring. It is indeed not uncommon to see companies hoping to apply certain methodological concepts (such as modules in the Ada
sense, or object-oriented principles) but then implement the result in a programming language such as Pascal or C which does not support them. Such an approach defeats several of the modularity criteria:
在任何层次(分析,设计,实现)中,这项原则所排斥的内容,正在联合一个方法迫使软件开发者进行手动转化或更改结构,这个方法建议了一个并不提供相应的模块化结构的某种模块概念和语言.我们可以看到许多公司正在希望应用某种方法学上的概念(如Ada中的模块,或面向对象的原则)但是接着在并不支持这些概念的设计语言中实现其结果,诸如Pascal或C这样的语言.这种情况当然并不罕见.这样的方式违反了一些模块化的标准:
• Continuity: if module boundaries in the final text do not correspond to the logical decomposition of the specification or design, it will be difficult or impossible to maintain consistency between the various levels when the system evolves. A change of the specification may be considered small if it affects only a small number of specification modules; to ensure continuity, there must be a direct correspondence between specification, design and implementation modules.
连续性: 如果在最终的代码中,模块界线不符合规格或设计的逻辑分解性,那么在系统进展中,在各种不同的层次之间维持一致性将会变得困难,或者根本不可能.如果一个规格的变化只影响小部分的规格模块,那么这可能会被认为无关紧要;为了确保连续性,在规格,设计和实现模块之间必须有直接对应.
• Direct Mapping: to maintain a clear correspondence between the structure of the model and the structure of the solution, you must have a clear syntactical identification of the conceptual units on both sides, reflecting the division suggested by your development method.
直接映射: 为了要在模型结构和解决方案结构之间维持一个清晰的通讯,您必须要在两者之间有一个清晰的概念单元的语法统一,以反映被您的开发方法所要求划分.
• Decomposability: to divide system development into separate tasks, you need to make sure that every task results in a well-delimited syntactic unit; at the implementation stage, these units must be separately compilable.
分解性: 为了要把系统开发分割为单独的任务,您需要确定每个任务正好是一个独立的语法单元;在实现的阶段,这些单元要能独立的编译.
• Composability: how could we combine anything other than modules with unambiguous syntactic boundaries?
组合性: 我们如何能把除了有清晰语法界线的模块之外的任何事物结合起来?
• Protection: you can only hope to control the scope of errors if modules are syntactically delimited.
保护性: 如果模块是按依照语法分割的,您只能希望控制错误的范围了.
Self-Documentation
自包含文档
Like the rule of Information Hiding, the Self-Documentation principle governs how we should document modules:
就象信息隐藏的规则, 自包含文档原则决定了我们应该如何对模块写文档:
Self-Documentation principle
The designer of a module should strive to make all information about the module part of the module itself.
模块的设计者应该努力让模块的本身包含其所有的信息.
What this precludes is the common situation in which information about the module is kept in separate project documents.
这排除了模块信息保存在单独的项目文档中的一般情况.
The documentation under review here is internal documentation about components of the software, not user documentation about the resulting product, which may require separate products, whether paper, CD-ROM or Web pages — although, as noted in the discussion of software quality, one may see in the modern trend towards providing more and more on-line help a consequence of the same general idea.
在这里所讨论的文档是有关软件组件的内部文档,不是最终产品的用户手册,手册可以是单独的产品,不管是纸,光盘或网页--然而,如软件品质所讨论的,一个可以看到的现代趋势是提供越来越多的在线帮助.
The most obvious justification for the Self-Documentation principle is the criterion of modular understandability. Perhaps more important, however, is the role of this principle in helping to meet the continuity criterion. If the software and its documentation are treated as separate entities, it is difficult to guarantee that they will remain compatible — “in sync” — when things start changing. Keeping everything at the same place, although not a guarantee, is a good way to help maintain this compatibility.
自包含文档原则最明显的理由是模块的理解性标准.然而,也许更重要是其在帮助符合连续性标准方面所起的作用.如果软件和它的文档被当作独立的实体,那么当事物开始变更的时候,保证它们会兼容—"在同步中"—是很困难的.保持所有的事物同一进度,虽然这不是一个保证,但是这是帮助维持兼容性的一个好方法.
Innocuous as this principle may seem at first, it goes against much of what the software engineering literature has usually suggested as good software development practices. The dominant view is that software developers, to deserve the title of software engineers, need to do what other engineers are supposed to: produce a kilogram of paper for every gram of actual deliverable. The encouragement to keep a record of the software construction process is good advice — but not the implication that software and its documentation are different products.
起先,这项原则可能看上去没有什么负作用,它反对许多软件工程文献通常所建议的良好的软件开发实践.权威的观点是软件开发者,要得到软件工程师的头衔的话,需要去做其他工程师们所应该做的:为每一克实际所交付的东西生产一公斤的纸.鼓励保存软件构造过程的记录是个好的忠告—但并不意味着软件和它的文档是不同的产品.
Such an approach ignores the specific property of software, which again and again comes back in this discussion: its changeability. If you treat the two products as separate, you risk finding yourself quickly in a situation where the documentation says one thing and the software does something else. If there is any worse situation than having no documentation, it must be having wrong documentation.
上述方式忽视了一次又一次反复讨论的软件的特定属性:它的可变性.如果把二种产品独立来看,您会发现您处在这样的一种冒险的境地:在文档里说这件事而软件却在做其它事.如果这里有比没有文档更糟糕的情形的话,那一定是错误的文档.
A major advance of the past few years has been the appearance of quality standards for software, such as ISO certification, the “2167” standard and its successors from the US Department of Defense, and the Capability Maturity Model of the Software Engineering Institute. Perhaps because they often sprang out of models from other disciplines, they tend to specify a heavy paper trail. Several of these standards could have a stronger effect on software quality (beyond providing a mechanism for managers to cover their bases in case of later trouble) by enforcing the Self-Documentation principle.
过去几年中一个主要的进步是软件品质标准(quality standards)的出现,像是ISO认证,
”2167”标准和来自美国国防部的后继版本,软件工程学会的能力成熟度模型(CMM).也许因为它们从来自其它的训练模型里跳出的,所以它们趋向于指定详细的书面材料.这些标准中的一些通过加强自包含文档原则在软件品质上有着更好的效果(这超越了以防后面的麻烦单纯为管理人员提供一个机制的出发点).
This book will draw on the Self-Documentation principle to define a method for documenting classes — the modules of object-oriented software construction — that includes the documentation of every module in the module itself. Not that the module is its documentation: there is usually too much detail in the software text to make it suitable as documentation (this was the argument for information hiding). Instead, the module should contain its documentation.
本书将会利用自包含文档原则来定义一个方法,这个方法为了文档类—面向对象软件构造的模块—在模块的自身中包括了每个模块的文档.并不是模块就是它的文档: 通常在软件文本中有太多的细节使其不适合成为文档(这也是信息隐藏的论据).作为替代的是,模块应该包含它的文档.
In this approach software becomes a single product that supports multiple views. One view, suitable for compilation and execution, is the full source code. Another is the abstract interface documentation of each module, enabling software developers to write client modules without having to learn the module’s own internals, in accordance with the rule of Information Hiding. Other views are possible.
在这种方式中,软件变成了一种支持多重观点(views)的单一产品.一种适合编译和执行的观点认为是完整的源程序代码.另外一种认为是每个模块的抽象接口文档,并促成软件开发者编写客户端模块而不必学习模块自身的内在结构,这符合信息隐藏的规则.其它的观点都有可能存在.
We will need to remember this rule when we examine the question of how to document the classes of object-oriented software construction.
当我们检验该如何编写面向对象软件构造的类文档这样的问题时,我们需要牢记这条规则.
Uniform Access
统一存取
Although it may at first appear just to address a notational issue, the Uniform Access principle is in fact a design rule which influences many aspects of object-oriented design and the supporting notation. It follows from the Continuity criterion; you may also view it as a special case of Information Hiding.
虽然它起先出现可能只是用于描述一个符号上的议题,但是事实上,统一存取的原则是一条影响面向对象设计和支持符号的多方面的设计规则.它遵循连续性标准;您也可以把它看作信息隐藏的一个特别情况.
Let x be a name used to access a certain data item (what will later be called an object) and f the name of a feature applicable to x. (A feature is an operation; this terminology will also be defined more precisely.) For example, x might be a variable representing a bank account, and f the feature that yields an account’s current balance. Uniform Access addresses the question of how to express the result of applying f to x, using a notation that does not make any premature commitment as to how f is implemented.
设x为存取某个数据项目(稍后将会被称之为一个对象)的变量名,f为应用于x上的一个特性名.(一个特性就是一个运算;这一术语也将会更精确地定义.)举例来说,x可能是一个表示银行账户的变量,f是产生账户当前余额的特性.统一存取描述了该如何表示x上f应用的结果的问题,对于f如何实现,并使用了一个不作任何的过早约定的符号.
In most design and programming languages, the expression denoting the application of f to x depends on what implementation the original software developer has chosen for feature f: is the value stored along with x, or must it be computed whenever requested? Both techniques are possible in the example of accounts and their balances:
在大多数的设计和程序语言中, x上f应用的表达式依赖于最初的软件开发者对特性f选择了什么样实现: 数值是连同x一起储存的,还是每当请求的时候必须要再次计算? 在这个账户和余额的例子中这两种技术都是有可能的:
A1 • You may represent the balance as one of the fields of the record describing each account, as shown in the figure. With this technique, every operation that changes the balance must take care of updating the balance field.
A1: 您可以用记录中的字段之一来表达余额,这些记录描述了每一条账户,如下图所示.这样的话, 每一个改变余额的运算必须要更新余额字段.
A2 • Or you may define a function which computes the balance using other fields of the record, for example fields representing the lists of withdrawals and deposits. With this technique the balance of an account is not stored (there is no balance field) but computed on demand.
A2:或者,您可以定义一个函数,使用记录中的其它字段来计算余额,例如,字段表达了提款和存款的清单.使用此技术,账户的余额没有被储存(这里并没有余额字段)但是在需要时计算.
A common notation, in languages such as Pascal, Ada
, C, C++ and Java, uses x. f in case A1 and f (x) in case A2.
在Pascal, Ada, C, C++和Java等语言中, A1中用x. f 符号,A2中用f (x)符号.
Choosing between representations A1 and A2 is a space-time tradeoff: one economizes on computation, the other on storage. The resolution of this tradeoff in favor of one of the solutions is typical of representation decisions that developers often reverse at least once during a project’s lifetime. So for continuity’s sake it is desirable to have a feature access notation that does not distinguish between the two cases; then if you are in charge of x’s implementation and change your mind at some stage, it will not be necessary to change the modules that use f. This is an example of the Uniform Access principle.
在A1和A2表示法之间的选择是一个时间空间上的折衷:一个节约在计算上,另一个在储存上.在解决方案中选其一,这种折衷决定是具有代表性的决定,在项目周期中开发者曾经常反复至少一次.因此,为了连续性的缘故,如果有一个特性能在不区别这二种情况下进行存取,那么这是极有价值的;然后如果您负责x的实现,并且在某个阶段时改变了自己的想法,这就不必改变使用f的模块.这是统一存取原则的一个例子.
In its general form the principle may be expressed as:
统一存取原则的通用形式可表达如下:
Uniform Access principle
All services offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation.
一个模块所提供的所有服务应该是通过一个统一的符号被使用,其不会泄漏出它们是通过存储还是经过计算而实现的.
Few languages satisfy this principle. An older one that did was Algol W, where both the function call and the access to a field were written a (x). Object-oriented languages should satisfy Uniform Access, as did the first of them, Simula 67, whose notation is x. f in both cases. The notation developed in part C will retain this convention.
很少的语言能满足这项原则.一个较旧的语言Algol W,把功能调用和字段存取都写成a(x).面向对象的语言应该满足统一存取原则,如同第一个语言Simula 67,在两种情况下符号都是x. f. 在本书C部分中,符号开发将会保持这个约定.
The Open-Closed principle
开闭原则
Another requirement that any modular decomposition technique must satisfy is the Open-Closed principle:
模块分解技术必须满足的另一个需求是开闭原则:
Open-Closed principle
Modules should be both open and closed.
模块应该都能开放(open)和关闭(closed).
The contradiction between the two terms is only apparent as they correspond to goals of a different nature:
在二个术语之间的矛盾只有在当它们符合不同目标的时候才出现:
• A module is said to be open if it is still available for extension. For example, it should be possible to expand its set of operations or add fields to its data structures.
如果一个模块一直能有效地扩充,那么它被说成是开放.比如,其有可能扩充它的运算集合或把字段加入到它的数据结构中.
• A module is said to be closed if it is available for use by other modules. This assumes that the module has been given a well-defined, stable description (its interface in the sense of information hiding). At the implementation level, closure for a module also implies that you may compile it, perhaps store it in a library, and make it available for others (its clients) to use. In the case of a design or specification module, closing a module simply means having it approved by management, adding it to the project’s official repository of accepted software items (often called the project baseline), and publishing its interface for the benefit of other module authors.
如果一个模块被其它的模块有效的使用,那么它被说成是关闭.这里假设模块已经给予了明确的定义,稳定的描述(其接口已有信息隐藏的能力).在实现的层次上,关闭一个模块也意味着您可以编译它,或许储存在一个库中,并且使其它模块(它的客户端)可以有效使用.对于设计模块或规格模块来说,关闭一个模块只是意味着被管理层核准了,作为公用的软件条目(时常被称为项目基准(baseline))把它加入到项目的正式软件包中,而且对其它的模块的作者公布其接口.
The need for modules to be closed, and the need for them to remain open, arise for different reasons. Openness is a natural concern for software developers, as they know that it is almost impossible to foresee all the elements — data, operations — that a module will need in its lifetime; so they will wish to retain as much flexibility as possible for future changes and extensions. But it is just as necessary to close modules, especially from a project manager’s viewpoint: in a system comprising many modules, most will depend on some others; a user interface module may depend on a parsing module (for parsing command texts) and on a graphics module, the parsing module itself may depend on a lexical analysis module, and so on. If we never closed a module until we were sure it includes all the needed features, no multi-module software would ever reach completion: every developer would always be waiting for the completion of someone else’s job.
关闭模块和保持开放的需求,因为不同的理由而出现.公开是软件开发者自然地考虑,因为它们知道,几乎不可能预见一个模块在它的周期中所需要的所有元素 —数据,运算;因此它们愿为将来的变化和扩充保持尽可能多的弹性.但是正如必需要关闭模块一样,尤其是项目经理的观点:一个系统中包含了大量的模块,大部分将会依赖于其它的一些;一个用户接口模块可能依赖一个解析(parsing)模块(为了解析指令文本)和一个图形模块,那个解析模块本身可能依赖于一个语法分析模块,等等.如果我们从不关闭一个模块直到我们确定它包含了所有的需要特性的话,那么,多模块软件就永远不会完成:每个开发者会总是会等候其他人的工作先完成.
With traditional techniques, the two goals are incompatible. Either you keep a module open, and others cannot use it yet; or you close it, and any change or extension can trigger a painful chain reaction of changes in many other modules, which relied on the original module directly or indirectly.
由于传统的技术,这二个目标是相互矛盾的.或者您保持一个模块开放,其它的模块仍然不能够使用它;或者您关闭它,任何的变化或扩充能引起痛苦的连锁反应,许多其它的模块都需要改变,它们直接地或间接地依赖最初的模块.
The two figures below illustrate a typical situation where the needs for open and closed modules are hard to reconcile. In the first figure, module A is used by client modules B, C, D, which may themselves have their own clients (E, F, ¼).
下面的两幅图阐明了一个典型的情况,需要开放和关闭的模块很难融合.在第一幅图中, 模块A被客户端模块B,C,D使用,它们自己也有客户端(E, F, ¼).
Later on, however, the situation is disrupted by the arrival of new clients — B' and others — which need an extended or adapted version of A, which we may call A':
可是稍后,情形被新来的B’和其它的客户端打乱了,新客户端需要一个扩充的或改编的A版本,我们叫它A’:
With non-O-O methods, there seem to be only two solutions, equally unsatisfactory:
不用OO的方式,这里看上去只有两种解决方案, 不令人满意:
N1 • You may adapt module A so that it will offer the extended or modified functionality (A' ) required by the new clients.
N1. 您可以改编A模块,这样将提供给新客户端所需要的扩充或修改功能性(A').
N2 • You may also decide to leave A as it is, make a copy, change the module’s name to A' in the copy, and perform all the necessary adaptations on the new module. With this technique A' retains no further connection to A.
N2. 您可以决定留下A,拷贝并改名为A’,在新模块中完成所有必须的改编. 这样一来,A’就不和A有更进一步的关系了.
The potential for disaster with solution N1 is obvious. A may have been around for a long time and have many clients such as B, C and D. The adaptations needed to satisfy the new clients’ requirements may invalidate the assumptions on the basis of which the old ones used A; if so the change to A may start a dramatic series of changes in clients, clients of clients and so on. For the project manager, this is a nightmare come true: suddenly, entire parts of the software that were supposed to have been finished and sealed off ages ago get reopened, triggering a new cycle of development, testing, debugging and documentation. If many a software project manager has the impression of living the Sisyphus syndrome — the impression of being sentenced forever to carry a rock to the top of the hill, only to see it roll back down each time — it is for a large part because of the problems caused by this need to reopen previously closed modules.
方案N1的潜在灾祸很明显.A可能已经被访问了很长的一段时间并且有许多B,C和D这样的客户端.改编需要满足新客户端的需求,这些改编假设在使用A的旧客户端的基础上是无效的;假如这样的话,A的改变可能会引发显著的一系列方面的改变,客户端的改变,客户端之客户端的改变等等.对于项目经理,这真是噩梦成真:突然,已经完成而且之前就封版的整个软件又要重新开始,并引发了又一轮新的周期:开发,测试,除错和编写文档.如果多数软件项目经理有着逼真的西西弗斯(Sisyphus)并发症状的感觉—被判决永远地将一块岩石携带到小山顶,每次只能看到它滚下的感觉—这大部份是因为要重新开放先前关闭的模块的问题所造成的.
[希腊神话:西西弗斯, 希腊古时科林斯残暴的国王,因作恶多端,死后堕入地狱,被判以永远将一块巨石推上海蒂斯的一座小山,而每当接近山顶时,石头又会滚下来]
On the surface, solution N2 seems better: it avoids the Sisyphus syndrome since it does not require modifying any existing software (anything in the top half of the last figure). But in fact this solution may be even more catastrophic since it only postpones the day of reckoning. If you extrapolate its effects to many modules, many modification requests and a long period, the consequences are appalling: an explosion of variants of the original modules, many of them very similar to each other although never quite identical.
表面上,方案N2似乎更好一点:由于它不需要修改任何的已存在的软件(在最后一副图的顶部上的一切),它避免了西西弗斯并发症状.但是事实上这个解决方案甚至更悲惨,它只是延迟了发作的时间而已.如果您对许多模块和修改请求使用同样的结果,在一段时间后,其结果令人心惊胆颤:最初模块的变体呈爆炸性增长, 它们大部分彼此非常相似但又不完全相同.
In many organizations, this abundance of modules, not matched by abundance of available functionality (many of the apparent variants being in fact quasi-clones), creates a huge configuration management problem, which people attempt to address through the use of complex tools. Useful as these tools may be, they offer a cure in an area where the first concern should be prevention. Better avoid redundancy than manage it.
在许多组织中,这种模块丰富度并没有和有效的功能性的丰富度相匹配(许多明显的变体事实上是准克隆),这产生了一个极大的配置管理(configuration management)的问题,人们尝试通过使用复杂的工具来描述它们.由于这些工具是有效的,所以它们对某个领域提供了治疗方法.但这个领域首要关注的应是预防.对于管理冗余来说,更好的是避免它.
Configuration management will remain useful, of course, if only to find the modules which must be reopened after a change, and to avoid unneeded module recompilations.
当然,如果仅仅是找出必须在一个变化之后重新开放的模块和避免不需要的模块再次编辑,配置管理将会一直有效.
But how can we have modules that are both open and closed? How can we keep A and everything in the top part of the figure unchanged, while providing A' to the bottom clients, and avoiding duplication of software? The object-oriented method will offer a particularly elegant contribution thanks to inheritance.
但是我们如何能有同时开闭的模块?当提供A'给底部的客户端的时候, 我们如何能保持A和一切在图形顶端的部份不变,同时避免软件的复制? 面向对象的方法提供一个特别优秀的方法:继承.
The detailed study of inheritance appears in later chapters, but here is a preview of the basic idea. To get us out of the change or redo dilemma, inheritance will allow us to define a new module A' in terms of an existing module A by stating the differences only. We will write A' as
继承的详细研究在稍后的章节中介绍,这里是基本概念的一个预览.为了要使我们避免变化(change)或重做(redo)的困境, 继承允许我们从已存在的模块A中定义一个新的模块A',只描述不同部分.我们把A’写成:
class A' inherit
A redefine f, g, ¼ end
feature
f is ¼
g is ¼
¼
u is ¼
¼
end
where the feature clause contains both the definition of the new features specific to A', such as u, and the redefinition of those features (such as f, g, ¼) whose form in A' is different from the one they had in A.
这里,特性子句包含了A’中新的特性定义,如u ,和重定义的那些A中有的却又不同于A的特性(如f, g, ¼.)
The pictorial representation for inheritance will use an arrow from the heir (the new class, here A') to the parent (here A):
继承的图示使用了从继承者(新类,A’)到父亲(A)的一个箭头:
Thanks to inheritance, O-O developers can adopt a much more incremental approach to software development than used to be possible with earlier methods.
由于继承,OO的开发者能采用一种比过去所用的老式方法更加增量的软件开发方式.
One way to describe the open-closed principle and the consequent object-oriented techniques is to think of them as a organized hacking. “Hacking” is understood here as a slipshod approach to building and modifying code (not in the more recent sense of
breaking into computer networks, which, organized or not, no one should condone). The hacker may seem bad but often his heart is pure. He sees a useful piece of software, which is almost able to address the needs of the moment, more general than the software’s original purpose. Spurred by a laudable desire not to redo what can be reused, our hacker starts modifying the original to add provisions for new cases. The impulse is good but the effect is often to pollute the software with many clauses of the form if that_special_case then¼, so that after a few rounds of hacking, perhaps by a few different hackers, the software starts resembling a chunk of Swiss cheese that has been left outside for too long in August (if the tastelessness of this metaphor may be forgiven on the grounds that it does its best to convey the presence in such software of both holes and growth).
一种描述开闭原则和相关的面向对象技术的方法是把它们想成是有组织的黑客行为(organized hacking).在这里,”黑客行为”可以理解成构建和修改代码的一种随意的方式(不是我们所知道的黑客:闯进计算机网络里,有组织或非组织,一个不能宽恕之人).黑客看上去很坏但是他却心地纯洁.他查看一个有效的软件片段,几乎能够在片刻间描述出需求,比软件的原始目的更全面.不需要做些重复的东西来获得赞赏,我们的黑客开始修改原代码以加入新的需求.良好的愿望但是效果却大打折扣,软件被许多if that_special_case then¼这样形式的子句所污染,所以在一些回合之后,也许是一些不同的黑客所为,软件开始类似于在八月份的天气下放在外面很长时间的一大块瑞士奶酪(如果当场无法理解这个比喻可以原谅,它在尽最大的努力去表达这样一个具有千疮百孔和膨胀的软件).
The organized form of hacking will enable us to cater to the variants without affecting the consistency of the original version.
黑客行为的组织形式使我们能够不影响最初版本的稳定而迎合变体.
A word of caution: nothing in this discussion suggests disorganized hacking. In particular:
注意:这里没有讨论破坏黑客行为.尤其是:
• If you have control over the original software and can rewrite it so that it will address the needs of several kinds of client at no extra complication, you should do so.
如果您能控制最初的软件并能重写它,以便它会在没有额外的复杂化的情况下描述几种客户端的需求,那么您应该这么做。
• Neither the Open-Closed principle nor redefinition in inheritance is a way to address design flaws, let alone bugs. If there is something wrong with a module, you should fix it — not leave the original as it is and try to correct the problem in a derived module. (The only potential exception to this rule is the case of flawed software which you are not at liberty to modify.) The Open-Closed principle and associated techniques are intended for the adaptation of healthy modules: modules that, although they may not suffice for some new uses, meet their own well-defined requirements, to the satisfaction of their own clients.
既不是开闭原则也不是在继承中重新定义,这是描述设计缺点并忽略错误的一个方法.如果一个模块有些错误,您应该修改它—不是留在原来的代码中而且试着在派生模块中改正.(对这条规则的唯一潜在的例外情况是您无权修改有缺点的软件.)开闭原则和相关的技术试图改编健全的模块:虽然这些模块不能满足一些新的用法,但符合它们自己的明确定义的需求,并满足于它们自己的客户端.
Single Choice
单选
The last of the five modularity principles may be viewed as a consequence of both the Open-Closed and Information Hiding rules.
作为开闭和信息隐藏规则的推论,我们来研究五项模块性原则的最后一个.
Before examining the Single Choice principle in its full generality, let us look at a typical example. Assume you are building a system to manage a library (in the nonsoftware sense of the term: a collection of books and other publications, not software modules). The system will manipulate data structures representing publications. You may have declared the corresponding type as follows in Pascal-Ada syntax:
在研究单选原则的完整概论之前,让我们看着一个典型的例子.假如您正在建造一个系统来管理一个库(以非软件化的术语来说:书和其它出版物的一个集合,不是软件模块).系统将会操纵数据结构来表现该出版物.您可能已经声明了对应的类型,如下面的Pascal-Ada语法:
type PUBLICATION =
record
author, title: STRING;
publication_year: INTEGER
case pubtype: (book, journal, conference_proceedings) of
book: (publisher: STRING);
journal: (volume, issue: STRING);
proceedings: (editor, place: STRING) -- Conference proceedings
end
This particular form uses the Pascal-Ada notion of “record type with variants” to describe sets of data structures with some fields (here author, title, publication_year) common to all instances, and others specific to individual variants.
这种特别的形式使用了Pascal-Ada的"记录型变量(record type with variants)"概念,来描述一些字段(author, title, publication_year)的数据结构集合,其对所有的实例和其它个别的变量都是共用的.
The use of a particular syntax is not crucial here; Algol 68 and C provide an equivalent mechanism through the notion of union type. A union type is a type T defined as the union of pre-existing types A, B, ¼: a value of type T is either a value of type A, or a value of type B, ¼ Record types with variants have the advantage of clearly associating a tag, here book, journal, conference_proceedings, with each variant.
这里,特殊语法的使用并不重要;Algol68和C提供了提供一个相同的机制,联合类型(union type).一个联合类型是一个类型T,被定义为已存在的类型A,B,..的联合体: 类型T的值是类型A的值,或类型B的值,… 记录型变量有一点好处是可以清楚地表达联合的标记,在这里是book, journal, conference_proceedings,和每一个变量.
Let A be the module that contains the above declaration or its equivalent using another mechanism. As long as A is considered open, you may add fields or introduce new variants. To enable A to have clients, however, you must close the module; this means that you implicitly consider that you have listed all the relevant fields and variants. Let B be a typical client of A. B will manipulate publications through a variable such as
p: PUBLICATION
and, to do just about anything useful with p, will need to discriminate explicitly between the various cases, as in:
case p of
book: ¼ Instructions which may access the field p l publisher ¼
journal: ¼ Instructions which may access fields p l volume, pl issue ¼
proceedings: ¼ Instructions which may access fields p l editor, pl place ¼
end
设A为一个模块,包含了上述的声明或使用了其它机制的类似声明.在A是开放状态的时候,您可以增加字段或引进新的变量.然而,为使客户端能用A,您一定关闭模块;这意谓您要绝对考虑您已经列出的所有有关的字段和变量.设B是A的一个典型的客户端.B将会通过如下变量来处理出版物
p: PUBLICATION
并且,要使p对所有出版物都有效,需要在各种不同的情况中明确地加以区别,如下所示:
case p of
book: ¼ Instructions which may access the field p l publisher ¼
journal: ¼ Instructions which may access fields p l volume, pl issue ¼
proceedings: ¼ Instructions which may access fields p l editor, pl place ¼
end
The case instruction of Pascal and Ada
comes in handy here; it is of course on purpose that its syntax mirrors the form of the declaration of a record type with variants. Fortran and C will emulate the effect through multi-target goto instructions (switch in C). In these and other languages a multi-branch conditional instruction (if ¼ then ¼ elseif ¼ elseif ¼ else ¼ end) will also do the job.
这里,Pascal和Ada的case指令正好派上用场;当然这是特意让它的语法反映了记录型变量的声明形式.Fortran和C将会通过多层goto的方法来效仿这种指令的效果(C中的switch语句).在这些和其它的语言中,一个多分支条件语句(if ¼ then ¼ elseif¼ elseif ¼ else ¼ end)也会做这样的工作.
Aside from syntactic variants, the principal observation is that to perform such a discrimination every client must know the exact list of variants of the notion of publication supported by A. The consequence is easy to foresee. Sooner or later, you will realize the need for a new variant, such as technical reports of companies and universities. Then you will have to extend the definition of type PUBLICATION in module A to support the new case. Fair enough: you have modified the conceptual notion of publication, so you should update the corresponding type declaration. This change is logical and inevitable. Far harder to justify, however, is the other consequence: any client of A, such as B, will also require updating if it used a structure such as the above, relying on an explicit list of cases for p. This may, as we have seen, be the case for most clients.
除了语法的变化之外,主要的结果是为了执行这样的一种鉴别能力,每个客户端一定要知道A所支持的出版物变量的精确的列表.其结果可以轻易地预见到.迟早您会有新变化的需求,像是公司和大学的技术报告.然后,您不得不扩充模块A中的PUBLICATION的定义以支持新的情况.这足够合理: 您已经修改了出版物概念上的理解,因此,您应该更新对应的类型声明.这个变化合乎逻辑并不可避免.然而,更困难的是要想验证另一个结果:如果用了如上的结构,依赖于出版物变化情况的精确列表的话,象B这样的任何A的客户端也需要更新.如我们所见,可能大多数的客户端都能遇上这样的情况.
What we observe here is a disastrous situation for software change and evolution: a simple and natural addition may cause a chain reaction of changes across many client modules.
在这里,我们所观察的是软件变化和演化中的一种灾难性的情形:一个简单自然的增加可能会引发横跨许多客户端模块的一个连锁反应.
The issue will arise whenever a certain notion admits a number of variants. Here the notion was “publication” and its initial variants were book, journal article, conference proceedings; other typical examples include:
每当某个观念接纳了许多的变量,问题就将出现了.这里的观念是”出版物”,其初始变量是书,期刊文章,会议纪要;其它的典型例子包括:
• In a graphics system: the notion of figure, with such variants as polygon, circle, ellipse, segment and other basic figure types.
一个图形系统: 图形的概念,包含这些变量,如多角形,圆周,椭圆,线段和其它的基本图形类型.
• In a text editor: the notion of user command, with such variants as line insertion, line deletion, character deletion, global replacement of a word by another.
一个文字编辑器: 用户指令的概念,包含这些变量,如行插入,行删除,字符删除,词组的全部替换.
• In a compiler for a programming language, the notion of language construct, with such variants as instruction, expression, procedure.
一个程式设计语言的编译器:语言构造的概念,包含这些变量,如指令,表达式,程序.
In any such case, we must accept the possibility that the list of variants, although fixed and known at some point of the software’s evolution, may later be changed by the addition or removal of variants. To support our long-term, software engineering view of the software construction process, we must find a way to protect the software’s structure against the effects of such changes. Hence the Single Choice principle:
在所有的这些例子中,我们必须接受这些可能性,变量的清单虽然在软件演化的某些时候是固定和已知的,但以后可能会被增加或删除.为了支持长期的,软件构造过程的软件工程观点,我们必须要找到一个方法来保护软件结构,避免如此变化的结果.单选原则由此而来:
Single Choice principle
Whenever a software system must support a set of alternatives, one and only one module in the system should know their exhaustive list.
每当一个软件系统必须支持一组替代选择,那么在系统中有且只有一个模块知道它们的详细列表.
By requiring that knowledge of the list of choices be confined to just one module, we prepare the scene for later changes: if variants are added, we will only have to update the module which has the information — the point of single choice. All others, in particular its clients, will be able to continue their business as usual.
依照选择的列表定义只限制在一个模块中的需求,我们为稍后的变化准备了布景: 如果变量增加,我们只是必须更新表明单选信息的模块.所有其它的,特别是其客户端,能够一切如常.
Once again, as the publications example shows, traditional methods do not provide a solution; once again, object technology will show the way, here thanks to two techniques connected with inheritance: polymorphism and dynamic binding. No sneak preview in this case, however; these techniques must be understood in the context of the full method.
再一次,如出版物例子所示,传统的方法并没有提供解决方案;这里,由于二种与继承相关的技术:多态和动态绑定,对象技术将再一次展示其方式.然而,在这种情况下没有定式,这些技术一定要在完整方法的上下文中才能了解.
The Single Choice principle prompts a few more comments:
单选原则提示了一些更多的解释:
• The number of modules that know the list of choices should be, according to the principle, exactly one. The modularity goals suggest that we want at most one module to have this knowledge; but then it is also clear that at least one module must possess it. You cannot write an editor unless at least one component of the system has the list of all supported commands, or a graphics system unless at least one component has the list of all supported figure types, or a Pascal compiler unless at least one component “knows” the list of Pascal constructs.
依照此原则,知道选择列表的模块数目应该是完全唯一的.模块化的目标建议我们最多一个模块拥有这些定义;不过,它也清楚的表明至少一个模块一定持有它.您不能够编写一个编辑器除非至少系统的一个组件有全部支持指令的列表,或不能够编写一个图形系统除非至少一个组件有全部的图形类型列表,也不能够编写一个Pascal编译器除非至少一个组件"知道"Pascal构造的列表.
• Like many of the other rules and principles studied in this chapter, the principle is about distribution of knowledge in a software system. This question is indeed crucial to the search for extendible, reusable software. To obtain solid, durable system architectures you must take stringent steps to limit the amount of information available to each module. By analogy with the methods employed by certain human organizations, we may call this a need-to-know policy: barring every module from accessing any information that is not strictly required for its proper functioning.
如同在这章中学习的大部份其它的规则和原则,这项原则是有关于在一个软件系统中的知识分配(distribution of knowledge).这个问题毫无疑问地对探索可扩充的,可复用的软件起着决定性作用.为了要获得坚固持久的系统架构,您必须采取迫切的步骤来限制每个模块可用的信息数量.根据某些人类组织雇用人员的方法,我们可以称这为需要-去-知道(need-to-know)策略:禁止每个模块存取任何信息,这些信息对它正当的功能并没有严格地要求.
• You may view the Single Choice principle as a direct consequence of the Open-Closed principle. Consider the publications example in light of the figure that illustrated the need for open-closed modules: A is the module which includes the original declaration of type PUBLICATION; the clients B, C, ¼ are the modules that relied on the initial list of variants; A' is the updated version of A offering an extra variant (technical reports).
您可能把单选原则看作开闭原则的直接结果.根据描绘开闭模块需求的插图考虑出版物的例子: A是包括类型PUBLICATION最初声明的模块; 客户端B,C.. 是依赖于变量初始列表的模块;A'是A的更新版本,提供了一个额外的变量(技术上的陈述).
• You may also understand the principle as a strong form of Information Hiding. The designer of supplier modules such as A and A' seeks to hide information (regarding the precise list of variants available for a certain notion) from the clients.
您也可以把原则理解成信息隐藏的强化形式.提供模块(如A和A’)的设计者寻求自客户端的信息隐藏(当作从一个特定观念的有效变量的精确列表).
3.4 KEY CONCEPTS INTRODUCED IN THIS CHAPTER
• The choice of a proper module structure is the key to achieving the aims of reusability and extendibility.
一个适当的模块结构的选择是达成复用性和扩充性目标的关键.
• Modules serve for both software decomposition (the top-down view) and software composition (bottom-up).
模块为软件分解性(由上而下的观点)和软件组合性(由下而上的观点)服务.
• Modular concepts apply to specification and design as well as implementation.
模块的概念适用于规格和设计,也适用于实现.
• A comprehensive definition of modularity must combine several perspectives; the various requirements may sometimes appear at odds with each other, as with decomposability (which encourages top-down methods) and composability (which favors a bottom-up approach).
一个全面的模块化定义必须要结合几种观点;由于分解性(鼓励由上而下的方法)和组合性(支持由下而上的方式),各种不同的需求有时可能出现彼此不一致的现象.
• Controlling the amount and form of communication between modules is a fundamental step in producing a good modular architecture.
在模块之间控制通讯数量和形式是产生一个好的模块架构的基本步骤.
• The long-term integrity of modular system structures requires information hiding, which enforces a rigorous separation of interface and implementation.
模块化系统结构的长期完整性需要信息隐藏,信息隐藏实施在接口和实现之间的严格分离.
• Uniform access frees clients from internal representation choices in their suppliers.
统一存取从其供应者的内部表示法选择中释放了客户端.
• A closed module is one that may be used, through its interface, by client modules.
一个关闭模块是一个通过它的接口,可以被客户端模块所使用的模块.
• An open module is one that is still subject to extension.
一个开放模块是一个一直需要扩充的模块.
• Effective project management requires support for modules that are both open and closed. But traditional approaches to design and programming do not permit this.
高效的项目管理需要支持模块的开放或关闭.但是传统的设计和编程方法并不允许这样做.
• The principle of Single Choice directs us to limit the dissemination of exhaustive knowledge about variants of a certain notion.
单选原则指导我们限制对确定观念的变量的全面定义进行传播.
3.5 BIBLIOGRAPHICAL NOTES
The design method known as “structured design” [Yourdon 1979] emphasized the importance of modular structures. It was based on an analysis of module “cohesion” and “coupling”. But the view of modules implicit in structured design was influenced by the traditional notion of subroutine, which limits the scope of the discussion.
The principle of uniform access comes originally (under the name “uniform reference”) from [Geschke 1975].
The discussion of uniform access cited the Algol W language, a successor to Algol 60 and forerunner to Pascal (but offering some interesting mechanisms not retained in Pascal), designed by Wirth and Hoare and described in [Hoare 1966].
Information hiding was introduced in two milestone articles by David Parnas [Parnas 1972] [Parnas 1972a].
Configuration management tools that will recompile the modules affected by modifications in other modules, based on an explicit list of module dependencies, are based on the ideas of the Make tool, originally for Unix [Feldman 1979]. Recent tools —there are many on the market — have added considerable functionality to the basic ideas.
Some of the exercises below ask you to develop metrics to evaluate quantitatively the various informal measures of modularity developed in this chapter. For some results in O-O metrics, see the work of Christine Mingins [Mingins 1993] [Mingins 1995] and Brian Henderson-Sellers [Henderson-Sellers 1996a].
EXERCISES
E3.1 Modularity in programming languages
Examine the modular structures of any programming language which you know well and assess how they support the criteria and principles developed in this chapter.
E3.2 The Open-Closed principle (for Lisp programmers)
Many Lisp implementations associate functions with function names at run time rather than statically. Does this feature make Lisp more supportive of the Open-Closed principle than more static languages?
E3.3 Limits to information hiding
Can you think of circumstances where information hiding should not be applied to relations between modules?
E3.4 Metrics for modularity (term project)
The criteria, rules and principles of modularity of this chapter were all introduced through qualitative definitions. Some of them, however, may be amenable to quantitative analysis.
The possible candidates include:
• Modular continuity.
• Few Interfaces.
• Small Interfaces.
• Explicit Interfaces.
• Information Hiding.
• Single Choice.
Explore the possibility of developing modularity metrics to evaluate how modular a software architecture is according to some of these viewpoints. The metrics should be size-independent: increasing the size of a system without changing its modular structure should not change its complexity measures. (See also the next exercise.)
E3.5 Modularity of existing systems
Apply the modularity criteria, rules and principles of this chapter to evaluate a system to which you have access. If you have answered the previous exercise, apply any proposed modularity metric.
Can you draw any correlations between the results of this analysis (qualitative, quantitative or both) and assessments of structural complexity for the systems under study, based either on informal analysis or, if available, on actual measurements of debugging and maintenance costs?
E3.6 Configuration management and inheritance
(This exercise assumes knowledge of inheritance techniques described in the rest of this book. It is not applicable if you have read this chapter as part of a first, sequential reading of the book.)
The discussion of the open-closed principle indicated that in non-object-oriented approaches the absence of inheritance places undue burden on configuration management tools, since the desire to avoid reopening closed modules may lead to the creation of too many module variants. Discuss what role remains for configuration management in an object-oriented environment where inheritance is present, and more generally how the use of object technology affects the problem of configuration management.
If you are familiar with specific configuration management tools, discuss how they interact with inheritance and other principles of O-O development.