讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 4.5 多態 >

4.5 多態

如果某個計算機語言有類型與子類型層次,那麼它必須要解決這樣一個問題:對於對像類型與聲明的指向該對象的引用類型之間的關係,這種層次體系意味著什麼。Swift遵循著多態的原則。我認為,正是多態的作用才使得基於對象的語言徹底演變為完善的面向對像語言。Swift的多態原則如下所示:

替代

在需要某個類型時,我們可以使用該類型的子類型。

內在一致性

對像類型的關鍵在於內部特性,而與對象是如何被引用的毫無關係。

下面來看看這些原則的含義。假設有一個Dog類,它有一個子類NoisyDog:


class Dog {
}
class NoisyDog : Dog {
}
let d : Dog = NoisyDog
  

替代法則表示上述代碼最後一行是合法的:我們可以將NoisyDog實例賦給類型為Dog的引用d。內在一致性法則表示,在底層d現在就是個NoisyDog。

你可能會問:如何證明內在一致性規則?如果對NoisyDog的引用類型是Dog,那麼它怎麼就是NoisyDog呢?為了說明這一問題,我們來看看當子類重寫了繼承下來的方法時會發生什麼。下面重新定義Dog與NoisyDog進行說明:


class Dog {
    func bark {
        print(\"woof\")
    }
}
class NoisyDog : Dog {
    override func bark {
        super.bark; super.bark
    }
}
  

看看下面的代碼,想想能否編譯通過,如果能,那麼結果是什麼:


func tellToBark(d:Dog) {
    d.bark
}
var d = NoisyDog
tellToBark(d)
  

上述代碼可以編譯通過。我們創建了一個NoisyDog實例並將其傳給了需要Dog參數的函數。這麼做是可以的,因為NoisyDog是Dog的子類(替代)。NoisyDog可以用在需要Dog的地方。從類型上來看,NoisyDog就是一種Dog。

不過當代碼實際運行並調用tellToBark函數時,它裡面的局部變量d所引用的對象的bark會做什麼呢?一方面,d的類型是Dog,Dog的bark函數會打印出\"woof\"一次;另一方面,當調用tellToBark時,實際傳遞的是NoisyDog實例,而NoisyDog的bark函數會打印出\"woof\"兩次,那結果會是什麼呢?下面就來看一下:


func tellToBark(d:Dog) {
    d.bark
}
var d = NoisyDog
tellToBark(d) // woof woof
  

結果是\"woof woof\"。內在一致性法則表明在發送消息時,重要的事情並不是如何通過引用來判斷消息接收者的類型,而是接收者的實際類型到底是什麼。無論持有的變量類型是什麼,傳遞給tellToBark的是NoisyDog;因此,bark消息會使得該對像打印出兩次\"woof\"。它是個NoisyDog!

下面是多態的另一個重要影響:關鍵字self的含義。它指的是實際實例,其含義取決於實際實例的類型,即便單詞self出現在父類代碼中亦如此。比如:


class Dog {
    func bark {
        print(\"woof\")
    }
    func speak {
        self.bark
    }
}
class NoisyDog : Dog {
    override func bark {
        super.bark; super.bark
    }
}
  

調用NoisyDog的speak方法時會打印什麼呢?下面來試一下:


let d = NoisyDog
d.speak // woof woof
  

speak方法聲明在Dog而非NoisyDog中,即聲明在父類中。speak方法會調用bark方法,這是通過關鍵字self實現的(這裡其實可以省略對self的顯式引用,不過即便如此,self還是會隱式使用,因此我還是顯式使用了self)。Dog中有個bark方法,NoisyDog中有個重寫的bark方法。那麼到底會調用哪個bark方法呢?

關鍵字self位於Dog類的speak方法實現中。不過,重要的事情並不是單詞self在哪裡,而是它表示什麼含義。它表示當前實例。內在一致性法則告訴我們,當前實例是個NoisyDog!因此,調用的是NoisyDog重寫的bark。

歸功於多態,你可以充分利用子類為已有的類增加功能並做更多的定制。這在iOS編程世界中尤為重要,其中大多數類都是由Cocoa定義的,並不屬於你。比如,UIViewController是由Cocoa定義的;它有大量Cocoa會調用的內建方法,這些方法會執行各種重要的任務,而且是以一種通用的方式執行的。在實際情況中,你會聲明UIViewController的子類並重寫這些方法來完成適合於特定應用的任務。這對Cocoa不會造成任何影響,因為替代法則在發揮著作用,在Cocoa期望接收或要調用UIViewController時,它可以接收你自己定義的UIViewController子類,這麼做不會產生任何問題。而且,這種替代行為與你的期望是一致的,因為(內在一致性法則)當Cocoa調用子類中的UIViewController方法時,真正調用的實際上是子類重寫的版本。

多態很酷,不過其速度會慢一些。它需要動態分發,這意味著運行時要思考向類實例發送的消息到底表示什麼。這也是在可能的情況下優先使用結構體而非類的另一個原因:結構體無須動態分發。此外,可以通過將類或類成員聲明為final或private,以及打開全模塊優化(參見第6章)來減少動態分發的使用。