面向对象编程——继承与多态
周立功教授数年之心血之作《程序设计与数据结构》以及《面向AMetal框架与接口的编程(上)》,电子版已无偿性分享到电子工程师与高校群体,书本内容公开后,在电子行业掀起一片学习热潮。经周立功教授授权,本公众号特对《程序设计与数据结构》一书内容进行连载,愿共勉之。
第四章为面向对象编程,本文为4.3 继承与多态。
>>> 4.3.1 抽象
假设需要设计一个处理工资单的数据包,可以将排序作为一个关键的业务进行抽象。虽然各种排序的实现不一样,但它们的共性都是"排序",这就是抽象的基础。如果要建立一个矩阵代数程序包,就要讨论抽象矩阵。虽然各种类型矩阵的实现各不相同,但根据它们表现的共同行为特性,可以将这些矩阵归为一类,显然其共性又一次支持了抽象。
如果用户有一个这样的需求——校验push到栈中的数据,则实现者一定会问"校验规则是什么?"因为校验是一个非常"抽象"的概念;如果用户明确地告诉实现者——对push到栈中的数据进行范围值校验或偶校验,则不会出现这样模糊的问题。当需要对push到栈中的数据进行范围值校验时,则需要编写一个RangeValidator类;当再需要添加一个奇偶校验器时,势必又要编写一个OddEvenValidator类。显然每添加一种校验器就要增加一个接口,根本无法做到重用。
虽然它们的类型不同,且不同校验器的对象各有不同,但它们共同的概念都是"校验器"。回归校验器的本质,无论是什么校验器,其共同的属性是校验参数,其共同的行为是可以使用相同的方法——在动态中根据对象的类型调用不同的校验器函数。
显然,用户是在概念层次上提出了校验的需求与实现者交流,而具体如何校验是在实现层次进行的,用户无需准确地知道具体是如何实现的。因此只要概念不变,即可做到用户与实现细节的变化完全分离。
在面向过程编程中,新手对共性的认识往往来源于直觉,以创建范围值校验器类和偶校验器类为例,程序员普遍都会按照以下方法表达这种共性,将Validate提取为一个公共的函数指针。比如:
而对于一个拥有"面向对象思维"且经验丰富的程序员,更倾向于将各种校验器的共性打包在一个函数指针中作为结构体的成员创建一个抽象类。Validator抽象类的定义如下:
其中,pThis是指向当前对象的指针,Validator是一个没有具体属性,代表多种具有共性的数据和行为的具体校验器总称的抽象类。Validator类没有提供任何实现validate方法的代码,正是因为这一点,该方法才能成为一个抽象的方法,因为提供任何代码都会使方法成为具体方法。
由于Validator是一个抽象类,因此无法创建实例,自然也就不知道要校验什么?那么谁知道呢?范围值校验器和奇偶校验器类知道自己要做什么校验。由于Validator有一个validate方法,因此可以将Validator抽象类封装成RangeValidator派生类的成员——Validator类的变量isa,即将实现细节委托给子类。在范围值校验器和奇偶校验器类重新定义,各自实现它自己的validate方法。
>>> 4.3.2 继承
在这里将引入一个新的概念继承描述类之间的关系。由于RangeValidator范围值校验器和OddEvenValidator奇偶校验器的共性是校验参数和调用校验函数的方法,因此将其共性上移到一个名为Validator校验器类(父类)中。
基于此,在将具有可变性的校验参数分别转移到RangeValidator和OddEvenValidator中的同时,并将Validator类型的变量isa作为结构体的成员,即可创建新的结构体数据类型:
其中,pThis为指向Validator类对象的指针,RangeValidator和OddEvenValidator派生自Validator类,RangeValidator和OddEvenValidator是Validator的子类,Validator类是RangeValidator和OddEvenValidatorr类的基类或超类。因为RangeValidator是一种校验器,OddEvenvalidator也是一种校验器。当一个子类继承自一个基类时,它可以做基类能做的任何事情,因此RangeValidator和OddEvenValidator都是Validator的扩充。
虽然父类和子类的类型不一样,当通过继承将不同类的共同属性和行为抽象为一个公共的基类后,于是它们就具有了共同的属性和行为,这就是OOP通过继承实现代码重用的方法。因为抽象类在概念上定义了相似的一组类的共同属性和方法,因而能够将这一组相关类看成一个概念。也就是说,抽象类代表了将所有派生类联系起来的核心概念,也正是这个核心概念定义了派生类的共性。同时还提供了与这一组相关类的通信接口规约,然后每个具体类