多態(tài)(Objective-C)
一刊懈、多態(tài)的概念
場景1:
老師:【操著四川口音】,童鞋門娃闲,今天虚汛,老師給你們講講神馬是多態(tài)。
小明:啥皇帮?墮胎卷哩?【淫笑】。
老師:小明属拾,你出去将谊。
多態(tài):在程序員的世界,多態(tài)就是相同的方法渐白,不同的實現(xiàn)尊浓。
場景2:
老師:請同學們用“欣欣向榮”來造句。
小紅:祖國的事業(yè)欣欣向榮纯衍。
小明:欣欣向榮榮表白栋齿,遭到拒絕。
老師:小明襟诸,你出去褒颈。
如果說用欣欣向榮造句是一條消息,小紅和小明是兩個對象励堡,我們可以看到谷丸,不同的對象,響應相同的消息应结,得到的是完全不同的結果刨疼。這種不同對象響應相同消息的能力就是【多態(tài)】。
把上面的場景翻譯成程序員語
定義一個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(@"祖國的建設事業(yè) %@",word);
}
@end
兩個類都帶一個方法名為- (void)makeStatementsWithWord:(NSString*)word;的實例方法揩慕。這個方法的參數(shù)表示用來造句的詞。
main函數(shù)如下
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] 祖國的建設事業(yè) 欣欣向榮
如果你已經(jīng)能舉出一些類似的例子迎卤,那說明你已經(jīng)對多態(tài)有一個初步的認識了。
下面我要講一個故事玷坠。通過這個故事蜗搔,你會加深對多態(tài)的理解劲藐。
二、動態(tài)綁定和id類型
大歡和大鵬是我的兩位得力助手樟凄,大歡是講師聘芜,人稱刀疤歡,他的“刀疤”是被樹枝刮傷的缝龄。大鵬是程序員汰现,人稱傻鵬,顯然我們不必再去討論他的智商問題叔壤。
故事就發(fā)生在他們倆身上瞎饲。
現(xiàn)在我需要他們倆去幫我給一位新來的女同事做一個面試,然后給我一個反饋結果炼绘。我給這個幫我做面試的人起了一個代號企软,叫simple(一個頭腦簡單的面試官)。顯然我公司對待面試是很重視的饭望。
然后,我先讓大歡上場形庭,所以這個時候simple就是大歡铅辞。
然后大歡經(jīng)過2個小時的面試,給出我一個這樣簡單的反饋:
1.溝通能力很強
2.編碼能力很強
3.項目經(jīng)驗豐富
4.薪資要求很低
非常適合我們公司
顯然對于我這樣一個嚴格要求能力的人萨醒,這樣的答復是不夠的斟珊。我還批評了大歡做事太粗糙。
然后我決定讓大鵬再去幫我做一個面試富纸。這個時候simple就是大鵬囤踩。大鵬經(jīng)過5分鐘以后也回來給了我一個反饋。
1.很漂亮
2.沒有男朋友
最后經(jīng)過慎重的考慮晓褪,我決定錄用她了堵漱。
【拍磚】哎哎,課還沒講完呢你們都干嘛去盎练隆勤庐?
好我們正經(jīng)一點,如果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.項目經(jīng)驗豐富");
NSLog(@"4.薪資要求很低");
NSLog(@"非常適合我們公司");
}
@end
main函數(shù)如下:
// 大歡大鵬上場
Coder* daHuan = [Coder new];
Teacher* daPeng = [Teacher new];
// 大歡成為面試官simple
id simple = daHuan;
[simple haveInterview];
// 大鵬成為面試官simple
simple = daPeng;
[simple haveInterview];
打印結果:
1.溝通能力很強
2.編碼能力很強
3.項目經(jīng)驗豐富
4.薪資要求很低
非常適合我們公司
1.很漂亮
2.沒有男朋友
細心的同學可能已經(jīng)注意到精偿,我們兩次向simple發(fā)送haveInterview消息弧圆,系統(tǒng)是如何區(qū)分當前應該執(zhí)行哪一種haveInterview消息呢?
動態(tài)綁定:事實上笔咽,OC的系統(tǒng)總是跟蹤當前對象所屬的類搔预,然后在運行時確定需要調(diào)用的方法,而不是在編譯時叶组。
因此拯田,當simple接收到haeInterview消息時,系統(tǒng)首先去追蹤當前simple表示的是屬于哪個類的對象甩十,進而確定當前該執(zhí)行哪個類的方法船庇。
所以便有了當大歡作為simple得到的是
1.溝通能力很強
2.編碼能力很強
3.項目經(jīng)驗豐富
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.項目經(jīng)驗豐富");
NSLog(@"4.薪資要求很低");
NSLog(@"非常適合我們公司");
}
- (void)zhuangbiTeach {
NSLog(@"大歡向你拋出了一個白眼");
NSLog(@"哎哎,你們聊的這個層面太淺挟裂,讓哥來給你迷途指引一下方向");
}
@end
在主函數(shù)中享钞,做如下處理
// 大鵬上場
Teacher* daPeng = [Teacher new];
// 大鵬成為面試官simple
id simple = daPeng;
// 此處編譯時沒錯,運行時報錯
[simple zhuangbiTeach];
如上,[simple zhuangbiTeach]; 這行代碼會報錯栗竖。報錯結果如下
-[Teacher zhuangbiTeach]: unrecognized selector sent to instance 0x1001057a0
也就是說Teacher類實例并不能響應zhuangbiTeach方法暑脆。
在程序編譯階段,因為系統(tǒng)無法確定simple表示的對象類型狐肢,系統(tǒng)只能確定zhuangbiTeach是一個有效的方法添吗。所以沒有報錯。而zhuangbiTeach是大歡的技能份名,聲明在Coder類中碟联,當程序運行時,追蹤simple所表示的對象是一個Teacher對象僵腺,而Teacher類并沒有實現(xiàn)zhuangbiTeach方法鲤孵,所以程序在運行時報錯了。
id數(shù)據(jù)類型與靜態(tài)類型
初學的小伙伴經(jīng)常會問我辰如,智哥普监,既然id類型可以表示任意類型的對象,那以后我就都用id類型來表示對象啦琉兜。當然凯正,新手總是對新技術充滿好奇,這個我非常理解豌蟋。所以這個時候廊散,我總會耐心的做如下解釋,你咋不上天呢夺饲?
我不得不給大家講一點哲學。需知萬事皆有度施符,物極必反啊往声。沒有什么是絕對的好,也沒有什么是絕對的不好戳吝。好與不好是要分場景的浩销。一個妹子跑到你面前說你好帥,然后把從小朋友手里搶的棒棒糖送給你听哭,你能說她不好嗎慢洋?
為了理解為什么不能一直用id類型,我們先理解兩種類型陆盘,靜態(tài)類型和動態(tài)類型普筹。靜態(tài)類型就是我們使用類名字來聲明的對象指針,動態(tài)類型就是id類型隘马。比如我們定義大鵬可以使用如下兩種方式來表示他
:
1. id daPeng = [[Teacher alloc] init]; // 動態(tài)類型
2. Teacher* daPeng = [[Teacher alloc] init]; // 靜態(tài)類型
我想我已經(jīng)表述清楚什么是動態(tài)類型什么是靜態(tài)類型了太防。
使用靜態(tài)類型時,編譯器可以盡可能的確保變量的用法在程序中始終保持一致酸员。它可以在編譯階段檢查來確定這個對象要調(diào)用的方法是否可以成功響應蜒车。否則將直接報錯讳嘱。也就是說,使用靜態(tài)類型酿愧,可以更好的在編譯階段而不是運行階段查錯沥潭。
另外id daPeng 和 Teacher* daPeng兩種方式中,顯然使用靜態(tài)類型的可讀性更高嬉挡。所以钝鸽,對于程序的自說明性也是有非常重大的意義的。
注意:由于id不是一個具體的類型棘伴,系統(tǒng)在編譯階段無法確定寞埠,所以id也不能使用點運算符訪問屬性。
動態(tài)類型的使用
動態(tài)類型的使用焊夸,得益于多態(tài)是被支持的仁连。一般體現(xiàn)在多態(tài)類型作參數(shù)和做返回值兩個方面。
再來一個小故事阱穗。大歡一直為自己的發(fā)型感到驕傲饭冬。但最近他想換一個新發(fā)型。所以他決定去找一個給力的發(fā)型師幫他設計一款新發(fā)型揪阶。故事就此展開昌抠。
先設計一個發(fā)型師Designer類
@interface Designer : NSObject
- (void)showHairStyle;
@end
@implementation Designer
- (void)showHairStyle {
NSLog(@"不是每一位發(fā)型師都會設計發(fā)型");
}
@end
再設計一個大歡所屬的Coder類,大歡需要有一個方法鲁僚,就是尋找一位發(fā)型師炊苫,由于發(fā)型師可以是很多不同風格的發(fā)型師,所以使用id類型來表示不同風格的發(fā)型師
@interface Coder : NSObject
- (void)designHairStyle:(id)designer;
@end
// 為保證showHairStyle是可以被系統(tǒng)識別的冰沙,所以導入Designer.h
#import "Designer.h"
@implementation Coder
- (void)designHairStyle:(id)designer {
[designer showHairStyle];
}
@end
Coder類侨艾,有一個designHairStyle方法,參數(shù)是一個id類型拓挥。表示需要一個設計師來設計發(fā)型唠梨。
那么,只要有一個對象侥啤,它繼承自Designer類当叭,就可以成為設計師,來幫助大歡設計發(fā)型盖灸。
所以蚁鳖,我們設計兩個Designer的子類
1.男發(fā)型師
#import "Designer.h"
@interface ManDesigner : Designer
@end
@implementation ManDesigner
- (void)showHairStyle {
NSLog(@"一款光滑的光頭發(fā)型");
}
@end
2.女發(fā)型師
#import "Designer.h"
@interface WomanDesigner : Designer
@end
@implementation WomanDesigner
- (void)showHairStyle {
NSLog(@"一款飄逸的長發(fā)及腰");
}
@end
main函數(shù)如下:
先導入兩個頭文件
#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];
打印結果:
一款光滑的光頭發(fā)型
一款飄逸的長發(fā)及腰
如果大歡對設計的發(fā)型不滿意,可以再找一位發(fā)型師赁炎,只要這位發(fā)型師也實現(xiàn)了showHairStyle方法即可才睹。
所以,多態(tài)的存在,讓我們的程序可以設計的更加靈活琅攘。然而垮庐,在使用動態(tài)類型的時候,也會存在很多潛在風險坞琴。比如哨查,我們使用id類型來表示一個設計師,如果這個設計師并不會設計發(fā)型(沒有實現(xiàn)showHairStyle方法)剧辐,當大歡調(diào)designHairStyle方法時寒亥,用程序就會報錯。所以下節(jié)課我們來學習荧关,如何規(guī)避這些風險溉奕。
如有疏漏,敬請指正忍啤,下次再見加勤!