5.4 Inheritance
Inheritance means that we can develop a new class merely by stating how it differs from another, already existing class. The new class then inherits the existing class. The main advantage with this approach is that existing classes can be reused to a great extent. The more general classes are placed higher up in the inheritance hierarchy, whereas the more specialized ones are placed lower down. It is not only classes which have been designed for the current system that can be reused, but also those designed earlier, maybe in earlier projects. In Smalltalk, this is one of the major ideas. Smalltalk is delivered with an extensive class library and, as such, the programming is based to a large extent on reusing these classes, liven other object-oriented languages, such as C++, Eiffel and Objective-C, have class libraries.
继承这种设计方式意味着我们可以非常方便设计新的类,仅仅通过明确定义新的类和已知的类有那些差别的方式. 这样一来新的类就可以直接从已知类中进行继承. 这种设计方式最主要的优势在于能够最大限度的复用已经存在的类. 在层次化的继承结构中,通常更加通用的类general classes被放置在顶端的位置,而那些更加特殊的类specialized ones则被放置在底部的位置. 依靠继承这种设计方法,不光是在当前项目中已经设计好的类可以直接拿来复用, 那些在前期项目中应用的对象也可以拿来复用.在Smalltalk中,这是一种非常主要的设计思路. Smalltalk的开发环境是和一个扩展的类库同步交付的, 基于这样一种设计方式,程序设计在很大程度是基于这些类库的重用. 其他流行的面向对象开发语言,比如C++, Eiffel and Objective-C,也拥有自己的类库.
The inheritance mechanism thus simplifies the process of reuse. In traditional programming languages, procedures are the reuse 'level', but in an object-oriented programming language, the reuse levels are classes. A class contains several operations (procedures) and a data structure. This makes the reuse of classes a much more powerful feature than the reuse of procedures. The inheritance mechanism enables the reuse of whole classes and class hierarchies. However, inheritance is not a prerequisite for reuse, as will be discussed in Chapter 11.
利用继承的机制能够非常方便的简化重用的过程.在传统的程序设计语言中,过程(procedures)是最小的复用级别,然而在一个面向对象的编程语言中,类是最小的复用级别. 一个类可以包含若干个操作operations (procedures)和一个数据结构. 由于类的结构中操作和数据结构是聚合在一次,这种基于类的复用方式比仅仅基于操作的复用方式更加强大和方便.而层次化的继承结构这种机制允许程序设计人员针对所有的类,和层次化的类结构进行复用.然而,继承是一种非常方便的复用机制,而并非复用的前提条件,在第11章中,我们还会进一步深入的讨论.
Inheritance enables modifications to be performed in a simple way, as a property common to several classes is implemented in a class inherited by these classes. If we wish to modify this property, we need only modify the corresponding ancestor to update the characteristic in all descendants.
继承这种面向对象的设计特点允许程序设计者通过一种相对比较简单的方式来对程序结构进行局部变更,具体的原理是通过把几个类公共的,特性,行为和数据结构在一个祖先类中实现,而这个祖先类可以供其他几个类来继承使用.假如我们想调整这个属性,我们仅仅需要调整对应的祖先类,这样一来所有后裔类的属性也同步得当了调整.
As a result of an operation being possibly defined in a class higher up in the inheritance hierarchy, the previously outlined algorithm for operation searching will need to be slightly modified. If we cannot find the operation in the instance's class, we must proceed to the class' parent, and so on. Thus the class must have a reference, parent, to its parent class. Figure 5.8 illustrates this.
在层次化的继承结构中,和一个类相关联的操作有可能不是在这个类的本地进行定义,而有可能是在层次化继承结构中的祖先类当中进行定义,前面提到的当一个类接收到一个激励以后,所执行的操作搜索算法将会需要轻微的调整.假如我们在一个实例对象所属于的类当中无法搜索到对应操作,我们就必须在这个类的父亲类中继续搜索,这种继续将会持续到层次化继承结构的顶层. 这样一来,每一个类必须要一个索引号,用于索引到他对应的父亲类,图5-8揭示了这种关系.
If the operation does not exist in the environment referred to by parent, then the search is continued upwards in the hierarchy until, if necessary, the root class is accessed. (The root class is the class at the top, that is, the root, of the inheritance hierarchy.) If the operation is still not found in the root class, then there is no interpretation for the received stimulus and an error occurs. Algorithm 5.2 for binding a stimulus to an operation will, therefore, now look as follows:
假如接收激励对应的操作在父亲类的运行环境中还是无法找到,那么这种针对操作的搜索会沿着层次化继承结构向上继续进行, 直到找到对应的操作为止,在必要的情况下,相应的根类也会被搜索到. (其中根类指的位于是层次化继承机构顶层的祖先类) . 假如搜索持续到根类都无法查找到, 那么接收类就没有办法针对这个接收的激励进行解释,同时一个错误将会产生. 算法5,2 介绍了如何将一个激励和操作进行绑定和关联操作,详细的过程描述如下:
Algorithm 5.2 Binding a stimulus to an operation with inheritance
Given an instance environment and a stimulus to the instance:
1. In the environment referred to by instanceOf the operation corresponding to the stimulus' name is searched for.
2. If the operation is not found, perform Step 1 in the environment referred to by parent. Continue until the operation is found. If parent does not refer fo another class, the stimulus is unknown. In 'this case perform error handling.
1. Interpret the corresponding operation using both the stimulus' parameters and the instance's environment.
1. Return any values from the execution of the operation.
Algorithm 5.2 在对象实例中把激励和操作进行绑定关联
Given an instance environment and a stimulus to the instance:
给定一个对象实例的运行环境并发送一个激励给他:
1. In the environment referred to by instanceOf the operation corresponding to the stimulus' name is searched for. 在实例变量的参数instanceOf所索引到类的运行环境中,和激励名称所对应的操作将会被进行搜索.
2. If the operation is not found, perform Step 1 in the environment referred to by parent. Continue until the operation is found. If parent does not refer fo another class, the stimulus is unknown. In 'this case perform error handling.假如对应的操作没有被搜索到,在在实例变量的参数instanceOf所索引到类的父亲类的运行环境中重复进行步骤1的搜索,这种搜索在层次化的继承结构中会一直继续,直到对应的操作被找到.假如搜索到最后,父亲类无法继续索引到一个新的祖先类,而激励还是未知的.在这种场景下,将执行一个错误处理.
3. Interpret the corresponding operation using both the stimulus' parameters and the instance's environment. 假如匹配操作成功,则使用激励携带的参数和对象实例的运行环境来解释对应的操作.
4. Return any values from the execution of the operation 操作执行完毕以后,返回操作执行完毕以后的任何取值.
Inheritance can be used in different ways, as discussed in Chapter 3. Abstract classes can be used to define a common protocol for several classes. The protocol then only provides the operation's signature, that is, which stimuli can be received, while the actual implementation may exist in the descendants. Thus the implementation can be different for different descendants (see also below)
根据第三章的讨论, 层次化的继承结构可以有不同的应用方式.可以提炼不同类的公共协议形成一个抽象类. 这个协议仅仅提供相关操作签名(operation's signature),也就是那些操作可以被层次话的继承结构中的所有类来接收,而具体的执行环境则在不同的后裔类当中存在. 基于这种方式,不同的后裔类可以根据需要选择不同的实现方式(implementation).
Inheritance can, as previously mentioned, be used to reuse an implementation. This type of reuse can sometimes contradict the intuitive picture one has of inheritance. A typical example is how the relation between a stack and a double-ended queue (deque) should be shown. Which class should inherit which? If we regard inheritance as a way of expressing conceptual relations then neither of the solutions will be appropriate. A double-ended queue is not a stack, nor is a stack a double-ended queue. The reason for wishing to inherit in this situation is to reuse code. The implementation existing for one can be used for the other. As, in this case, it is only the implementation that is required, it is possible to declare, in a class stack, an instance variable to reference the deque. The operations push and pop can then use suitable operations on the local deque for its implementation (see Figure 5.9).
根据前面讨论到的,继承这种设计方式可以用来重用一个已知的实现. 这种重用方式可能会和我们对继承的直觉印象产生矛盾的感觉. 一个典型的案例就是一个堆栈和一个双向队类的关系应该如何描述,哪个类是父亲类,哪个类是后裔类? 假如我们认为继承是一种表达逻辑关系的方式,那么任何一种继承关系都是不正确的. 因为双向队列不是一个堆栈,而一个堆栈也不是一个双向队列. 在这种场合下,讨论双向队列和堆栈的继承关系的主要目的是为了重新使用代码其中在一个类当中存在的实现可以在另外一个类中使用. 因为,在这种场景下,仅仅是代码的实现需要被重新使用.这样一来,我们就有可能在一个堆栈类当中进行声明一个实例变量,利用这个实例变量来索引到双向队列这个类. 这样一来关于PUSH和POP的操作就可以应用来自本地的deque操作相关的代码实现. (see Figure 5.9). 具体来说deque是父亲类,而STACK是后裔类.
It seems wrong to use inheritance in the above example, as it contradicts what was discussed as proper use of inheritance in Chapter 3. As an alternative to inheritance for reusing code, composition can be used (see Taenzer et al. (1989)). This means that we can use classes when we develop new classes, not by inheriting them, but by creating instances of them and using these instances, as in the example above. Thus designing a class with the help of instances from other classes is another way to reuse code: when we implemented the stack, in the above example, we reused the deque by creating instances of it. It is thus possible to reuse code by using two totally different strategies, with inheritance or through composition, even though they have completely different characteristics. Both these methods are very powerful and should be used in programming. Lieberman (1986) discusses delegation of common behavior between objects as a complete alternative to inheritance.
结合上面关于堆栈和双向双向队列的案例,这种代码重用的方法和第三章中讨论的关于继承关系的正确使用存在矛盾冲突。除了继承以外,我们还可以应用组合composition关系来实现代码的重用。(see Taenzer et al. (1989)) 基于组合这种方法就意味着我们可以使用已经存在的类来创建新的类;上面案例中所应用的方法不是通过继承的方式,而是通过创建已知类实例对象,并直接使用这些实例对象的方式.这样我们可以利用他类所创建的对象实例来设计一个新的类, 这是面向对象的设计过程中另外一种复用代码的方法.在上面提到设计实现堆栈的案例, 我们通过创建并引用双向队列的实例的方法,直接重用了其中的代码实现. 由此看来 ,继承(inheritance)和组成(composition)都是面向对象的过程中可选择的重用代码的方法.,虽然这是两种完全不同的策略.上面两种方法都是非常有效的程序设计方法,并应该在程序设计的过程中广泛的使用. Lieberman (1986)还讨论了一种和继承完全不同的代码复用方法,具体的实现方式是通过提炼不同对象之间公共行为特征来简化程序设计.
Partition hierarchies, as discussed in Chapter 3, are very useful for showing how the system is constructed, and should be used together with an inheritance hierarchy. These hierarchies enable classes to be structured in a different way to the inheritance hierarchy. Partition hierarchies provide a hierarchy between classes, where class instances can delegate behavior to other instances.
在第三章中讨论的层次化分割('partition')结构,是一种非常有用的建模方法来描绘一个系统的组成结构,同时这种方法应该和层次化的继承方法联合应用.层次化分割结构允许不同的类按照一种和层次化继承完全不同的方式来组织. 层次化分割('partition')结构是另外一种多个类的层次化关系,在这种结构中,一个类的实例对象可以授权一些行为(delegate behavior)给其他的实例对象.
When should we use inheritance hierarchy and when should we use other hierarchies, such as partition hierarchy? The use of inheritance was discussed extensively in Chapter 3. We will here only highlight one simple rule for this decision. We can often view inheritance as 'is-A' and partition as 'has-A'. A dog is a mammal and a house has a door. This simple rule though, is not always sufficient. In languages which allow us to control which operations, including inherited operations, are accessible to individual users, such as C++ and Eiffel, we can specify which operations a user has access to. In C++, we do this by specifying them as public and, in Eiffel, by placing them in the export clause. However, in Smalltalk, a user has access to all inherited operations and it is not possible to specify the protocol used by an object, as this is controlled by the inheritance hierarchy. We can see that both strategies have advantages and disadvantages, and so the one selected will depend on a number of factors, some of which were discussed in Chapter 3. Efficiency and how the hierarchy is to be used are two other factors affecting this choice.
在面向对象的建模过程中,那些场合下我们可以使用层次化继承结构,而在那些场合下我们可以使用其他的层次结构,比如层次化分割('partition').我们在第三章中已经非常充分的讨论了,在这里我们仅仅讨论一个简单的原则来用于选择合理的层次化结构. 我们通常认为继承是“是一个”,而分割是”拥有一个”的关系.一只狗是一个动物,而一个房子有一扇门. 这个规则可能并不是非常充分. 在层次化的继承结构中,有些编程序语言允许我们来面向单个用户来控制每个操作的接入权限,包括继承的操作;比如在C++ 和Eiffel中,我们可以确定一个用户允许接入的对象操作范围. 在C++中,我们可以通过定义操作为public属性,而在Eiffel中我们可以定义为export属性. 然而,在Smalltalk 中,一个用户则允许对所有的继承操作进行接入和调用, 而不可能由对象来定义一些协议来完成基于操作的接入控制, 因为这种控制策略是通过继承结构本身加以控制. 我们可以发现这两种控制策略都有各自的优势和劣势, 必须基于一定的要素来进行策略的选择, 其中一些要素我们在第三章已经讨论过了. 应用的充分性和这种继承结构该如何使用是另外两个影响选择的要素.
The number of inheritance hierarchies in a system varies between different languages. In Smalltalk, there is only one inheritance hierarchy, and thus one root class. Behavior, which is common to all system classes, is collected and stored in a root class which in Smalltalk is called Object. All classes inherit Object, either directly or indirectly. Examples of behavior which may be common to instances of all classes are: the test for determining which class an instance is associated with; comparison between instances; copying of instances; and so on. As Object does not have any parent class, it is also used for error handling in Smalltalk.
在一个对象系统中可以构建的层次化继承关系的数量在不同的编程语言中支持的情况不尽相同. 在SMALL-TALK中仅仅支持一个层次化继承关系,这样一来就需要有一个根类. 面向系统内所有类的公共行为被提炼和收集集中放置在根类a root class中,这个根类在SMALL-TALK中被叫做对象Object.所有的类都是继承于根类,可能是直接继承,也可能是间接继承. 举个例子来说,测试一个实例是由哪个类所创建的,比较两个实例的区别,拷贝实例的数据等操作都是属于所有类都需要支持的公共行为.因为Object没有任何的父亲类,因此他也用来支持错误处理.
In Eiffel, C++ and Simula, several parallel inheritance hierarchies can exist. You can therefore create different hierarchies for different structures in the system. It is thus possible to create new classes without using the existing ones. Both approaches have their advantages and disadvantages. Smalltalk is often used for training in object-oriented programming, as it forces the programmer to work with the inheritance hierarchy. When a new class is to be created, the class has to be placed somewhere in the inheritance tree. In this way the programmer is forced to think according to the inheritance hierarchy and also to consider which class is to be used as a basis for the new class to be developed. Programmers who have previously worked in C and begin using C++ may have difficulties both in building inheritance hierarchies and in identifying abstract classes. Therefore C-programmers may initially be trained in Smalltalk and later continue in C++, so that they are forced to learn the use of an inheritance hierarchy and thus learn to use the inheritance mechanism in C++ to a greater extent.
在Eiffel, C++ and Simula中,多个并行的层次化继承结构可以存在. 所有程序设计者可以在系统中设计多个层次化继承结构来支持不同的模块组成结构. 基于这种方式, 就有可能不依赖现有的类来创建新的类.这两种方式都有自己的优势和劣势.Smalltalk通常被用作一种面向对象的入门训练语言,因为这种语言强迫程序设计者基于继承结构来编写程序. 假如需要创建一个新的类, 那么这个类必须首先放置在继承树的某一个位置. 这样一来,程序设计者必须被迫基于层次化继承结构进行思考,同时还必须考虑新设计类选择那一个类来作为继承的父亲类.而那些已经熟悉了C的编程人员在刚开始接触C++时候会感觉非常困难,尤其在设计层次话继承结构和确定抽象类的设计.所以C的编程设计人员首先会利用SMALL-TALK来训练面向对象的编写程思想, 因为这种方式会强迫他们使用层次化的继承结构;等他们后续学习C++的时候,就能够更加充分的应用C++的各种扩展继承机制.
Encapsulation and inheritance are both essential within object-oriented programming. Irrespective of this, though, they are somewhat incompatible with each other. Encapsulation means that the user of a class should not see its internal representation; but, if we regard a descendant of a class as a user of the class, then the user has complete access to the internal parts of the class. This contradiction is due to the fact that we have three types of user: those who use the class via its interface, those who use the class through inheritance, and those who actually implement the class. In C++, therefore, three possibilities for encapsulation of operations and data structures are used: 'public' means that all three user types can access the class operations, 'protected' means that only the class itself or descendants of the class can access the parts of the class, and 'private' means that only the class itself can access the parts. Of course, these three types are combined when a class is developed. The problem with inheritance and encapsulation has been discussed by, for example, Snyder (1986) and Meyer (1988).
封装和继承是面向对象的程序设计中的两种基本特性. 然而其实两种特性存在着一些不兼容的特性. 封装意味着类的使用者无法看到类的内部结构;但是假如我们把一个类的后裔类也当作这个类的使用者的话, 那么这个使用者就能够完全的接入父亲类的内部结构.产生这种矛盾的原因是在于我们有三类用户在从外部使用一个类,第一类用户是通过一个类提供的接口来接入; 第二类用户是通过继承关系来接入,第三类用户则是类的实现者.为了支持这三类操作,在C++这种编程序语言中,关于操作和数据结构的封装方式有三种可以选择的配置模式.其中'public'意味着所有的三类用户都可以接入类的内部结构和操作;而'protected'则意味着只有类自己和他的后裔类能够接入到类的内部结构部分;而'private'则意味着只有类自己能够接入到这个内部结构部分. 当然在设计一个类的时候,上述三种模式是混合使用的. 关于继承和封装的应用的冲突也在面向对象的社区中广泛的讨论,比如Snyder (1986) and Meyer (1988)