11-多态性、动态类型和动态绑定

最后更新于:2022-04-01 00:17:18

多态这个其它语言也有。动态类型有类似的,但不完全相同。 动态绑定别的语言也有类似,但没有objective-c用的这么多。 多态能够使来自不同类的对象定义相同名称的方法。 动态类型能使程序直到执行时才确定对象所属的类。 动态绑定则能使程序直到执行时才确定实际要调用的对象方法。 多态性是指在父类中定义的成员变量和方法被子类继承之后, 可以具有不同的数据类型或表现出不同的行为。  这使得同一个变量和方法在父类及其各个子类中具有不同的表现形式。  简单点说:相同的名称,不同的类,系统总是携带有关“一个对象属于哪个类”这样的信息。 该信息能使系统在运行时做出这些关键性的决定,而不是在编译时。 这种使不同的类共享相同方法名称的能力就称为多态。 我们通过一个例子理解什么是多态: 例如: “几何图形” 类的“绘图” 方法, 在它的子类“椭圆形” 和“三角形” 中也都有“绘图” 的方法, 但是“绘图” 方法功能都不同。 几何图形类图 Graphics(几何图形) 类是Ellipse(椭圆形) 类和Triangle(三角形) 类的父类,  Ellipse和Triangle重写了onDraw方法。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-31_5684a3f0a265c.jpg) Graphics.h文件 Graphics类在h文件中定义onDraw, Graphics类应该无法实现这 个onDraw, 这是因为在几何图形中是无法知道要绘制的是椭 圆形和三角形, 这个方法应该是类似Java中的抽象方法, 或者是 C++中的虚方法。 @interface Graphics : NSObject { } -(void) onDraw; @end Ellipse类的h和m文件 ~~~ #import    #import "Graphics.h"      @interface Ellipse : Graphics {      }      @end   ~~~ ~~~ #import "Ellipse.h"         @implementation Ellipse      -(void)onDraw {       NSLog(@"绘制椭圆形");   }      @end   ~~~ Triangle类的h和m文件 ~~~ #import    #import "Graphics.h"      @interface Triangle : Graphics {      }      @end   ~~~ ~~~ #import "Triangle.h"         @implementation Triangle      -(void)onDraw {       NSLog(@"绘制三角形");   }      @end   ~~~ #### 调用的main函数 ~~~ #import    #import "Graphics.h"   #import "Ellipse.h"   #import "Triangle.h"      int main (int argc, const charchar * argv[]) {              Graphics *graphics;       graphics = [[Ellipse alloc] init];       [graphics onDraw];       [graphics release];              graphics = [[Triangle alloc] init];       [graphics onDraw];       [graphics release];              return 0;   }   ~~~ 运行结果: 绘制椭圆形 绘制三角形 动态类型和动态绑定 id 是泛类型 (generic data type), 可以用来存放各种类型的对象,  使用 id 也就是使用“动态类型”。  上面的例子改写下: ~~~ int main (int argc, const char * argv[]) { id graphics; graphics = [[Ellipse alloc] init]; [graphics onDraw]; [graphics release]; graphics = [[Triangle alloc] init]; [graphics onDraw]; [graphics release]; return 0; } ~~~ 把Graphics *改成id类型, 程序运行的结果没有任何影响。 由于动态类型的关系,id 在执行时,  Objective-C 的执行环境会找出该 id 所代表的原来类型,  所以根本没有所谓的转型。  id 并不是自动的转换成 Ellipse和 Triangle的父类,而是在执行期间,  由执行环境辨认出 id 实际代表的类型为Ellipse还是Triangle。  因此在这个例子中id 与Graphics没有任何关系。 务必注意,声明中并没有使用星号。 我们再举一个例子来说明动态的概念: 矢量和标量是数学和物理学应用中经常用到的两个概念; 矢量” 即有方向和大小的量, 如物理学中的“力” 。  标量为没有方向只有大小量, 如物理学中的“功”。 下面从程序的角度来实现这两个概念。 先定义两个类: “矢量” 和“标量” 类。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-31_5684a3f0b7142.jpg) Vector.h文件 ~~~ #import          @interface Vector : NSObject {       double vec1;       double vec2;   }      @property double vec1,vec2;   -(void)print;   -(void)setVec1:(double)v1 andVec2:(double) v2;   -(Vector *)add:(Vector *)v;      @end   ~~~ Vector.m文件 ~~~ #import "Vector.h"         @implementation Vector   @synthesize vec1,vec2;   -(void) setVec1:(double) v1 andVec2:(double)v2 {       vec1 = v1;       vec2 = v2;   }   -(Vector *)add:(Vector *)v {       Vector *result = [[Vector alloc] init];       [result setVec1:vec1 + [v vec1] andVec2: vec2 + [v vec2]];       return result;   }      -(void)print {       NSLog(@"%g,  %g",vec1,vec2);   }      @end   ~~~ Scalar.h文件 ~~~ #import          @interface Scalar : NSObject {       double scal;   }   @property double scal;   -(void)print;   -(void)setScal:(double)sval;   -(Scalar *)add:(Scalar *)s;      @end   ~~~ Scalar.m文件 ~~~ #import "Scalar.h"         @implementation Scalar      @synthesize scal;      -(void)print {       NSLog(@"%g", scal);   }      -(void)setScal:(double)sval {       scal = sval;   }      -(Scalar *)add:(Scalar *)s {       Scalar *result  = [[Scalar alloc] init];       [result setScal:scal + [s scal]];       return result;   }   ~~~ 调用的main函数 ~~~ #import       #import "Vector.h"   #import "Scalar.h"      int main (int argc, const charchar * argv[]) {              Scalar *scA =[[Scalar alloc] init];       Scalar *scB =[[Scalar alloc] init];          Vector *vecA =[[Vector alloc] init];           Vector *vecB =[[Vector alloc] init];              id scAandB;       id vecAandB;          [scA setScal: 10.5];       [scB setScal: 13.1];       [vecA setVec1: 3.2 andVec2: 4.7];       [vecB setVec1: 32.2 andVec2: 47.7];          [vecA print];       NSLog(@" + ");       [vecB print];       NSLog(@" = ");       vecAandB = [vecA add: vecB];       [vecAandB print];       [scA print];       NSLog(@" + ");       [scB print];       NSLog(@" = ");       scAandB = [scA add: scB];       [scAandB print];              [scA release];       [scB release];       [scAandB release];       [vecA release];       [vecB release];       [vecAandB release];              return  0;   }   ~~~ 运行结果: ~~~ 3.2, 4.7 + 32.2, 47.7 = 35.4, 52.4 10.5 + 13.1 = 23.6 ~~~ #### 代码说明: scAandB和vecAandB对象都是动态类型, 都可调用以"print"和"add"方法。     注意: 虽然id类型可以任何类型的对象,但是不要滥用 , 如果能够确定对象数据类型时候, 要使用“静态类型” ,  为什么要使用静态类型: 1)将一个变量定义为特定类的对象时,使用的是静态类型。 “静态”指的是对存储在变量中对象的类型进行显示声明。 这样存储在这种形态中的对象的类是预定义的,也就是静态的。 使用静态类型时,编译尽可能确保变量的用法在程序中始终保持一致。 编译器能够通过检查来确定应用于对象的方法是由该类定义的还是由该类继承的,否则它将显示警告信息。 也就是说“静态类型” 在编译阶段检查错误, 而不是在执行阶段。  2)使用静态类型的另一个原因是程序可读性好。。 动态类型的参数和返回类型: 如何使用动态类型来调用一个方法,需要注意如下规则: 如果在多个类中实现名称相同的方法,那么每个方法都必须符合各个参数的类型和返回值类型。 这样编译器才能为消息表达式生成正确的代码。编译器会对它所遇到的每个类声明执行一致性检查。 当一个方法选取对象作为它的参数,而另一个方法选取浮点数作为参数时, 或者一个方法以对象作为返回值,而另一个以整型数作为返回值。 编译器可能生成不正确的代码来向方法传递参数或处理返回值。 处理动态类型的方法: ~~~ -(BOOL) isKindOf:class-object(判断对象是否是class-object或其子类的成员)  -(BOOL) isMenberOfClass:class-object(判断对象是否是class-object的成员)  -(BOOL) respondsToSelector:selector(判断对象是否能够响应selector所指定的方法)  +(BOOL) instancesRespondToSelector:selector(判断指定的类实例是否能响应selector所指定的方法)  +(BOOL) isSubclassOfClass:class-object(判断对象是否是指定类的子类)  -(id) performSelector:selector(应用selector指定的方法)  -(id) performSelector:selector withObject:object(应用selector指定的方法,传递参数object)   -(id) performSelector:selector withObject:object1 withObject:object2(应用selector指定的方法,传递参数object1和object2 ~~~ 可以对一个方法名应用@selector指令。 例如:@selector (alloc)为名为alloc的方法生成一个SEL类型的值,该方法是从NSObject类继承的。 记住,测试包含继承的方法,并不是只测试直接定义在类中的方法。 performSelector:方法和它的变体允许你向对象发送消息,这个消息可以是存储在变量中的selector。 在iOS中,respondsToSelector:方法广泛用于实现委托(delegation)的概念。 为了让系统能够检查你确实实现了特定的方法, 使用respondsToSelector:判断是否可以将事件的处理委托给你的方法。 如果你没有实现这个方法,它会自己处理该事件,按定义的默认行为来执行。 #### 使用@try处理异常 @try:如果块中的某一语句抛出异常,执行不会终止,而是立即跳到@catch块继续执行 @catch:处理异常,可行的执行顺序是记录出错信息,清除和终止执行。 @finally:使用@finally块包含是否执行抛出异常的@try块中的语句代码; @throw:允许你抛出自己的异常, 这些概念和java的或者其它语言差不多。 一般来说,需要考虑更好的编程实践, 应该在错误发生前做系统的全面的覆盖性测试,而不是在错误发生后捕获异常。 抛出异常会使用大量的系统资源,Apple反对非必要的使用异常。
';