跳到主要內容

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

世界第一! 臺灣的驕傲! iPod 全球最貴 XD!!??

2009 年 9 月 10 日 凌晨 1 點鐘, Apple 於美國舉行了 Apple Special Event September 2009 . 先說點題外話, 這場演說對 Apple 迷而言, 是久違的一場, 也不知道還能有幾場像這場一樣, 由 Steve Jobs 親自站臺. 這場演說的主題, 除了 iTunes 9, iPhone (iPod touch) OS 更新為 3.1 外, 就是 iPod 產品線的更新. 在看完發表會之後, 我去研究了一下新 iPod 的全球售價, 這也是這篇文章的重點, 蘋果臺灣的定價創下了新的紀錄- 套用一下最近常聽到的 Slogan 世界第一, 臺灣的驕傲, iPod 全球最貴!!??