面向对象软件构造(第2版)-第4章 复用性方法Approaches to reusability (下)

4.7 TRADITIONAL MODULAR STRUCTURES

4.7 传统的模块结构

 

Together with the modularity requirements of the previous chapter, the five requirements of Type Variation, Routine Grouping, Implementation Variation, Representation Independence and Factoring Out Common Behaviors define what we may expect from our reusable components — abstracted modules.

连同先前章节的模块性需求一起,类型变化,例程分组,实现变化,表示法独立,合并通用行为,五个需求定义了我们可以从可复用组件-抽象模块中所期待的东西.

 

Let us study the pre-O-O solutions to understand why they are not sufficient — but also what we should learn and keep from them in the object-oriented world.

让我们学习OO之前的解决方案来理解为什么它们并不充份-同时在面向对象的领域中防止我们所了解的缺陷再次出现.

 

Routines

例程

 

The classical approach to reusability is to build libraries of routines. Here the term routine denotes a software unit that other units may call to execute a certain algorithm, using certain inputs, producing certain outputs and possibly modifying some other data elements. A calling unit will pass its inputs (and sometimes outputs and modified elements) in the form of actual arguments. A routine may also return output in the form of a result in this case it is known as a function.

复用性的经典方式是要建立例程库.这里术语例程表示一个软件单元,其它的单元可以调用它执行一个特定的算法,使用特定的输入,产生特定的输出并且可能修改一些其它的数据元素.一个调用单元将会以实参(actual arguments)的形式传递它的输入(输出,和修改过的元素).一个例程也可能以一个结果(result)的形式返回输出;在这种情况中它被认为是一个函数(function).

 

The terms subroutine, subprogram and procedure are also used instead of routine. The first two will not appear in this book except in the discussion of specific languages (the Ada literature talks about subprograms, and the Fortran literature about subroutines.) “Procedure” will be used in the sense of a routine which does not return a result, so that we have two disjoint categories of routine: procedures and functions. (In discussions of the C language the term “function” itself is sometimes used for the general notion of routine, but here it will always denote a routine that returns a result.)

术语子例程(subroutine),子程序(subprogram)过程(procedure)也常用来替代例程.除了讨论特定的语言之外(Ada文档中涉及的是子程序, Fortran文档中涉及的是子例程.),前两者在本书中将不再出现.过程将会被用于不返回结果的例程, 所以我们有二种不同的例程: 过程及函数.(在C语言的讨论中,术语函数的本身有时作为例程的通用观念,但是在这里它总是指有返回结果的例程.)

 

Routine libraries have been successful in several application domains, in particular numerical computation, where excellent libraries have created some of the earliest success stories of reusability. Decomposition of systems into routines is also what one obtains through the method of top-down, functional decomposition. The routine library approach indeed seems to work well when you can identify a (possibly large) set of individual problems, subject to the following limitations:

例程库已经成功地运用在一些应用程序领域中,特别是在数值计算中,其中一些优秀的库开创了一些早期的有关复用性的成功故事.系统分解成例程也可以通过由上而下的,功能分解的方式获得.当您能识别一(可能是大规模的)系列独立的问题,并能遵从下列各项限制的时候,例程库的方式就真正地起作用了:

 

R1 • Each problem admits a simple specification. More precisely, it is possible to characterize every problem instance by a small set of input and output arguments.

R1 每个问题允许一件简单的规格.更精确的说,尽可能的通过一小组的输入和输出参数来描述每个问题实例的特性.

 

R2 • The problems are clearly distinct from each other, as the routine approach does not allow putting to good use any significant commonality that might exist — except by reusing some of the design.

R2 由于例程方法不允许很好地利用任何可能存在的有效共通性-除了被一些设计复用之外,所以问题之间很明显彼此不同.

 

R3 • No complex data structures are involved: you would have to distribute them among the routines using them, losing the conceptual autonomy of each module.

R3 例程库不涉及复杂的数据结构:您必须在例程使用之时分配它们,同时也失去了每个模块上的设计独立性.

 

The table searching problem provides a good example of the limitations of subroutines. We saw earlier that a searching routine by itself does not have enough context to serve as a stand-alone reusable module. Even if we dismissed this objection, however, we would be faced with two equally unpleasant solutions:

表查询问题提供了一个很好的子例程限制性的例子.我们起先看见一个查询例程本身没有足够的上下文环境作为一个独立可复用的模块来应用.然而,即使我们消除了这个障碍,我们依然会面对二个相关的,讨厌的解决方案:

 

• A single searching routine, which would try to cover so many different cases that it would require a long argument list and would be very complex internally.

一个简单的查询例程,要试图包括如此众多的不同的情况,这会导致它需要一个很长的参数列表,并且内部也会非常的复杂.

 

• A large number of searching routines, each covering a specific case and differing from some others by only a few details in violation of the Factoring Out Common Behaviors requirement candidate reusers could easily lose their way in such a maze.

在违反合并通用行为的需求中,很多的查询例程,每个都包含一种特定的情况并且只有少量细节不同于其它;那些潜在的复用者在这样的迷宫中会不知所措.

 

More generally, routines are not flexible enough to satisfy the needs of reuse. We have seen the intimate connection between reusability and extendibility. A reusable module should be open to adaptation, but with a routine the only means of adaptation is to pass different arguments. This makes you a prisoner of the Reuse or Redo dilemma: either you like the routine as it is, or you write your own.

更通常的来讲,例程不具有足够的灵活性来满足复用的需要.我们已经看到复用性和扩充性之间的亲密联系.一个可复用的模块应该对改编开放,但是改编的唯一方法是要传递不同的参数给例程.这使您陷入复用和重做的难题:或是照现在的样子使用例程,或是编写您自己的.

 

Packages

 

In the nineteen-seventies, with the progress of ideas on information hiding and data abstraction, a need emerged for a form of module more advanced than the routine. The result may be found in several design and programming languages of the period the best known are CLU, Modula-2 and Ada. They all offer a similar form of module, known in Ada as the package. (CLU calls its variant the cluster, and Modula the module. This discussion will retain the Ada term.)

在二十世纪七十年代,随着信息隐藏和数据抽象概念的发展,需要一种比例程更高级的模块形式.在当时的一些设计和编程语言中发现了这样的结果CLU, Modula-2Ada是其中最好的.他们全都提供了一个相似的模块形式,Ada中称之为包(或软件包package).(CLU称之为变量群集,Modula为模块.本讨论中将会保留Ada的术语).

 

Packages are units of software decomposition with the following properties:

包是带有下列各项特性的软件分解单元:

 

P1 • In accordance with the Linguistic Modular Units principle, “package” is a construct of the language, so that every package has a name and a clear syntactic scope.

P1,符合语言学的模块单元原则,"包"是一种语言概念,因此每个包都有一个名字和一个清晰的语法作用域.

 

P2 • Each package definition contains a number of declarations of related elements, such as routines and variables, hereafter called the features of the package.

P2,每个包定义中包含了许多相关元素的声明,像是例程和变量,这些称之为包的功能(features).

 

P3 • Every package can specify precise access rights governing the use of its features by other packages. In other words, the package mechanism supports information hiding.

P3,每个包能够指定精确的存取权限,控制着其它的包使用其功能.换句话说,包机制支持信息隐藏.

 

P4 • In a compilable language (one that can be used for implementation, not just specification and design) it is possible to compile packages separately.

P4,在一个编译语言中(可被用于实现,不只是用于规格和设计),可以对包单独地编译.

 

Thanks to P3, packages deserve to be seen as abstracted modules. Their major contribution is P2, answering the Routine Grouping requirement. A package may contain any number of related operations, such as table creation, insertion, searching and deletion. It is indeed not hard to see how a package solution would work for our example problem. Here — in a notation adapted from the one used in the rest of this book for object-oriented software — is the sketch of a package INTEGER_TABLE_HANDLING describing a particular implementation of tables of integers, through binary trees:

由于P3,包应该被视为抽象的模块.它们的主要贡献是P2,对应了例程分组的需求. 一个包可以包含任何数量的相关运算,像是表的创建,插入,查寻和删除.我们会很容易的理解包如何解决我们例子中的问题.这里,使用了一段在本书中作为面向对象软件所采用符号,来描绘了包INTEGER_TABLE_HANDLING,它通过二进制树描述了整数表一个特别的实现:

 

package INTEGER_TABLE_HANDLING feature

type INTBINTREE is

record

-- Description of representation of a binary tree, for example:

info: INTEGER

left, right: INTBINTREE

end

new: INTBINTREE is

-- Return a new INTBINTREE, properly initialized.

do ¼ end

has (t: INTBINTREE x: INTEGER): BOOLEAN is

-- Does x appear in t?

do ¼ Implementation of searching operation ¼ end

put (t: INTBINTREE x: INTEGER) is

-- Insert x into t.

do ¼ end

remove (t: INTBINTREE x: INTEGER) is

-- Remove x from t.

do ¼ end

end -- package INTEGER_TABLE_HANDLING

 

This package includes the declaration of a type (INTBINTREE), and a number of routines representing operations on objects of that type. In this case there is no need for variable declarations in the package (although the routines may have local variables).

这个包包括了一个类型(INTBINTREE)的声明,和许多在这个类型对象上的运算例程.在这种情况下并不需要在包里声明变量(虽然例程可能有局部变量).

 

Client packages will now be able to manipulate tables by using the various features of INTEGER_TABLE_HANDLING. This assumes a syntactic convention allowing a client to use feature f from package P let us borrow the CLU notation: P$f. Typical extracts from a client of INTEGER_TABLE_HANDLING may be of the form:

户端包能够通过使用INTEGER_TABLE_HANDLING的各种功能操纵表.里假设一个语法约定允许一个户端使用来自包P的功能f让我们借CLU的符号: P$f. 来自INTEGER_TABLE_HANDLING的一个户端典型的用法可能是这种形式:

 

-- Auxiliary declarations:

x: INTEGER b: BOOLEAN

-- Declaration of t using a type defined in INTEGER_TABLE_HANDLING:

t: INTEGER_TABLE_HANDLING$INTBINTREE

-- Initialize t as a new table, created by function new of the package:

t := INTEGER_TABLE_HANDLING$new

-- Insert value of x into table, using procedure put from the package:

INTEGER_TABLE_HANDLING$put (t, x)

-- Assign True or False to b, depending on whether or not x appears in t

-- for the search, use function has from the package:

b := INTEGER_TABLE_HANDLING$has (t, x)

 

Note the need to invent two related names: one for the module, here INTEGER_TABLE_HANDLING, and one for its main data type, here INTBINTREE. One of the key steps towards object orientation will be to merge the two notions. But let us not anticipate.

需要注意的是二个相关的命名:一个是模块命名,这里是INTEGER_TABLE_HANDLING,另一个是为了它的主要数据类型,INTBINTREE.一个面向对象的关键步骤是合并这两个符号.但我们先不这么做.

 

A less important problem is the tediousness of having to write the package name (here INTEGER_TABLE_HANDLING) repeatedly. Languages supporting packages solve this problem by providing various syntactic shortcuts, such as the following Ada-like form:

一个不太重要的问题是不得不不断地写包的名字(这里是INTEGER_TABLE_HANDLING),这很令人厌烦.支持包的语言提供各种不同的语法捷径来解决这个问题, 如下列像Ada一样的形式:

 

with INTEGER_TABLE_HANDLING then

¼ Here has means INTEGER_TABLE_HANDLING$has, etc. ¼

end

 

Another obvious limitation of packages of the above form is their failure to deal with the Type Variation issue: the module as given is only useful for tables of integers. We will shortly see, however, how to correct this deficiency by making packages generic.

对于上述形式的包,另一个明显的限制是它们无法处理类型变化的议题: 给定的模块只能对整数表有效.然而,我们不久就会知道,该如何通过包的泛化来改正这个缺点.

 

The package mechanism provides information hiding by limiting clients’ rights on features. The client shown on the preceding page was able to declare one of its own variables using the type INTBINTREE from its supplier, and to call routines declared in that supplier but it has access neither to the internals of the type declaration (the record structure defining the implementation of tables) nor to the routine bodies (their do clauses). In addition, you can hide some features of the package (variables, types, routines) from clients, making them usable only within the text of the package.

通过在功能上限制客户端的权限,包机制提供了信息隐藏.在前页所示的客户端能够声明一个它自己的变量,使用供应者所提供的类型INTBINTREE,并调用在其供应者中被声明的例程;但是它既不对所声明的类型(定义表实现的record结构)内部存取,也不存取例程本身(它们的do子句).除此之外,您能对客户端隐藏包(变量,类型,例程)里面的一些功能,使得它们只有在包的代码内部中才可用.

 

Languages supporting the package notion differ somewhat in the details of their information hiding mechanism. In Ada, for example, the internal properties of a type such as INTBINTREE will be accessible to clients unless you declare the type as private.

对于信息隐藏机制的细节,那些支持包观念的语言稍微会有些不同.例如,在Ada中,像是INTBINTREE这样的类型的内部属性对用户端来说是可用的,除非您声明类型是私有的(private).

 

Often, to enforce information hiding, encapsulation languages will invite you to declare a package in two parts, interface and implementation, relegating such secret elements as the details of a type declaration or the body of a routine to the implementation part. Such a policy, however, results in extra work for the authors of supplier modules, forcing them to duplicate feature header declarations. With a better understanding of Information Hiding we do not need any of this. More in later chapters.

为了强调信息阴藏,通常封装语言会让您在接口和实现这二部份中声明包,把类型声明的细节或例程的本身这样的秘密元素转移至实现部分.然而,这样的一个政策为供应模块的作者造成额外的负担,强迫他们复制特性表头的声明.随着对信息隐藏的深入理解,我们并不需要这些.在稍后的章节中会有更多的讨论.

 

Packages: an assessment

有关评估

 

Compared to routines, the package mechanism brings a significant improvement to the modularization of software systems into abstracted modules. The possibility of gathering a number of features under one roof is useful for both supplier and client authors:

与例程相比,包机制通过抽象的模块对软件系统模块化带来了显著地提升.把众多特性聚集在一起的可能性对供应者和客户端作者都是有用的:

 

• The author of a supplier module can keep in one place and compile together all the software elements relating to a given concept. This facilitates debugging and change. In contrast, with separate subroutines there is always a risk of forgetting to update some of the routines when you make a design or implementation change you might for example update new, put and has but forget remove.

模块供应者能把所有涉及给定概念的软件元素保存在同一个地方,并且一起编译. 这有利于除错和变化.如果不这样的话,当您在作一些设计或实现上的变化时,因为存在分离的子例程,总是会有忘记更新一些例程的风险;举例来说, 您可能更新了new, puthas, 但是忘了remove.

 

• For client authors, it is obviously easier to find and use a set of related facilities if they are all in one place.

对客户端作者而言,如果例程全都是在一个地方,明显地能够比较容易地找到,并且很容易地运用一系列相关的工具.

 

The advantage of packages over routines is particularly clear in cases such as our table example, where a package groups all the operations applying to a certain data structure.

在很多的情况中,包胜过例程的优势特别明显,如表的例子,在里面一个包聚集了所有适用于某个数据结构运算.

 

But packages still do not provide a full solution to the issues of reusability. As noted, they address the Routine Grouping requirementbut they leave the others unanswered. In particular they offer no provision for factoring out commonality. You will have noted that INTEGER_TABLE_HANDLING, as sketched, relies on one specific choice of implementation, binary search trees. True, clients do not need to be concerned with this choice, thanks to information hiding. But a library of reusable components will need to provide modules for many different implementations. The resulting situation is easy to foresee: a typical package library will offer dozens of similar but never identical modules in a given area such as table management, with no way to take advantage of the commonality. To provide reusability to the clients, this technique sacrifices reusability on the suppliers’ side.

但是对于复用性议题,包仍然不能提供完整的解决方案.如上所示,它们解决了例程分组的需求;但是它们无法处理其余的要求.特别是它们并没有为合并通用行为提供准备.您已经注意到了做为描绘略图的INTEGER_TABLE_HANDLING,依赖一种特定的实现-二进制查询树的选择.实际上,由于信息隐藏的原因,客户端并不需要考虑这种选择.但是由于许多不同的实现,一个可复用的组件库需要提供给模块.产生的情形很容易预见:像是表管理这种特定的领域里,一个典型的包的库会提供几十个相似的但是从不同样的模块,没有什么方法可以利用共通性.为了要提供复用性给客户端,这种技术在供应者一方牺牲了复用性.

 

Even on the clients’ side, the situation is not completely satisfactory. Every use of a table by a client requires a declaration such as the above:

t: INTEGER_TABLE_HANDLING$INTBINTREE

forcing the client to choose a specific implementation. This defeats the Representation Independence requirement: client authors will have to know more about implementations of supplier notions than is conceptually necessary.

至于在客户端的一方,情形也并不完全令人满意.被客户端使用的每一个表都需要一个如下的声明:

t: INTEGER_TABLE_HANDLING$INTBINTREE

强迫客户端选择一个明确的实现.这违反了表示法独立的需求:客户端作者将不得不了解有关供应者想法的实现,这大大超出必须要了解的概念.

 

4.8 OVERLOADING AND GENERICITY

4.8 重载和泛型

 

Two techniques, overloading and genericity, offer candidate solutions in the effort to bring more flexibility to the mechanisms just described. Let us study what they can contribute.

重载和泛型,这二种技术比刚才所描述的机制给候选的解决方案带来了更多的灵活性.让我们来学习它们所能提供的好处.

 

Syntactic overloading

语法

 

Overloading is the ability to attach more than one meaning to a name appearing in a program.

重载是指对于在程序中出现的一个名字付于超过一个以上的意义的能力.

 

The most common source of overloading is for variable names: in almost all languages, different variables may have the same name if they belong to different modules (or, in the Algol style of languages, different blocks within a module).

最通常的重载的来源是为了变量名: 在几乎所有的语言中,如果属于不同的模块(或者,在Algol风格的语言中,在一个模块里面的不同的区块中),不同的变量可以有着相同的名字.

 

More relevant to this discussion is routine overloading, also known as operator overloading, which allows several routines to share the same name. This possibility is almost always available for arithmetic operators (hence the second name): the same notation, a + b, denotes various forms of addition depending on the types of a and b (integer, single-precision real, double-precision real). But most languages do not treat an operation such as "+" as a routine, and reserve it for predefined basic types — integer, real and the like. Starting with Algol 68, which allowed overloading the basic operators, several languages have extended the overloading facility beyond language built-ins to user-defined operations and ordinary routines.

更多相关地讨论是例程重载(routine overloading),也即是运算符重载,其允许一些例程有相同的名字.这种可能性几乎总是适用于算术运算符(第二个名字由此而来): 相同的符号,a+b,示了ab类型(整数,单精度实数,双精度实数)所决定各种不同形式的加法.但是绝大多数的语言不把诸如+这样的运算看做一个例程,而视为预先定义好的基本类型的保留符,如整数,实数等等.从允许重载基本运算符的Algol 68开始,一些语言已经延伸了重载的使用范围,从语言的内置定义到用户自定义的运算和常规例程.

 

In Ada, for example, a package may contain several routines with the same name, as long as the signatures of these routines are different, where the signature of a routine is defined here by the number and types of its arguments. (The general notion of signature also includes the type of the results, if any, but Ada resolves overloading on the basis of the arguments only.) For example, a package could contain several square functions:

square (x: INTEGER): INTEGER is do ¼ end

square (x: REAL): REAL is do ¼ end

square (x: DOUBLE): DOUBLE is do ¼ end

square (x: COMPLEX): COMPLEX is do ¼ end

Then, in a particular call of the form square (y), the type of y will determine which version of the routine you mean.

举例来说,Ada,一个包可以包含一些相同名字的例程,只要这些例程的标记式(signature)是不同的,在这里,例程的标记式是由其参数的数目和类型所定义的.(如果可能的话,标记式的通用概念也包括结果的类型,但是Ada中的重载只以参数为基础.) 举例来说,一个包可以包含一些平方函数:

square (x: INTEGER): INTEGER is do ¼ end

square (x: REAL): REAL is do ¼ end

square (x: DOUBLE): DOUBLE is do ¼ end

square (x: COMPLEX): COMPLEX is do ¼ end

然后,square (y)的一个特定调用中,y的类型将决定您想使用的例程的版本.

 

A package could similarly declare a number of search functions, all of the form

has (t: “SOME_TABLE_TYPE” x: ELEMENT) is do ¼ end

supporting various table implementations and differing by the actual type used in lieu of “SOME_TABLE_TYPE”. The type of the first actual argument, in any client’s call to has, suffices to determine which routine is intended.

一个包可以同样定义许多的查询函数,所有的形式

has (t: “SOME_TABLE_TYPE” x: ELEMENT) is do ¼ end

通过代替“SOME_TABLE_TYPE”实际使用的类型支持各种不同的表实现和差异. 在任何客户端调用has,第一个实参的类型足以决定使用哪个例程.

 

These observations suggest a general characterization of routine overloading, which will be useful when we later want to contrast this facility with genericity:

这些实例暗示了例程重载的一个总的特性,当我们稍后用泛型对比这个技术时其会非常有用.

Role of overloading

Routine overloading is a facility for clients. It makes it possible to write the same client text when using different implementations of a certain concept.

例程重载是一种用户端的技术.当使用一项特定概念的不同实现的时候,它使编写相同的客户端代码成为可能.

 

 

 



 

What does routine overloading really bring to our quest for reusability? Not much. It is a syntactic facility, relieving developers from having to invent different names for various implementations of an operation and, in essence, placing that burden on the compiler. But this does not solve any of the key issues of reusability. In particular, overloading does nothing to address Representation Independence. When you write the call

has (t, x)

you must have declared t and so (even if information hiding protects you from worrying about the details of each variant of the search algorithm) you must know exactly what kind of table t is! The only contribution of overloading is that you can use the same name in all cases. Without overloading each implementation would require a different name, as in

has_binary_tree (t, x)

has_hash (t, x)

has_linked (t, x)

对复用性的探索,例程重载真正带给我们了什么?不是很多.它是语法的功能,基本上减轻了开发者必须为一个运算的各种不同的实现发明不同的名字的负担,并把这个负担推给了编译器.但是这并不能解决任何的复用性的关键议题.特别地,重载对表示法独立什么也没做.当您编写调用has (t, x) 的时候,您必须先声明t,而且因此(即使信息阴藏保护您免于为查询算法的每个变量的细节担忧)您一定完全地清楚表t是什么类型! 重载的唯一贡献是您能在所有的情况下使用相同的名字而已.没有重载的话,每个实现则会需要一个不同的名字,如

has_binary_tree (t, x)

has_hash (t, x)

has_linked (t, x)

 

Is the possibility of avoiding different names a benefit after all? Perhaps not. A basic rule of software construction, object-oriented or not, is the principle of non-deception: differences in semantics should be reflected by differences in the text of the software. This is essential to improve the understandability of software and minimize the risk of errors. If the has routines are different, giving them the same name may mislead a reader of the software into believing that they are the same. Better force a little more wordiness on the client (as with the above specific names) and remove any danger of confusion.

避免不同名字的可能性终究是一种优点吗?也许不.面向对象或非面向对象的软件构造的一个基本规则是非欺骗的原则(principle of nondeception): 语义学上的不同应该反映在软件代码上的不同.这对改良软件的理解性和将错误的危险减到最少是很必要的.如果has例程是不同的,那么给它们相同的名字也许会误导软件的一个读者,使其相信它们并非不同.更好的方法是强迫在客户端上多加上一些文字(如同上述的特定的名字)并能消除任何混乱的危险.

 

The further one looks into this style of overloading, the more limited it appears. The criterion used to disambiguate calls — the signature of argument lists — has no particular merit. It works in the above examples, where the various overloads of square and has are all of different signatures, but it is not difficult to think of many cases where the signatures would be the same. One of the simplest examples for overloading would seem to be, in a graphics system, a set of functions used to create new points, for example under the form

p1 := new_point (u, v)

更进一步研究重载的这种风格,就愈会发现它的局限.过去一直用于消除调用歧义的标准-参数列表的标记式-没有特别的价值.在上述的例子中它可以工作, 例子中squarehas的各种不同的重载有足够不同的标记式,但是很容易想到的许多情况中标记式会是相同的.一个最简单的重载例子是在图形系统中一组产生新的点的函数,如下所示:

p1 := new_point (u, v)

 

There are two basic ways to specify a new point: through its cartesian coordinates x and y (the projections on the horizontal axis), and through its polar coordinates r and q (the distance to the origin, and the angle with the horizontal axis). But if we overload function new_point we are in trouble, since both versions will have the signature

new_point (p, q: REAL): POINT

这里有两个基本的方法来指定一个新的点: 通过它的笛卡儿坐标xy(在水平轴上的投影), 和通过它的极坐标rq (到原点的距离,和在水平轴上的角度). 但是如果我们重载new_point函数,那么我们就会遇到麻烦,因为这两个版本都有相同的new_point (p, q: REAL): POINT标记式.

 

This example and many similar ones show that type signature, the criterion for disambiguating overloaded versions, is irrelevant. But no better one has been proposed.

这个例子和许多相似的情况都显示了类型标记式并不是消除重载版本歧义的准则.但是却没有更好地提议.

 

The recent Java language regrettably includes the form of syntactic overloading just described, in particular to provide alternative ways to create objects.

遗憾的是,近来的JAVA语言包括了仅仅用来描述的重载语法的形式,而没有特别提供其它可能的方法以产生对象.

 

Semantic overloading (a preview)

重载语义(概述)

 

The form of routine overloading described so far may be called syntactic overloading. The object-oriented method will bring a much more interesting technique, dynamic binding, which addresses the goal of Representation Independence. Dynamic binding may be called semantic overloading. With this technique, you will be able to write the equivalent of has (t, x), under a suitably adapted syntax, as a request to the machine that executes your software. The full meaning of the request is something like this:

到现在为止,我们所描述的重载例程形式可以被称之为重载语法(syntactic overloading).面向对象的方法将会带来更加有趣的技术,动态绑定,其实现了表示法独立的目标.动态绑定可以称之为重载语义(semantic overloading).通过此技术,并借助于一个对应的适当语法,您将能够编写has (t, x)的类似例程作为一个在运行您软件的机器上的请求.这个请求的完整意义有点像下列的描述:

 

Dear Hardware-Software Machine:

 

Please look at what t is I know that it must be a table, but not what table implementation its original creator chose — and to be honest about it I’d much rather remain in the dark. After all, my job is not table management but investment banking [or compiling, or computer-aided-design etc.]. The chief table manager here is someone else. So find out for yourself about it and, once you have the answer, look up the proper algorithm for has for that particular kind of table. Then apply that algorithm to determine whether x appears in t, and tell me the result. I am eagerly waiting for your answer.

 

I regret to inform you that, beyond the information that t is a table of some kind and x a potential element, you will not get any more help from me.

 

With my sincerest wishes,

 

Your friendly application developer.

 

亲爱的硬件-软件机器:

请考虑t是什么;我知道它一定是一个表,但是不清楚表的最初创建者所选择的实现.我宁可保持一无所知.毕竟,我的工作不是表的管理而是投资银行业务[或编译,或计算机辅助设计等等].在这里,主要的表管理者是其他人.因此,请为自己找出有关t的答案,一旦您找到了,对应于特定的表类型的has例程找出适当的算法.然后应用此算法去确定在t中是否有x,并且告诉我结果.我正在热切地期待您的回答.

 

我很遗憾的告知您,除了t是某种类型的表和x是一个可能的元素这两个信息, 您将不能从我这儿得到任何的帮助.

 

此致

敬礼

您忠实的应用开发者

 

Unlike syntactic overloading, such semantic overloading is a direct answer to the Representation Independence requirement. It still raises the specter of violating the principle of non-deception the answer will be to use assertions to characterize the common semantics of a routine that has many different variants (for example, the common properties which characterize has under all possible table implementations).

不同于重载语法,这种重载语义是表示法独立需求的直接答案.它还引起了违犯非欺骗原则的担心;对于一个具有许多不同变体的例程(例如,描述在所有可能的表实现之内的has的公共特性),答案是要使用断言来描述其公共语义上的特性.

 

Because semantic overloading, to work properly, requires the full baggage of object orientation, in particular inheritance, it is understandable that non-O-O languages such as Ada offer syntactic overloading as a partial substitute in spite of the problems mentioned above. In an object-oriented language, however, providing syntactic overloading on top of dynamic binding can be confusing, as is illustrated by the case of C++ and Java which both allow a class to introduce several routines with the same name, leaving it to the compiler and the human reader to disambiguate calls.

因为要使语义重载正确的工作,需要面向对象的全部理论,特别是继承,像是Ada这样的非OO语言提供语法重载作为一个部分的替代,而不管上面所提到的问题, 这可以理解.然而,在面向对象的语言中,在动态绑定之前提供语法重载可能会使人困惑,如同C++和JAVA的情况所阐述的,两者都允许一个类引入几个有着相同名字的例程,让编译器和读者(人)来区分其调用.

 

Genericity

泛型

 

Genericity is a mechanism for defining parameterized module patterns, whose parameters represent types.

泛型是定义参数化模块模式的一个机制,它的参数描述了类型.

 

This facility is a direct answer to the Type Variation issue. It avoids the need for many modules such as

INTEGER_TABLE_HANDLING

ELECTRON_TABLE_HANDLING

ACCOUNT_TABLE_HANDLING

by enabling you instead to write a single module pattern of the form

TABLE_HANDLING [G]

where G is a name meant to represent an arbitrary type and known as a formal generic parameter. (We may later encounter the need for two or more generic parameters, but for the present discussion we may limit ourselves to one.)

这种功能是类型变化议题的直接回答.它通过让您能够改写成一个单一模块模式的形式TABLE_HANDLING [G] 避免了象下面这些模块的需求

INTEGER_TABLE_HANDLING

ELECTRON_TABLE_HANDLING

ACCOUNT_TABLE_HANDLING

这里,G是一个表达了一个任意的类名字,是一个泛化形参(formal generic parameter). (我们稍后可能遇到二更多的化形参的需,但是现在的讨论我们限制为一个.)

 

Such a parameterized module pattern is known as a generic module, although it is not really a module, only a blueprint for many possible modules. To obtain one of these actual modules, you must provide a type, known as an actual generic parameter, to replace G the resulting (non-generic) modules are written for example

TABLE_HANDLING [INTEGER]

TABLE_HANDLING [ELECTRON]

TABLE_HANDLING [ACCOUNT]

using types INTEGER, ELECTRON and ACCOUNT respectively as actual generic parameters. This process of obtaining an actual module from a generic module (that is to say, from a module pattern) by providing a type as actual generic parameter will be known as generic derivationthe module itself will be said to be generically derived.

这样的一个参数化模块模式即为泛化模块(generic module),虽然它并不是真正的一个模块,只是许多可能模块的一个蓝图.为了要获得这些真实的模块,您必须要提供一个类型,即是一个泛化实参(actual generic parameter)来替代G结果的(非泛化的)模块如下

TABLE_HANDLING [INTEGER]

TABLE_HANDLING [ELECTRON]

TABLE_HANDLING [ACCOUNT]

分别地使用INTEGER, ELECTRONACCOUNT类型作为泛化实参.通过提供一个化实参的类型,从泛化模块(也就是说,从一个模块模式)获得一个真正模块的过程被视为泛化派生(generic derivation)模块的本身被称之为泛化派生模块.

 

Two small points of terminology. First, generic derivation is sometimes called generic instantiation, a generically derived module then being called a generic instance. This terminology can cause confusion in an O-O context, since “instance” also denotes the run-time creation of objects (instances) from the corresponding types. So for genericity we will stick to the “derivation” terminology.

有两个小的术语声明.首先,泛化派生有时被称之为泛化实例(generic instantiation),一个泛化派生模块被称为一个泛化实体(generic instance).由于"实体"也表示运行时创建的,来自对应类型的对象,这一种用辞能引起OO上下文的混乱,所以有关泛型我们会坚持使用"派生"术语.

 

Another possible source of confusion is “parameter”. A routine may have formal arguments, representing values which the routine’s clients will provide in each call. The literature commonly uses the term parameter (formal, actual) as a synonym for argument (formal, actual). There is nothing wrong in principle with either term, but if we have both routines and genericity we need a clear convention to avoid any misunderstanding. The convention will be to use “argument” for routines only, and “parameter” (usually in the form “generic parameter” for further clarification) for generic modules only.

另一个可能的混乱源头是"参数(parameter)".一个例程可以有式自变量(formal arguments),描述例程的客户端在每个调用中将会提供的数值.学术上普遍使用术语参数(parameter)(式的,实际的)作为自变量(argument)的同义字(式的,实际的).对于任一个术语大体而言没有什么毛病,但是如果我们有例程和泛型,那么我们需要一个清晰的约定以避免任何的歧义.对例程约定只使用自变量argument,泛化模块只用参数parameter(为了更清楚地表达,通常用泛化参数的形式).

 

Internally, the declaration of the generic module TABLE_HANDLING will resemble that of INTEGER_TABLE_HANDLING above, except that it uses G instead of INTEGER wherever it refers to the type of table elements. For example:

从里面看, 泛化TABLE_HANDLING的声明类似于上述的INTEGER_TABLE_HANDLING,除了使用G代替INTEGER用以指代表元素的类型.譬如:

 

package TABLE_HANDLING [G] feature

type BINARY_TREE is

record

info: G

left, right: BINARY_TREE

end

has (t: BINARY_TREE x: G): BOOLEAN

-- Does x appear in t?

do ¼ end

put (t: BINARY_TREE x: G) is

-- Insert x into t.

do ¼ end

(Etc.)

end -- package TABLE_HANDLING

 

It is somewhat disturbing to see the type being declared as BINARY_TREE, and tempting to make it generic as well (something like BINARY_TREE [G]). There is no obvious way to achieve this in a package approach. Object technology, however, will merge the notions of module and type, so the temptation will be automatically fulfilled. We will see this when we study how to integrate genericity into the object-oriented world.

看到类型被声明为BINARY_TREE是有点烦人,而且也诱惑您把它定义成泛型(有点象BINARY_TREE[G]). 在包的方式中并没有显而易见的方法来完成这些动作.然而,对象技术会合并模块和类型的观念,这样,这些诱惑将会被自动地实现.当我们学习如何把泛型整合到面向对象领域的时候,我们就会了解了.

 

It is interesting to define genericity in direct contrast with the definition given earlier for overloading:

对比于先前给出的重载定义,定义泛型是有趣的:

Role of genericity

Genericity is a facility for the authors of supplier modules. It makes it possible to write the same supplier text when using the same implementation of a certain concept, applied to different kinds of object.

对于提供模快的作者来说,泛型是一个工具.当使用一个特定概念的相同实现的时候,它可以编写相同的代码应用到不同的类型对象上.

 



 

What help does genericity bring us towards realizing the goals of this chapter? Unlike syntactic overloading, genericity has a real contribution to make since as noted above it solves one of the main issues, Type Variation. The presentation of object technology in part C of this book will indeed devote a significant role to genericity.

对于实现本章的目标,泛型带给我们什么?不像重载语法,如上所述由于泛型解决一个主要的议题,类型变化,其有了一个真正的贡献.本书的C部份中,对象技术的表示法将会贡献一个重要的角色给于泛型.

 

Basic modularity techniques: an assessment

基本模块技术评估

 

We have obtained two main results. One is the idea of providing a single syntactic home, such as the package construct, for a set of routines that all manipulate similar objects. The other is genericity, which yields a more flexible form of module.

我们已经得到了二个主要的结果.一是提供一个单一语法组的概念给一组操纵相似对象的例程,就像包的构造.另一个是泛型,它产生一个更灵活的模块形式.

 

All this, however, only covers two of the reusability issues, Routine Grouping and Type Variation, and provides little help for the other three — Implementation Variation, Representation Independence and Factoring Out Common Behaviors. Genericity, in particular, does not suffice as a solution to the Factoring issue, since making a module generic defines two levels only: generic module patterns, parameterized and hence open to variation, but not directly usable and individual generic derivations, usable directly but closed to further variation. This does not allow us to capture the fine differences that may exist between competing representations of a given general concept.

然而,所有这些只包括了两个复用性议题,例程分组和类型变化,对另外三个-实现变化,表示法独立和合并通用行为-却没什么太大的帮助.特别地,泛型对于合并议题的解决方案并不足够,这是由于使一个模块泛化只定义二个层次: 泛化模块模式,参数化和因此而来的对变化开放,但这并不直接有效;独立的泛化派生,可直接应用但是对进一步的变化关闭.这不允许我们捕获细微的差异,这些差异可能存在与一项特定的一般概念的表示法竞争之间.

 

On Representation Independence, we have made almost no progress. None of the techniques seen so far — except for the short glimpse that we had of semantic overloading — will allow a client to use various implementations of a general notion without knowing which implementation each case will select.

在表示法独立上,我们几乎止步不前.除了我们一带而过的重载语义以外,迄今为止没有什么技术会允许一个客户端使用一个一般概念的各种不同的实现,而不必知道每种情况中将会选择的实现.

 

To answer these concerns, we will have to turn to the full power of objectoriented concepts.

要解答这些关注的问题,我们将不得不转向面向对象概念的强大威力.

 

4.9 KEY CONCEPTS INTRODUCED IN THIS CHAPTER

4.9 摘要

 

• Software development is a highly repetitive activity, involving frequent use of common patterns. But there is considerable variation in how these patterns are used and combined, defeating simplistic attempts to work from off-the-shelf components.

·        软件开发是一个高度重复的活动,包括频繁的使用通用模式.但是这些模式在如何使用和组合上有着相当的变化,这妨碍了应用现成组件的这种过分单纯地尝试.

 

• Putting reusability into practice raises economical, psychological and organizational problems the last category involves in particular building mechanisms to index, store and retrieve large numbers of reusable components. Even more important, however, are the underlying technical problems: commonly accepted notions of module are not adequate to support serious reusability.

·        实现复用性引发经济上的,心理上的和组织上的问题;特别的,最终的范畴包括了索引,储存和检索大量可复用组件的构建机制.然而,更重要的是下列的技术问题: 有关模块的普遍接受的概念并不能足以支持复杂的复用性.

 

• The major difficulty of reuse is the need to combine reuse with adaptation. The “reuse or redo” dilemma is not acceptable: a good solution must make it possible to retain some aspects of a reused module and adapt others.

·        复用的主要困难是结合复用和改写的需要.复用或重做的选择让人为难:一个好的解决方案必须使之可能,既保留一个被复用模块的一些部分并改写其它的部分.

 

• Simple approaches, such as reuse of personnel, reuse of designs, source code reuse, and subroutine libraries, have experienced some degree of success in specific contexts, but all fall short of providing the full potential benefits of reusability.

·        简单的方法,像是人事的复用,设计的复用,源代码的复用和子例程库的复用,在特定的环境中已经经历了一定程度地成功,但是却无法提供复用性完整的可能的优势.

 

• The appropriate unit of reuse is some form of abstracted module, providing an encapsulation of a certain functionality through a well-defined interface.

·        正确的复用单元是一些抽象模块的形式,经过一个定义明确的接口提供具有一个特定功能性的封装.

 

• Packages provide a better encapsulation technique than routines, as they gather a data structure and the associated operations.

·        由于聚集了一个数据结构和相关的运算,包提供了比例程更好的封装技术.

 

• Two techniques extend the flexibility of packages: routine overloading, or the reuse of the same name for more than one operation genericity, or the availability of modules parameterized by types.

·        二种技术扩充了包的灵活性:例程重载,对多个运算的相同名字的复用;泛形, 类型参数化的模块有效性.

 

• Routine overloading is a syntactic facility which does not solve the important issues of reuse, and harms the readability of software texts.

·        例程重载是一个语法工具,其不能解决复用的重要议题,而且伤害软件代码的可读性.

 

• Genericity helps, but only deals with the issue of type variation.

·        泛形帮助而且解决了类型变化的议题.

 

• What we need: techniques for capturing commonalities within groups of related data structure implementations and techniques for isolating clients from having to know the choice of supplier variants.

·        什么是我们所需的: 在相关的数据结构实现里捕获共通性的技术;从必须了解所提供的变体的选择中隔离客户端的技术.

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页