什麼是多態算法
什麼是多態:編程
多態就是某一類事物的多種形態 設計模式
貓: 貓-->動物函數
狗: 狗-->動物優化
男人 : 男人 -->人 -->動物網站
女人 : 女人 -->人 -->動物spa
多態表示不一樣的對象能夠執行相同的動做, 可是經過他們本身的實現代碼來執行設計
程序中的多態:父類指針指向子類對象 指針
多態的條件code
有繼承關係
子類重寫父類方法
父類指針指向子類對象
狗 *g = [狗 new];
動物 *a = [狗 new];
貓 *c = [貓 new];
動物 *a = [貓 new];
表現:當父類指針指向不一樣的對象的時候,經過父類指針調用被重寫的方法的時候,會執行該指針所指向的那個對象的方法
多態的優勢
多態的主要好處就是簡化了編程接口。它容許在類和類之間重用一些習慣性的命名,而不用爲每個新的方法命名一個新名字。這樣,編程接口就是一些抽象的行爲的集合,從而和實現接口的類的區分開來。
多態也使得代碼能夠分散在不一樣的對象中而不用試圖在一個方法中考慮到全部可能的對象。 這樣使得您的代碼擴展性和複用性更好一些。當一個新的情景出現時,您無須對現有的代碼進行 改動,而只須要增長一個新的類和新的同名方法。
多態的原理
動態綁定:
動態類型能使程序直到執行時才肯定對象的真實類型
動態類型綁定能使程序直到執行時才肯定要對那個對象調用的方法
OC能夠在運行時加入新的數據類型和新的程序模塊:動態類型識別,動態綁定,動態加載
id類型:通用對象指針類型,弱類型,編譯時不進行具體類型檢查
補充:
動態數據類型: 在編譯的時候編譯器並不知道變量的真實類型, 只有在運行的時候才知道它的真實類型 而且若是經過動態數據類型定義變量, 若是訪問了不屬於動態數據類型的屬性和方法, 編譯器不會報錯
靜態數據類型: 默認狀況下全部的數據類型都是靜態數據類型, 在編譯時就知道變量的類型, 知道變量中有哪些屬性和方法 , 在編譯的時候就能夠訪問這些屬性和方法, 而且若是是經過靜態數據類型定義變量, 若是訪問不了屬於靜態數據類型的屬性和方法, 那麼編譯器就會報錯
里氏替換原則: 子類對象可以替換其父類對象被使用。就是說「子類是父類」,好比,人是動物,但動物不必定是人,有一個Animal類和一個繼承自Animal類的Person類, 這時, Animal類型的變量能夠指向Person類的實例, 可是Person類類型的變量不能指向Animal類的實例!
開放封閉原則:軟件實體(類 模塊 函數等等) 應該能夠擴展,可是不可修改. 這個原則實際上是有兩個特徵:
一個是說"對於擴展是開放的(Open for extension) " : 意味着有新的需求時,能夠對已有的軟件實體進行擴展,以知足新的需求
一個是說"對於更改是封閉的(Closed for modification)" : 意味着軟件實體一旦設計完成,就能夠獨立完成其工做,而不要對其進行任何修改。
多態的實現
人喂寵物吃東西的例子:
人(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分支就好了 不須要再提供原有的運算類 因此對其餘已經存在的運算類都不會有影響 這樣便實現了開放-封閉原則
書摘: "編程是一門技術,更是一門藝術,不能只知足於寫完代碼運行結果正確就完事,時常考慮如何讓代碼更加簡練,更加容易維護,容易擴展和複用, 只有這樣才能夠真正獲得提升"