iOS開發入門 ☞ OC語言·筆記六,iosoc
物件導向的三大特性: 封裝, 繼承, 多態1. 封裝
1.1 基本概念
將零散的東西組合起來。
- 廣義上封裝指:將代碼封裝成函數,將執行個體變數和方法封裝成類,將類封裝成架構....
- 物件導向中的封裝指:封裝屬性和方法放在一個對象中,只給外界公開訪問的介面,而且把具體實現隱藏起來。
1.2 封裝的好處
提高可讀性,可維護性,可擴充性
1.3 OC中的封裝
OC語言天然就是封裝好的。
定義一個類時,@interface部分就是給外界公開的提供者。@implementation部分就是隱藏起來的具體實現。
.h檔案中寫的是公開的介面
.m檔案中寫的是隱藏的實現
//私人方法,只要不在標頭檔的介面部分聲明的方法就是私人方法
2. 繼承
2.1 概念
繼承是一種代碼複用技術(代碼重複使用)。是類與類之間的一種關係(“is a”關係)。
B類繼承了A類。A類叫B類的父類,B類叫A類的子類。
其他語言中還有基類,衍生類別的概念
2.2 繼承的方式
單繼承 OC, Java...., Swift 單繼承指一個類只能有一個父類.
多繼承 C++支援多繼承 多繼承指一個類可以有多個父類.
OC語言中的類在一顆樹上,只有一個祖宗NSObject; swift不只有一顆樹,是一片森林。
2.3 OC中繼承的文法
@interface 類名 : 父類名
@end
2.4 什麼情況下用繼承
理論上:
如果兩個類之間擁有is a關係,這兩個類應該是繼承關係。
狗是動物 Dog is a Animal.
Animal是父類, Dog是子類
如果兩個類之間擁有has a關係,應該用組合或彙總
計算中有一個CPU Computer has a CPU
組合和彙總是另一種類與類之間的關係
實際開發中使用繼承:
先寫的父類,還是先寫的子類?//都可以
2.5 抽象類別
C++: 純虛函數,沒有函數體的函數。存在純虛函數的類是抽象類別,不可以執行個體化了對象。
Java: 抽象方法和抽象類別, abstract來聲明
OC: OC語言中沒有抽象類別和抽象方法的文法。
派生:在子類中添加新的屬性和方法
2.6 重寫:子類對父類的方法不滿意時,可重寫父類中的方法
隱藏:當子類重寫父類的方法後,子類中將有兩個同名的方法,而從父類中繼承的方法不能在類外被調用
2.6.1 概念
1) override 重寫(覆蓋): 子類不滿意從父類中繼承來的方法,重新將此方法實現了。
要求:方法名和父類一樣,參數類型一樣,傳回值一樣。只有方法的實現不一樣。
2) overload 重載: OC中並不存在overload。
overload的概念是在一個範圍內(比如一個類中),出現多個方法名相同的方法,這些方法的參數類型不同,導致可以同時出現,我們說,這些方法之間形成了重載的關係。
OC中不允許同一個範圍存在多個方法名相同的方法。
-(id)initWithName:(NSString *)name andAge:(NSUInteger)age;
-(id)initWithName:(NSString *)name andGender:(BOOL)gender; //OC中方法名不同
OC中只有重寫,沒有重載
方法的重寫:子類對父類繼承的方法不滿意,可以在子類中重寫父類的方法。
如果重寫父類的方法,優先調用子類的方法,如果子類沒有重寫父類的方法,則調用父類的方法。
2.6.2 注意
雖然父類中的屬性是公開的,但產生的執行個體變數卻是私人的,在子類中不能訪問
2.6.3 普通方法的重寫
如同perimeter, area, show
方法名相同,參數類型相同,傳回值相同
2.6.4 屬性的重寫
如同TRSqaure中的width,height屬性
2.6.5 特殊方法的繼承和重寫
1) 初始化方法
在OC中初始化方法是會被繼承的。
繼承來的初始化方法有些可以用,有些不能用。
如果在子類中,繼承自父類的初始化方法不能用(不能完成要求的初始化任務),在子類中就需要重寫這個初始化方法。
2) 類方法(Factory 方法)
類方法也可以被繼承
Factory 方法自然也可以被繼承,但直接繼承的Factory 方法名不匹配,實際開發中很少這樣用。子類最好提供自己的Factory 方法。
2.7 繼承的缺陷
1) 提高了程式的複雜度,維護性和擴充性降低。
2) 破壞類的封裝性 慎用繼承!
2.8 為什麼使用繼承?
1) 代碼複用
將子類重複的代碼抽象到父類中
2) 制定規範(規則)
NSObject
3) 為了多態
沒有繼承,就沒有多態
組合與彙總
類與類之間的一種關係,比較常見;主要作用:代碼複用!
1.1 什麼是組合
表示兩個對象之間是整體和部分的強關係,是“contains(包含) a”關係,要求兩個類同生共死。
生命週期完全一致,同生共死。部分的生命週期不能超越整體!
例如:一個視窗內有按鈕、標籤,當視窗關閉時,視窗與按鈕、標籤同時銷毀。
1.2 組合的定義:
@interface BRButton : NSObject @end @interface BREdit : NSObject @end @interface BRWindow : NSObject /** * 組合是兩個類的強關係(要求定義成成員變數) * 1.在對象初始化時給執行個體變數賦值(同生共死) * 2.不能在類的外部對執行個體變數訪問/賦值 */ { BRButton *button; BREdit *edit; } @end |
組合Demo:
1.3 組合的優缺點:
優點:
1)當前對象只能通過所包含的那個對象去調用其方法,所以所包含的對象的內部細節對當前對象是不可見的。
2)當前對象與包含的對象是一個低耦合關係,如果修改包含對象的類中代碼,不需要修改當前對象類的代碼。
3)當前對象可以在運行時動態綁定所包含的對象。可以通過set方法給所包含對象賦值。
缺點:
容易產生過多的對象
為了能組合多個對象,必須仔細對介面進行定義。
1.4 什麼是彙總
表示兩個對象之間是整體和部分的弱關係,是”has a”關係,不要求兩個類同生共死。
生命週期不一致,一個類無法控制另一個類的生死。部分的生命週期可以超越整體。
例如:電腦和滑鼠,電腦被銷毀時,滑鼠可以留下在其他電腦上繼續使用。
1.5 彙總的定義
@interface BRMouse : NSObject @end |
@interface BRComputer : NSObject //彙總是兩個類的弱關係,在類的外部給執行個體變數賦值(要求定義成屬性) @property BRMouse *mouse; @end |
彙總Demo:
1.6 彙總的優缺點:
優點:
1)被包含的對象通過包含它們的類來訪問
2)很好的封裝
3)被包含的對象內部細節不可見
4)可以在運行時動態定義彙總的方式
缺點:
1)系統可能會包含太多個物件
2)當使用不同的對象時,必須小心定義的介面。
組合和彙總的生命週期不一樣,組合是同生共死(關係緊密);彙總沒有特別的關係。
1.7 類與類之間常見的三種關係:繼承、組合、彙總
1) 繼承(繼承的主要目的:代碼複用,制定規範,為了多態) 慎用繼承!(is a關係才用繼承,否則濫用繼承)
2) 組合和彙總(單純的為了代碼複用)
組合和彙總的主要目的:是為了代碼的複用。
應用情境:(代碼的重複使用。如果想使用別人的代碼,組合、彙總在一起就可以了)
實際開發中,如果為了複用代碼,提倡使用組合或彙總來複用,而不用繼承。即是把一個類作為另一個類的屬性。(整體與部分)
一個類想使用另一個類的方法?怎麼實現...
1.繼承:我繼承你,我就可以直接調你的方法(提高程式的複雜度…不建議用!)
2.組合/彙總:把你變成我的一部分(即你是我的屬性),我可以通過訪問你去調用你的方法![我.你 你的方法];
組合:關係很緊密,同生共死。(cpu焊在主板上,主板壞了cpu也就壞了;綁在一起同生共死!)
彙總:關係很疏遠,不同生共死。(電腦是電腦,cpu是cpu;這個cpu可以用給這個電腦,也可以用給那個電腦,cpu可以從主板上拔下來給另一個電腦用)
3. 多態(Polymorphism)
3.1 什麼是多態
多種形態,引用的多種形態。對於一個引用變數,可以指向任何類的對象 (一個對外介面,多個內在實現)
父類的引用指向本類或任何子類的對象,會調用不同的方法(表現出多種形態,這種表現叫多態)
TRAnimal *animal = [[TRDog alloc]init];//父類的引用指向子類的對象 [animal eat];//狗啃骨頭 (調用子類重寫的方法) animal = [[TRCat alloc]init]; [animal eat];//貓吃魚 |
多態的表現:同一個引用變數s,調用相同的方法(show),顯示不同的結果。
TRShape *s=[[TRRect alloc]init]; //父類的引用指向子類的對象 [s show]; //顯示矩形 調用子類重寫的方法 s=[TRCircle alloc]init]; [s show]; //顯示圓形 |
3.2 編譯期類型和運行期類型
編譯器編譯時間,引用的類型叫編譯期類型。
程式運行時,引用指向的類型叫運行期類型。
程式編譯時間按編譯期類型找方法編譯,程式運行時按運行期類型找方法調用。
父類的引用指向子類的對象時,只能調用父類中有的方法。
因為編譯器在編譯時間是按父類來檢查的,雖然運行時調用的是子類的方法,但是編譯時間是按父類來檢查的。
3.3 為什麼用多態
為什麼用父類的引用指向子類的對象:
為了寫出更加能用(通用),相容性更好的代碼。
3.4 多態的各種表現【用途】
1)【多重參數變形】經常在方法的參數表現多態:參數類型使用父類型,可以傳任何子類的對象
NSObject *和id類型做為方法的參數區別在於:
NSObject *類型的引用只能調用NSObject類中有的方法。
id類型的引用可以調用任何存在(不能瞎寫)的方法。
(一般不用NSObject *類型,用id類型替代。id有風險的,編譯器根本不檢查對錯)
/** 方法的參數 表現多態 */ void showAnimal(TRAnimal *animal) { [animal eat]; [animal sleep]; //[animal watchHome];//父類中沒有這個方法編譯不通過 } int main() { @autoreleasepool { TRDog *dog = [[TRDog alloc]init]; showAnimal(dog); showAnimal([TRCat new]); } return 0; } |
2)【傳回值多態】方法的傳回值上表現多態
3)【數組多態】在數組和集合中表現的多態
//數組 表現多態 [多個對象同時執行eat方法] TRAnimal *animals[3] = {[TRAnimal new], [TRDog new], [TRCat new]}; for (int i = 0; i < 3; i++) { [animals[i] eat]; } |
多態無處不在!
多態Demo