什么是多態
什么是多態:
多態就是某一類事物的多種形態
貓: 貓-->動物
狗: 狗-->動物
男人 : 男人 -->人 -->動物
女人 : 女人 -->人 -->動物
多態表示不同的對象可以執行相同的動作, 但是通過他們自己的實現代碼來執行
程序中的多態:父類指針指向子類對象
多態的條件
有繼承關系
子類重寫父類方法
父類指針指向子類對象
狗 *g = [狗 new];
動物 *a = [狗new];
貓 *c = [貓 new];
動物 *a = [貓 new];
表現:當父類指針指向不同的對象的時候,通過父類指針調用被重寫的方法的時候,會執行該指針所指向的那個對象的方法
多態的優點
多態的主要好處就是簡化了編程接口。它允許在類和類之間重用一些習慣性的命名,而不用為每一個新的方法命名一個新名字。這樣,編程接口就是一些抽象的行為的集合,從而和實現接口的類的區分開來。
多態也使得代碼可以分散在不同的對象中而不用試圖在一個方法中考慮到所有可能的對象。這樣使得您的代碼擴展性和復用性更好一些。當一個新的情景出現時,您無須對現有的代碼進行 改動,而只需要增加一個新的類和新的同名方法。
多態的原理
動態綁定:
動態類型能使程序直到執行時才確定對象的真實類型
動態類型綁定能使程序直到執行時才確定要對那個對象調用的方法
OC可以在運行時加入新的數據類型和新的程序模塊:動態類型識別,動態綁定,動態加載
id類型:通用對象指針類型,弱類型,編譯時不進行具體類型檢查
補充:
動態數據類型: 在編譯的時候編譯器并不知道變量的真實類型,只有在運行的時候才知道它的真實類型 并且如果通過動態數據類型定義變量, 如果訪問了不屬于動態數據類型的屬性和方法, 編譯器不會報錯
靜態數據類型: 默認情況下所有的數據類型都是靜態數據類型, 在編譯時就知道變量的類型, 知道變量中有哪些屬性和方法 , 在編譯的時候就可以訪問這些屬性和方法, 并且如果是通過靜態數據類型定義變量, 如果訪問不了屬于靜態數據類型的屬性和方法, 那么編譯器就會報錯
里氏替換原則: 子類對象能夠替換其父類對象被使用。就是說“子類是父類”,比如,人是動物,但動物不一定是人,有一個Animal類和一個繼承自Animal類的Person類, 這時, Animal類型的變量可以指向Person類的實例, 但是Person類類型的變量不能指向Animal類的實例!
開放封閉原則:軟件實體(類 模塊 函數等等) 應該可以擴展,但是不可修改. 這個原則其實是有兩個特征:
一個是說"對于擴展是開放的(Open for extension) " : 意味著有新的需求時,可以對已有的軟件實體進行擴展,以滿足新的需求
一個是說"對于更改是封閉的(Closed formodification)" : 意味著軟件實體一旦設計完成,就可以獨立完成其工作,而不要對其進行任何修改。
多態的實現
人喂寵物吃東西的例子:
人(Person) 行為: 喂寵物吃東西(feedPet)
寵物(Pets) 行為: 吃東西(eat)
貓(Cat) 行為: 吃東西(eat)
狗(Dog) 行為: 吃東西(eat)
(貓類和狗類 繼承自寵物類)
示例代碼:
寵物類的聲明實現:
#import <Foundation/Foundation.h> @interface Pets : NSObject // 吃東西 - (void) eat; @end #import "Pets.h" @implementation Pets // 吃東西 - (void)eat{ NSLog(@"寵物吃東西"); } @end
貓類的聲明實現:
#import "Pets.h" @interface Cat : Pets // 貓類特有的方法 抓老鼠 - (void) catchMouse; @end #import "Cat.h" @implementation Cat // 重寫父類吃東西方法 - (void)eat{ NSLog(@"人喂寵物貓吃東西"); } // 實現貓類特有的方法 抓老鼠 - (void)catchMouse{ NSLog(@"老貓抓老鼠"); } @end
狗類的聲明實現:
#import "Pets.h" @interface Dog : Pets @end #import "Dog.h" @implementation Dog // 重寫父類吃東西方法 - (void)eat{ NSLog(@"人喂寵物狗吃東西"); } @end
人類的聲明實現:
#import "Pets.h" @interface Person : NSObject // 喂寵物吃東西 + (void) feedPet:(Pets *) pet; @end #import "Person.h" @implementation Person // 喂寵物吃東西 + (void)feedPet:(Pets *)pet{ [pet eat]; } @end
Main.m :
#import <Foundation/Foundation.h> #import "Person.h" #import "Pets.h" #import "Dog.h" #import "Cat.h" int main(int argc, const char * argv[]) { // 多態的體現 Pets * pet = [Pets new]; [Person feedPet:pet]; pet = [Dog new]; [Person feedPet:pet]; pet = [Cat new]; [Person feedPet:pet]; return 0; }
輸出結果:
/* 2015-08-31 18:10:06.659 多態示例[833:53348] 寵物吃東西 2015-08-31 18:10:06.660 多態示例[833:53348] 人喂寵物狗吃東西 2015-08-31 18:10:06.660 多態示例[833:53348] 人喂寵物貓吃東西
*/
》在上面代碼中我們將Dog 和 Cat的實例賦值給了Pets類型的變量 pet , 即父類類型的指針指向了子類的對象, 這便是里氏替換原則的體現
》我們給Person類定義了一個類方法 : + (void) feedPet:(Pets) pet; 這個方法是接收一個Pets類型的對象作為參數, 然后再方法體里通過傳進來的對象調用吃的方法(eat), 我們給feedPet方法傳遞的參數都是Pets類型的變量 pet, 但是通過輸出結果可以知道, 實際上是分別調用了寵物 狗 和 貓 的吃的方法 也就是說:當父類指針指向不同的對象的時候,通過父類指針調用被重寫的方法,會執行該指針所指向的那個對象的方法, 這就是多態
多態注意點
父類指針指向子類對象, 如果需要調用子類特有的方法, 必須先強制類型轉換為子類才能調用
Pets * pet = [Cat new]; // 錯誤信息: No visible @interface for 'Pets' declares the selector 'catchMouse' [pet catchMouse]; // 類型轉換后在調用方法 [(Cat *)pet catchMouse];
封裝繼承多態練習(示例源于 大話設計模式 程杰著)
實現一個計算機控制臺程序 要求輸入兩個數和操作符 輸出結果
1.0版:
#import <Foundation/Foundation.h>int main(int argc, const char * argv[]) { double a =1, b = 10, c = 0; char d = '+'; if(d == '+'){ c = a + b; } if( d == '-'){ c = a - b; } if( d == '*'){ c = a * b; } if( d == '/'){ c = a / b; } NSLog(@" 運算結果:%.2lf", c); return 0;}
結論:
"代碼無錯就是優" 能獲得想要的結果 挺好的嘛
問題:
1: 變量命名不規范 a b c d 沒啥意義 看著名字也不知道是用來干嘛的
2: 注釋都沒有 程序只有自己暫時能看懂
3: 每個if都要判斷一遍
4: 做除法時如果除以 0 會怎么樣
2.0版:
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { // 用于計算的兩個數字和運算符應有用戶輸入 此處寫死在程序中 double num1 = 10, num2 = 20, result = 0; char Operate = '+'; // 根據輸入的運算符選擇運算方式并獲得運算結果 switch (operate) { case '+': result = num1 + num2; break; case '-': result = num1 - num2; break; case '*': result = num1 * num2; break; case '/': if (num2 != 0) { result = num1 / num2; } else{ NSLog(@"除數不能為 0"); // 是除數還是被除數啊 忘了 } break; } NSLog(@"運算結果是: %.2lf", result); return 0;}
結論:
好了 變量名像點樣了 也不會做沒必要的判斷了 還能得到想要的結果 這總沒問題了吧
書摘:
"碰到問題就直覺的用計算機能夠理解的邏輯來描述和表達待解決的問題和具體的求解過程,這本身沒有錯, 但這樣的思維卻使得我們的程序只為滿足實現當前的需求, 程序不容易維護, 不容易擴展, 更不容易復用,從而達不到高質量代碼的需求"
問題:
現在的計算器只是控制臺的輸出 如果要做個桌面程序 或者網站上的計算器 或者是手機應用怎么辦?? 把代碼拷貝過去?? 怎么讓代碼復用呢
3.0版:
Operation類聲明文件:
// 添加一個Operation運算類#import <Foundation/Foundation.h>@interface Operation : NSObject// 傳入兩個數字 和 一個操作符 獲得運算結果+ (double) getResultOfNum1:(double) num1 andNum2: (double) num2 withOperate:(char) operate; @end
Operation類實現文件:
#import "Operation.h" @implementation Operation// 根據傳入的兩個數字和運算符獲得運算結果+ (double)getResultOfNum1:(double)num1 andNum2:(double)num2 withOperate:(char)operate{ // 用于存儲結果 double result = 0; // 選擇運算方式并獲得結果 switch (operate) { case '+': result = num1 + num2; break; case '-': result = num1 - num2; break; case '*': result = num1 * num2; break; case '/': if (num2 != 0) { result = num1 / num2; } else{ NSLog(@"除數不能為 0"); } break; } // 返回運算結果 return result;}@end
Main.m:
#import <Foundation/Foundation.h>#import "Operation.h" int main(int argc, const char * argv[]) { // 兩個參與運算的數字 res用于存儲運算結果 double num1 = 10, num2 = 20, res = 0; // 運算符 char ope = '*'; // 調用運算類的運算方法獲得結果 res = [Operation getResultOfNum1:num1 andNum2:num2 withOperate:ope]; // 打印輸出 NSLog(@"運算結果是:%.2lf", res); return 0;}
結論:
現在通過封裝把計算和顯示分開了 也就是實現了業務邏輯和界面邏輯的分離 這里主函數中的代碼也精簡了一些 不用去管方法里面到底是怎么得到結果的 運算類的代碼也不用去動便可復用
問題:
但是 怎么讓代碼可以靈活的擴展呢? 比如要加一個求余 開方等運算進去, 我們現在需要改動的是在運算類的switch中添加分支就行了, 但是這樣真的好嗎? 只是加一個運算符卻需要其他已經有的運算符都來參與編譯
書上舉例:
"現在公司要你維護薪資管理系統, 原本只有技術人員(月薪) 銷售(底薪+提成) 經理(年薪+股份)這三種運算算法, 現在需要加一個兼職員工(日薪)的算法, 按照現在這種做法, 公司必須把包含原有三種算法的類交給你來修改, 如果一不小心將兼職員工工資算法加進去的同時,順手把技術人員的月薪提高那么一丟丟, 是不是很爽呢, 總有人不爽的"
優化:
將加減乘除等運算做分離, 修改其中一個也不影響其他, 增加運算算法也不影響已有的算法, 這就是對擴展開放, 對修改封閉--開放-封閉原則
4.0版:
Operation類的聲明實現:
#import <Foundation/Foundation.h> @interface Operation : NSObject{ @public double _number1; double _number2;}- (void) setNumber1: (double) number1;- (void) setNumber2: (double) number2;// 獲取運算結果- (double) getResult;@end #import "Operation.h" @implementation Operation- (void)setNumber1:(double)number1{ _number1 = number1;}- (void)setNumber2:(double)number2{ _number2 = number2;}// 獲取運算結果- (double)getResult{ double result = 0; return result;}@end
加法類的聲明實現:
#import "Operation.h"// 加法運算類@interface OperationAdd : Operation@end#import "OperationAdd.h"@implementation OperationAdd// 獲得兩數相加結果- (double)getResult{ double result = 0; result = _number1 + _number2; return result;}@end
減法類的聲明實現:
#import "Operation.h"// 減法運算類@interface OperationSub : Operation@end #import "OperationSub.h"@implementation OperationSub// 獲得兩數相減的結果- (double)getResult{ double result; result = _number1 - _number2; return result;}@end
乘法類的聲明實現:
#import "Operation.h"// 乘法運算類@interface OperationMul : Operation@end #import "OperationMul.h"@implementation OperationMul// 獲得兩數相乘的結果- (double)getResult{ double result = 0; result = _number1 * _number2; return result;}@end
除法類的聲明實現:
#import "Operation.h"// 除法運算類@interface OperationDiv : Operation@end #import "OperationDiv.h"@implementation OperationDiv// 獲得兩數相除的結果- (double)getResult{ double result = 0; if (_number2 != 0) { result = _number1 / _number2; } else{ NSLog(@"除數不能為 0"); } return result;}@end
工廠類的聲明實現:
#import "OperationAdd.h"#import "OperationSub.h"#import "OperationMul.h"#import "OperationDiv.h" // 專門用于獲得運算類實例@interface GetOperation : NSObject+ (Operation *) createOperationWithOperate: (char) operate;@end #import "GetOperation.h"@implementation GetOperation// 獲得運算類實例+ (Operation *)createOperationWithOperate:(char)operate{ // 多態的應用 父類指針指向子類實例對象 Operation * ope = nil; // 根據傳入的操作符獲得相應的實例對象 switch (operate) { case '+': ope = [OperationAdd new]; break; case '-': ope = [OperationSub new]; break; case '*': ope = [OperationMul new]; break; case '/': ope = [OperationDiv new]; break; } return ope;}@end
Main.m:
#import <Foundation/Foundation.h>#import "GetOperation.h" int main(int argc, const char * argv[]) { double num1 = 10, num2 = 20, res = 0; char operate = '*'; Operation * ope = [GetOperation createOperationWithOperate:operate]; ope.number1 = num1; ope.number2 = num2; res = [ope getResult]; NSLog(@"運算結果是: %.2lf", res); return 0;}
現在如果要修改其中一種運算 對其他的運算都不會有影響了 如果想要添加一種新運算 也只需要添加一個新運算類 然后在工廠方法中修改下switch分支就行了 不需要再提供原有的運算類 所以對其他已經存在的運算類都不會有影響 這樣便實現了開放-封閉原則
書摘: "編程是一門技術,更是一門藝術,不能只滿足于寫完代碼運行結果正確就完事,時??紤]如何讓代碼更加簡練,更加容易維護,容易擴展和復用, 只有這樣才可以真正得到提高"
新聞熱點
疑難解答