跳到主要內容

關於 Polymorphism! 是的, 就是多型!

這幾天真的是寫了好多關於程式基礎的東西, 今天心血來潮想寫個物件導向的特性 - Polymorphism. 是的, 你沒看錯, 就是多型.

多型, 這有什麼好寫的呀? 每本物件導向的書籍, 不管是程式語言或是方法論, 一定都會提到的基本特性. 去書局翻翻就有了, 甚至在網路上搜尋 “物件導向” “多型”, 就可以找到讀也讀不完的資料. 在這老調重彈, 似乎有點... 多餘?
會想把這個議題拿出來講, 關鍵就在於我發現 Objective-C 上的多型, 和我以前認識的不太一樣, 更加 Powerful.

Java 上的多型, 無非透過 extends class (繼承類別), 和 implements interface (實作介面), 來達到呼叫相同名稱的方法卻有不同實作內容的效果. Objective-C 上也是如此! 只是 Java 中的 interface 到了 Objective-C 叫做 protocol.
如果只有這樣的差異, 也沒什麼好多說的. 不同的是 Objective-C 中多了兩個特性讓多型這件事情變得很不一樣 - Dynamic Typing (動態型別) 和 Dynamic Binding (動態繫結).

看一下在官方文件中的定義:
Dynamic Typing
Discovering the class of an object at runtime rather than at compile time.
Dynamic Binding
Binding a method to a message—that is, finding the method implementation to invoke in response to the message—at runtime, rather than at compile time.
簡單來說, 動態型別就是指
=> 物件實際上指涉 (refer) 到哪個類別是在執行期間 (runtime) 才判定, 而非在編譯期間 (compile time).
而動態繫結是指
=> 在執行期間才依據 message 找出方法 (method) 實際的實作並呼叫. 所謂的 message 是 Objective-C 呼叫方法的方式, 如 "[someObject doSomething]" 即為呼叫物件 someObject 的方法 doSomething.

看到這樣的說明, 頗抽象的, 看個實例馬上就會懂這是多麼神奇的一件事情.
範例中一共有三個類別, 分別為父類別 (superclass) 的 Animal, 以及繼承 Animal 的兩個子類別 (subclass) Cat 與 Dog. Animal 宣告了一個方法 - (void)print, 而 Cat 與 Dog 個別都覆寫 (override) 了這個方法.
OK, 準備工作完成, 看一下下面的程式碼吧!
Animal *animal;
// 實際上是 Cat
animal = [Cat new];
[animal print];
[animal release];
// 實際上是 Dog
animal = [Dog new];
[animal print];
[animal release];
其實只要懂一點物件導向, 就不難猜出會有以下的輸出結果:
It's a cat.
It's a dog.
沒什麼好驚奇的, 因為這就是我認知中的 Polymorphism, 也是 Java 中的 Polymorphism 的運作方式. 現在改寫一下程式碼, 見識一下 Dynamic Typing 加 Dynamic Binding 的效果:
id anyType;
// 實際上是 Cat
anyType = [Cat new];
[anyType print];
[anyType release];
// 實際上是 Dog
anyType = [Dog new];
[anyType print];
[anyType release];
猜猜輸出結果如何? 答案是 => 一樣!!
問題來了 => id 是什麼? 為什麼 Cat 和 Dog 可以轉型為 id? 為什麼 id 認得 - (void)print 方法? id 和 Animal 的關係是?

事實上, id 是個泛用型別 (generic data type), 可以用來存放各種資料型態的物件. 使用 id 也就是使用 Dynamic Typing 的方式.

乍看之下, id 的概念在 Java 中有點類似在沒有泛型語法支援前的狀況. 舉例來說, 在 Java 的 ArrayList 中放的必須是 Object 類別的物件, 但因為所有的類別都是繼承自 Object 類別, 所以將資料存進 ArrayList 中並沒有什麼大問題. 但取出資料時呢? 這時就麻煩了, 必須自行轉回原本的資料型態才能使用定義在子類別中的屬性和方法. 這一點就是和 id 最大的差異.
由於 Dynamic Typing 的關係, id 在執行時, Objective-C 的執行環境會去找出該 id 所代表的資料形態. 也因此, 根本沒有所謂的轉型.

到目前為止, 解開了 id 是什麼, 以及為什麼 Cat 和 Dog 和 id 之間的關係.
那為什麼 id 會認得 - (void)print 方法? 這就是 Dynamic Binding 神奇的地方.

實際上, id 並不認得 - (void)print 這個方法. Dynamic Binding 是在執行時才去找出對應 message 的實作方法並呼叫.
也就是說, 在編譯時, 編譯器並不去考慮 id 是否擁有 - (void)print 這個方法. 而是在執行時期時, 在 Dynamic Typing 找出原本的資料型態 (Cat 和 Dog) 後, 才到該類別中尋找 - (void)print 這個方法的實作並執行. 這也意味著, 如果呼叫的方法實際上並不存在於 id 所代表的類別中, 系統將在執行時期拋出例外.

最後, 只剩下一個問題, id 和 Animal 的關係是?
答案是 => 沒有關係!
id 並不是自動的轉換成 Cat 和 Dog 的父類別, 而是在執行期間, 由執行環境辨識出 id 實際代表的型別為 Cat 和 Dog. 因此在這個範例中, id 壓根子不認識 Animal.

這就是 Objective-C 底下的 Polymorphism. 神奇吧!?
除了透過繼承類別和採用協定 (Java 中的 interface) 外, Dynamic Typing 與 Dynamic Binding 這兩個特性打破了這樣的限制, 就算沒有繼承和實作協定的關係, 一樣可以擁有多型帶來的便利.

留言

Stewart寫道…
這篇寫得不賴,用很白話的方式說明動態型別跟繫結還滿清楚的。
阿刃寫道…
謝了...
本來以為寫這些艱澀的東西, 除了我之外就沒人想看了 XD
Unknown寫道…
請問一下,如果沒有繼承的關係...Obj-c是怎麼進行多型的呢?這有點不太懂,可否解惑,謝謝
阿刃寫道…
如果問題是 Obj-C 要怎麼達到多型?
那答案有兩種,
第一種就是透過繼承,
第二種就是透過 Dynamic Typing & Dynamic Binding 來達成。理由請參考此篇文章的本文~

這個網誌中的熱門文章

引數?! 參數??!! 什麼鬼啊!!

我想這個不僅是只有我會遇到的問題, 大概也是所有 Programming 的人都有的疑惑 (應該是吧?) 什麼引數?? 什麼參數?? 天啊... 到底是誰翻譯的呀!!!! 就字面上來看... 講句實在話, 我真的不了解, 大概是學藝不精所致. 也許有些人認為 => 管它那麼多!! 寫得出來就好了!! 不過龜毛如我, 我還是想搞清楚定義是什麼!

Mac OS X 10.6 同步收取 Gmail, how?

在 Apple 官方的 Mac OS X 10.6 介紹網頁中, 雖然並沒有在內建的郵件軟體 - Mail 著墨太多. 但是實際上 Mail 也做了一些改善, 讓使用可以更方便, 而這篇文主要目的就是說明怎麼讓 Mail 與 Gmail 進行同步. 在過去使用郵件軟體收取電子郵件的使用經驗中, 往往都需要輸入很多資料, 什麼 "寄件伺服器" "收件伺服器" "通訊協定"... 等等等, 這對一般用戶來說, 只能說是災難. 畢竟沒學過那些專有名詞, 天曉得那是些什麼東西!? 而 Mac OS X 10.6 中的 Mail 不同了, 現在只需要輸入 Email 帳號及密碼, 軟體自動搞定細部設定! 有沒有這麼簡單!? 手邊有 Hotmail 的朋友可以試試看, 只要輸入帳號密碼, 10.6 的 Mail 就會幫你設定好其它需要設定的資料. 不過今天的主角是 Gmail 啦! 而要使用的是 Gmail 提供的 IMAP 的功能, 在使用前得先到 Gmail 中進行一些簡單的設定, 當然小弟也會說明這樣設定的理由 :) OK! Here we go!!!

第二章 緊急返鄉 - 推著爺爺去散步

也許是曾經身為軍人的關係, 爺爺很排斥使用輪椅... 也許是曾經身為一家之主的榮耀, 縱使小便不方便卻堅持不肯使用尿壺... 也許就是因為爺爺是爺爺, 所以很少開口要我幫忙他些什麼凡是自己來, 總是他在給我, 我卻很少能給他什麼... 只是兩次的手術, 在病床上躺久了, 他的身體狀況真的大不如前... 沒辦法想自己起來走走就走走, 想坐到沙發就坐到沙發, 想拿起遙控器看看電視就拿, 想回房休息就回房間. 這樣的光景, 以前是再熟悉不過的... 很希望爺爺真的能趕快好起來, 而回家時一切都沒變, 爺爺看到我回家開心大笑起身握握我的手... 現在的他, 只能任我們宰割... 儘管他再怎麼使勁, 那纖細的雙腿依然撐不住他的身子, 不喜歡輪椅也得坐輪椅... 儘管他再怎麼不願意, 行動不方便的他還是被我們包了尿布, 不喜歡尿布也得包尿布... 爺爺一點點的隱私, 一點點的尊嚴也不剩, 連不願意吃飯, 都被我們直接灌食, 連絕食的權力都沒有...