“致命方塊”:多重繼承與協議

上節課我們提到了協議,但是只講了它的一種應用方式,這節課我們就來深入地了解一下這個用起來和 class 差不多的協議究竟有什麼高深奧義。

現在,我們要再一次回顧那個可恥的繼承樹:

類的繼承樹
類的繼承樹

這裡我們寫了武器……是用來進行攻擊和防守的。那麼,作為一個遊戲,武器的模型不能夠單單只用在這一個地方,不然的話開發的成本就太大了——我們要盡可能的榨乾代碼的價值。

我們與設計師溝通以後,設計師想了想,我們可以給遊戲的角色加入飾品欄——飾品欄本來就有的啦……但是我們可以把一部分武器作為飾品放進去——比如那些冷兵器的模型,縮小一些就好了,還能增加角色的各種屬性——比如大刀增加攻擊力,長劍就增加速度?

嗯,不錯的選擇,那麼,現在這些武器就需要增加這些功能了——該怎麼辦呢?

還好我們有繼承:

好,讓我們看看,怎麼在這個繼承樹上做文章——目前來看我們能想到的升級方法是

  • 給“武器”這個父類直接添加飾品的相關代碼,這樣所有的子類就都 OK 啦! ——不過,一個大砲的飾品?還是一個叫做“內力”的飾品?
  • 給每個要做成飾品的武器單獨添加方法! ——好吧,多態不復存在了,你看做飾品的團隊不干死你。
  • 把“飾品”做成一個抽象的方法,然後要能作為飾品的武器自己去實現,這樣就符合多態了,也不會出現叫做“內力”的飾品了,可是上百個能作為飾品的武器都要去實現一遍好像也有點太過分了!
  • 那麼,要不我們再來個抽像類,然後讓所有的子類去繼承一下?繼承兩個父類!

綜上來看,似乎第三個才是最省力的選擇,效率也是最高的,可是,這裡我們會遇到一個嚴重的 bug:

如果一個類可以同時成為兩個父類的子類——也就是它可以繼承多個父類,那麼誰去保證這兩個父類就一定沒有親緣關係呢?一旦它們祖上哪裡有了一點關係,那這就可能成了一個環!

我們把這個拓撲簡化,一個類繼承自兩個父類,而這兩個父類又同時是同一個類的子類——看吧,這裡出現了一個菱形方塊,最終最底層的子類繼承了最頂層父類成員兩遍!

再深入的我們這裡就不探討了,這就是所謂的“致命方塊”,相信我,你不會願意去處理這個問題的——所以,編譯器也不會允許這樣的事情出現。

其實任何子類都只能從一個類繼承而來。

那這個時候可能有人就要問了:我見過,即使默認項目模板的聲明里都是繼承了好幾個類的,比如說:

好吧,其實,NSApplicationDelegate 是這樣聲明的:

它是一個協議。

協議

好了,這一次,我們來重新認識一下協議——其實它蛋生的真正目的就是為了解決我們上文遇到的問題,而上節課之所以用到了協議,其實是一種曲線救國的辦法罷了。

總之,協議就是一個完全抽象的類——這樣的話就不會出現上文中的致命問題——因為子類要繼承了一個協議(這裡說“繼承”已經不合適,應該叫做“遵循”)就必須實現協議裡的所有抽象成員——這樣就避免了編譯器不能確定版本的問題。而且,還保留了多態的機制!

然後,為了使用上的體驗一致性,Swift 的繼承語法同樣也是協議的遵循語法,而且二者可以寫在一起用逗號分隔,看起來就像是普普通通的聲明。

但你要注意,這些逗號分隔的類型裡,只有一個能是類,其他都得是協議。

這樣,我們就可以讓需要做成飾品的武器子類直接去遵循飾品的協議即可!

另外

我們還要說一點,由於接口允許各種遵循——不再限定單一,而且它是從規則上要求完全抽象的,所以它可以跨繼承樹使用:

比如說我還有個“護甲”的繼承樹,那麼護甲中的一些模型也可以做成飾品,同樣也可以遵循“飾品”協議!

 

 

由...出版 R0uter

如非聲明,本人所著文章均為原創手打,轉載請註明本頁面鏈接和我的名字。

發表評論

您的電子郵件地址不會被公開. 必填字段標 *