多态(Objective-C)
一、多态的概念
场景1:
老师:【操着四川口音】,童鞋门,今天,老师给你们讲讲神马是多态。
小明:啥?堕胎?【淫笑】。
老师:小明,你出去。
多态:在程序员的世界,多态就是相同的方法,不同的实现。
场景2:
老师:请同学们用“欣欣向荣”来造句。
小红:祖国的事业欣欣向荣。
小明:欣欣向荣荣表白,遭到拒绝。
老师:小明,你出去。
如果说用欣欣向荣造句是一条消息,小红和小明是两个对象,我们可以看到,不同的对象,响应相同的消息,得到的是完全不同的结果。这种不同对象响应相同消息的能力就是【多态】。
把上面的场景翻译成程序员语
定义一个Boy类
@interface Boy : NSObject
- (void)makeStatementsWithWord:(NSString*)word;
@end
@implementation Boy
- (void)makeStatementsWithWord:(NSString*)word {
NSLog(@"%@荣表白,遭到了拒绝",word);
}
@end
定义一个Girl类
@interface Girl : NSObject
- (void)makeStatementsWithWord:(NSString*)word;
@end
@implementation Girl
- (void)makeStatementsWithWord:(NSString*)word {
NSLog(@"祖国的建设事业 %@",word);
}
@end
两个类都带一个方法名为- (void)makeStatementsWithWord:(NSString*)word;的实例方法。这个方法的参数表示用来造句的词。
main函数如下
Boy* xiaoMing = [[Boy alloc] init];
[xiaoMing makeStatementsWithWord:@"欣欣向荣"];
Girl* xiaoHong = [[Girl alloc] init];
[xiaoHong makeStatementsWithWord:@"欣欣向荣"];
打印结果如下:
20XX-XX-XX 11:04:31.990 test[2751:393671] 欣欣向荣荣表白,遭到了拒绝
20XX-XX-XX 11:04:31.991 test[2751:393671] 祖国的建设事业 欣欣向荣
如果你已经能举出一些类似的例子,那说明你已经对多态有一个初步的认识了。
下面我要讲一个故事。通过这个故事,你会加深对多态的理解。
二、动态绑定和id类型
大欢和大鹏是我的两位得力助手,大欢是讲师,人称刀疤欢,他的“刀疤”是被树枝刮伤的。大鹏是程序员,人称傻鹏,显然我们不必再去讨论他的智商问题。
故事就发生在他们俩身上。
现在我需要他们俩去帮我给一位新来的女同事做一个面试,然后给我一个反馈结果。我给这个帮我做面试的人起了一个代号,叫simple(一个头脑简单的面试官)。显然我公司对待面试是很重视的。
然后,我先让大欢上场,所以这个时候simple就是大欢。
然后大欢经过2个小时的面试,给出我一个这样简单的反馈:
1.沟通能力很强
2.编码能力很强
3.项目经验丰富
4.薪资要求很低
非常适合我们公司
显然对于我这样一个严格要求能力的人,这样的答复是不够的。我还批评了大欢做事太粗糙。
然后我决定让大鹏再去帮我做一个面试。这个时候simple就是大鹏。大鹏经过5分钟以后也回来给了我一个反馈。
1.很漂亮
2.没有男朋友
最后经过慎重的考虑,我决定录用她了。
【拍砖】哎哎,课还没讲完呢你们都干嘛去啊?
好我们正经一点,如果simple能表示大欢又能表示大鹏,那么它到底是个什么类型呢?是讲师类型还是程序员类型? 显然用哪一种都不合适。所以,OC给我们提供一种更加宽泛的类型,即id类型。
id类型可以表示任意的对象类型。注意,一定是对象类型,非对象类型是不可以的。而且后面没有星号*。
例如:
Teacher* daPeng = [Teahcer new];
id simple = daPeng; // 正确
id value = 5; // 错误 不能表示非对象类型
id* value1 = daPeng; // 错误 id后面没有星号
我们把上面的例子,使用代码表示如下:
定义Teacher类
@interface Teacher : NSObject
- (void)haveInterview;
@end
@implementation Teacher
- (void)haveInterview {
NSLog(@"1.很漂亮");
NSLog(@"2.没有男朋友");
}
@end
定义Coder类
@interface Coder : NSObject
- (void)haveInterview;
@end
@implementation Coder
- (void)haveInterview {
NSLog(@"1.沟通能力很强");
NSLog(@"2.编码能力很强");
NSLog(@"3.项目经验丰富");
NSLog(@"4.薪资要求很低");
NSLog(@"非常适合我们公司");
}
@end
main函数如下:
// 大欢大鹏上场
Coder* daHuan = [Coder new];
Teacher* daPeng = [Teacher new];
// 大欢成为面试官simple
id simple = daHuan;
[simple haveInterview];
// 大鹏成为面试官simple
simple = daPeng;
[simple haveInterview];
打印结果:
1.沟通能力很强
2.编码能力很强
3.项目经验丰富
4.薪资要求很低
非常适合我们公司
1.很漂亮
2.没有男朋友
细心的同学可能已经注意到,我们两次向simple发送haveInterview消息,系统是如何区分当前应该执行哪一种haveInterview消息呢?
动态绑定:事实上,OC的系统总是跟踪当前对象所属的类,然后在运行时确定需要调用的方法,而不是在编译时。
因此,当simple接收到haeInterview消息时,系统首先去追踪当前simple表示的是属于哪个类的对象,进而确定当前该执行哪个类的方法。
所以便有了当大欢作为simple得到的是
1.沟通能力很强
2.编码能力很强
3.项目经验丰富
4.薪资要求很低
非常适合我们公司
而当大鹏作为simple的时候,得到的是
1.很漂亮
2.没有男朋友
三、编译时和运行时检查
在使用id类型表示的对象类型,在编译时是并没有确定该对象类型的,事实上这个时候也无法确定类型。就像来参加面试那个妹子,她并不认识大欢和大鹏,她只知道与他对话的是一个面试官(前面我们使用simple代号来表示)。直到开始面试时,才确定当前面试官simple到底是大鹏还是大欢。
也就是说,id表示的对象类型是在程序运行时检查出来的。
例如大欢喜欢在讨论技术的时候,会喜欢先给你一个不屑的眼神然后在给你耐心讲解这个知识点更深入的一层含义,我们把他的这种行为称为zhuangBiTeach,大鹏喜欢在课下给他的学生解决一些技术难题,我们把他的这种行为称为debugTeach。
这样我们需要把上面的两个类做如下修改:
1.教师类
@interface Teacher : NSObject
- (void)haveInterview;
- (void)debugTeach;
@end
@implementation Teacher
- (void)haveInterview {
NSLog(@"1.很漂亮");
NSLog(@"2.没有男朋友");
}
- (void)debugTeach {
NSLog(@"大鹏帮你定位了bug并分析了原因");
NSLog(@"然后梳理了一下思路,分分钟完美解决了bug");
}
@end
2.程序员类
@interface Coder : NSObject
- (void)haveInterview;
- (void)zhuangbiTeach;
@end
@implementation Coder
- (void)haveInterview {
NSLog(@"1.沟通能力很强");
NSLog(@"2.编码能力很强");
NSLog(@"3.项目经验丰富");
NSLog(@"4.薪资要求很低");
NSLog(@"非常适合我们公司");
}
- (void)zhuangbiTeach {
NSLog(@"大欢向你抛出了一个白眼");
NSLog(@"哎哎,你们聊的这个层面太浅,让哥来给你迷途指引一下方向");
}
@end
在主函数中,做如下处理
// 大鹏上场
Teacher* daPeng = [Teacher new];
// 大鹏成为面试官simple
id simple = daPeng;
// 此处编译时没错,运行时报错
[simple zhuangbiTeach];
如上,[simple zhuangbiTeach]; 这行代码会报错。报错结果如下
-[Teacher zhuangbiTeach]: unrecognized selector sent to instance 0x1001057a0
也就是说Teacher类实例并不能响应zhuangbiTeach方法。
在程序编译阶段,因为系统无法确定simple表示的对象类型,系统只能确定zhuangbiTeach是一个有效的方法。所以没有报错。而zhuangbiTeach是大欢的技能,声明在Coder类中,当程序运行时,追踪simple所表示的对象是一个Teacher对象,而Teacher类并没有实现zhuangbiTeach方法,所以程序在运行时报错了。
id数据类型与静态类型
初学的小伙伴经常会问我,智哥,既然id类型可以表示任意类型的对象,那以后我就都用id类型来表示对象啦。当然,新手总是对新技术充满好奇,这个我非常理解。所以这个时候,我总会耐心的做如下解释,你咋不上天呢?
我不得不给大家讲一点哲学。需知万事皆有度,物极必反啊。没有什么是绝对的好,也没有什么是绝对的不好。好与不好是要分场景的。一个妹子跑到你面前说你好帅,然后把从小朋友手里抢的棒棒糖送给你,你能说她不好吗?
为了理解为什么不能一直用id类型,我们先理解两种类型,静态类型和动态类型。静态类型就是我们使用类名字来声明的对象指针,动态类型就是id类型。比如我们定义大鹏可以使用如下两种方式来表示他
:
1. id daPeng = [[Teacher alloc] init]; // 动态类型
2. Teacher* daPeng = [[Teacher alloc] init]; // 静态类型
我想我已经表述清楚什么是动态类型什么是静态类型了。
使用静态类型时,编译器可以尽可能的确保变量的用法在程序中始终保持一致。它可以在编译阶段检查来确定这个对象要调用的方法是否可以成功响应。否则将直接报错。也就是说,使用静态类型,可以更好的在编译阶段而不是运行阶段查错。
另外id daPeng 和 Teacher* daPeng两种方式中,显然使用静态类型的可读性更高。所以,对于程序的自说明性也是有非常重大的意义的。
注意:由于id不是一个具体的类型,系统在编译阶段无法确定,所以id也不能使用点运算符访问属性。
动态类型的使用
动态类型的使用,得益于多态是被支持的。一般体现在多态类型作参数和做返回值两个方面。
再来一个小故事。大欢一直为自己的发型感到骄傲。但最近他想换一个新发型。所以他决定去找一个给力的发型师帮他设计一款新发型。故事就此展开。
先设计一个发型师Designer类
@interface Designer : NSObject
- (void)showHairStyle;
@end
@implementation Designer
- (void)showHairStyle {
NSLog(@"不是每一位发型师都会设计发型");
}
@end
再设计一个大欢所属的Coder类,大欢需要有一个方法,就是寻找一位发型师,由于发型师可以是很多不同风格的发型师,所以使用id类型来表示不同风格的发型师
@interface Coder : NSObject
- (void)designHairStyle:(id)designer;
@end
// 为保证showHairStyle是可以被系统识别的,所以导入Designer.h
#import "Designer.h"
@implementation Coder
- (void)designHairStyle:(id)designer {
[designer showHairStyle];
}
@end
Coder类,有一个designHairStyle方法,参数是一个id类型。表示需要一个设计师来设计发型。
那么,只要有一个对象,它继承自Designer类,就可以成为设计师,来帮助大欢设计发型。
所以,我们设计两个Designer的子类
1.男发型师
#import "Designer.h"
@interface ManDesigner : Designer
@end
@implementation ManDesigner
- (void)showHairStyle {
NSLog(@"一款光滑的光头发型");
}
@end
2.女发型师
#import "Designer.h"
@interface WomanDesigner : Designer
@end
@implementation WomanDesigner
- (void)showHairStyle {
NSLog(@"一款飘逸的长发及腰");
}
@end
main函数如下:
先导入两个头文件
#import "ManDesigner.h"
#import "WomanDesigner.h"
代码如下:
Coder* daHuan = [[Coder alloc] init];
ManDesigner* tom = [[ManDesigner alloc] init];
WomanDesigner* lucy = [[WomanDesigner alloc] init];
[daHuan designHairStyle:tom];
[daHuan designHairStyle:lucy];
打印结果:
一款光滑的光头发型
一款飘逸的长发及腰
如果大欢对设计的发型不满意,可以再找一位发型师,只要这位发型师也实现了showHairStyle方法即可。
所以,多态的存在,让我们的程序可以设计的更加灵活。然而,在使用动态类型的时候,也会存在很多潜在风险。比如,我们使用id类型来表示一个设计师,如果这个设计师并不会设计发型(没有实现showHairStyle方法),当大欢调designHairStyle方法时,用程序就会报错。所以下节课我们来学习,如何规避这些风险。
如有疏漏,敬请指正,下次再见!
网友评论