CocoaPods 是使用 Ruby 這門腳本語言實現的工具。Ruby 有不少優質的特性被 CocoaPods 所利用,爲了在後續的源碼閱讀中不會被這些用法阻塞,因此在這個系列中,會給出一些 CocoaPods 的番外篇,來介紹 Ruby 及其當中的一些語言思想。web
面向對象中的繼承
構造一個動物類
Mix-in
在有些編程書中也被翻譯成「混入模式」。根據字面意思,Mix-in
就是經過「混入」額外的功能,從而簡化多層次的複雜繼承關係。編程
咱們舉一個例子來講明。假如咱們設計了一個 Animal
類,而且要實現一下四種動物的定義:微信
-
Dog
- 狗 -
Bat
- 蝙蝠 -
Parrot
- 鸚鵡 -
Ostrich
- 鴕鳥
若是按照哺乳動物和鳥類動物來歸類,則能夠設計出如下類的層級關係:編程語言

但若是按照「能跑」和「能飛」來歸類,則應該設計如下的類層次:編輯器

可是在咱們的代碼中又想擁有以前哺乳動物和鳥類動物也增長進來,那麼咱們就要設計更加複雜的層次:工具
-
動物 -
能飛(BFly) -
能跑(BRun) -
能飛(MFly) -
能跑(MRun) -
哺乳動物(Mammal) -
鳥類動物(Bird)

若是繼續增長分類手段,例如「寵物類」和「非寵物類」,則類的數量就會以指數級別增加,難以維護且可讀性極差。post
那麼咱們應該用什麼方式來解決這個問題呢?測試
使用多繼承解決
首先,咱們能夠按照哺乳動物和鳥類動物來進行繼承關係的描述。因爲 Python 支持多繼承語法,因此咱們下面用 Python 來描述一下使用多繼承來描述上述場景:flex
class Animal(object):
pass
# 動物大類
class Mammal(Animal):
pass
class Bird(Animal):
pass
如今,咱們給動物加上加上 Runnable
和 Flyable
的功能,當咱們定義好這兩個描述能力的類,使用多繼承來描述每一個動物便可:spa
# 描述能力的類
class Runnable(object):
def run(self):
print('Running...')
class Flyable(object):
def fly(self):
print('Flying...')
# 每一個動物
class Dog(Mammal, Runnable):
pass
class Bat(Mammal, Flyable):
pass
經過多重繼承,一個子類能夠得到多個父類的全部功能,而且其繼承的關係樹以下:

多繼承的問題
Ruby 這門語言是不支持多繼承的,取而代之是使用 Mix-in。那麼多繼承到底有什麼樣的問題呢?
在「松本行弘的程序世界」中,做者列舉了如下三點:
-
結構複雜化 - 若是是單繼承,一個類的父類是什麼,父類的父類是什麼,這些十分明確。由於單一繼承關係中,是一棵多叉樹結構。可是若是是多重繼承,繼承關係就十分複雜了。 -
優先順序模糊 - 假若有 A、C 同時繼承了基類,B 繼承了 A,而後 D 又同時繼承了 B 和 C,因此此時 D 繼承父類方法的順序應該是 D ⇒ B ⇒ A ⇒ C 仍是 D ⇒ B ⇒ C ⇒ A?又或者是其餘順序?如此優先順序十分模糊。 -
功能衝突 - 由於多重繼承有多個父類,因此當不一樣的父類中更有相同的方法時就會產生衝突。若是 B 和 C 同時又有相同的方法時,D 繼承的是哪一個實現就會產生衝突。
可是單一繼承又會有上文提到的缺陷。那麼咱們要如何平衡這個問題呢?其實方法很簡單,引入「受限制的多重繼承」特性便可。

拋開各個編程語言只討論面向對象思想,繼承關係在最終的表現結果上每每只有兩種含義:
-
類有哪些方法 - 子類對於父類屬性描述的繼承; -
類的方法具體的實現是什麼樣的 - 子類對於父類方法實現邏輯的繼承;
在靜態語言中,這二者的區別更加的明顯,幾乎都是以關鍵字來作含義的隔離。例如 Java 中用 extend
實現單一繼承,使用 implements
來間接實現多重繼承;在 Swift 中,咱們也會使用 class
和 protocol
來區別兩種場景。
可是僅僅是區分了上述兩種繼承含義,這並不完美。Java 中用 implements
來實現多重繼承,雖然避免來功能的衝突性,可是 implements
是沒法共享的(這裏的前提是 Java 8 以前,在 Java 8 以後,interface
可使用 default
關鍵字增長默認實現),若是想實現共享就要用組合模式來調用別的 class
從而實現共通功能,十分麻煩。
在如此背景下咱們來介紹 Ruby 中的 Mix-in 模式。
Mix-in 以及其意義
上面說到,咱們須要提供一種「受限制的多重繼承」的特殊的繼承方式,咱們將這種繼承簡化稱呼爲規格繼承。簡單來說,規格繼承就是指不但會將方法名繼承下去,而且能夠定義某些繼承方法的默認實現。
若是你是 Swift 玩家,那麼會馬上想到,這就是 protocol
的 extension
默認實現。 是的,Mix-in 就是這個含義。在 Ruby 中 Mix-in 的語法是經過 module
和 include
方式來實現的,咱們來舉個例子說明一下。
class Animal
end
class Mammal < Animal
end
class Bird < Animal
end
module RunMixin
def run
puts "I can run"
end
end
module FlyMinxin
def fly
puts "I can fly"
end
end
class Dog < Mammal
include RunMixin
end
class Parrot < Bird
include FlyMinxin
end
dog = Dog.new
dog.run # "I can run"
parrot = Parrot.new
parrot.fly # "I can fly"
經過這種方式,咱們將 Run 和 Fly 的能力抽象成了兩個 module
,當描述對應 class
時須要的時候,就使用 Min-in 模式將其 include
就能夠得到對應能力。

那麼若是咱們將 Mammal 哺乳動物和 Bird 鳥類動物封裝成 Mix-in ,並將 Fly 和 Run 作成一級 class
這樣能夠嗎?在實現上是能夠的,可是並不推薦。
這裏簡單說一下緣由:由於 Mix-in 指望是一個行爲的集合,而且這個行爲能夠添加到任意的 class
中。從某種程度上來講,繼承描述的是「它是什麼」,而 Mix-in 描述的是「它能作什麼」。從這一點出發,Mix-in 的設計是單一職責的,而且 Mix-in 實際上對宿主類一無所知,也有一種狀況是隻要宿主類有某個屬性,就能夠加入 Mix-in。
Mix-in in CocoaPods
在 CocoaPods 的 config.rb
中,其中有不少關於 Pods 的配置字段、CocoaPods 的一些關鍵目錄,而且還持有一些單例的 Manager。
在「總體把握 CocoaPods 核心組件」一文中,咱們介紹來 pod install
的過程都是在 installer.rb
中完成的,而這個 Installer
的 class ,中的定義是這樣的:
module Pod
class Installer
autoload :Analyzer, 'cocoapods/installer/analyzer'
autoload :InstallationOptions, 'cocoapods/installer/installation_options'
autoload :PostInstallHooksContext, 'cocoapods/installer/post_install_hooks_context'
#...
include Config::Mixin
#...
end
end
咱們能夠看到 Installer
拿入了 Config::Mixin
這個 module
。而這個 config
屬性其實就是 CocoaPods 中的一些全局配置變量和一些配置字段。
例如咱們在 write_lockfiles
方法中來查看 config
的用法:
def write_lockfiles
# 獲取 lockfile 數據
@lockfile = generate_lockfile
# 將 lockfile 數據寫入 Podfile.lock
UI.message "- Writing Lockfile in #{UI.path config.lockfile_path}" do
@lockfile.write_to_disk(config.lockfile_path)
end
# 將 lockfile 數據寫入 manifest.lock
UI.message "- Writing Manifest in #{UI.path sandbox.manifest_path}" do
@lockfile.write_to_disk(sandbox.manifest_path)
end
end
這裏面的 config
就是經過 Mix-in 方式拿進來的變量,意在更加容易的去訪問那些全局變量和配置字段。
咱們在寫入文件的位置下一個斷點,能夠清楚的打印 lockfile_path
;固然我也可使用 config
打印其餘的重要信息:
config.lockfile_path # lockfile 的 Path
config.installation_root # 執行 install 的目錄
config.podfile # 解析後的 Podfile 實例
config.sandbox # 當前工程的 sandbox 目錄
# ...
具體的屬性能夠查看 config.rb
中的代碼來肯定。既然 Config
已經變成一個 Mix-in ,在 CocoaPods 中引入的地方天然就會不少了:

簡單說一句 Duck Typing 思想
下面是一點對於編程思想的思考,能夠不看。
最後咱們來講一個高級的東西(其實只是名字很高級),那就是 Duck Typing,在不少書中也被稱做鴨子類型。
Duck Typing 描述的是這麼一個思想:若是一個事物不是鴨子(Duck),若是它走起路來像一隻鴨子,叫起來也像一隻鴨子,即咱們能夠說它從表現來看像一隻鴨子,那麼咱們就能夠認爲它是一隻鴨子。
這種思想應用到編程中是什麼樣的呢?簡而言之,一個約定要求必須實現某些功能,而某個類實現類這個功能,就能夠把這個類看成約定的具體實現來使用。
咱們從這個角度來看,其實 Mix-in 這種模式就更加區別於多繼承,而是一種 Duck Typing 思想的語法糖。咱們不用將一層層 interface
所有繼承,而是聲明即實現。
Duck Typing 是一種設計語言的思想,若是你想了解的更多,也能夠從 Duck Test 這種測試方式開始瞭解。

總結
本文從 CocoaPods 中使用到的 Ruby 語法特性提及,講述了在 Ruby 當中,爲了解決多繼承中的問題從而引入的 Mix-in 模式,而且 Ruby 也爲其定義了 module
和 include
關鍵字的語法糖。從 Mix-in 模式裏,咱們能夠了解多繼承的一些缺點,而且說明了 Mix-in 是爲了解決什麼問題。最後稍微引入了 Duck Typing 這種程序設計思想,有興趣的朋友能夠自行研究。
知識點問題梳理
這裏羅列了一些問題用來考察你是否已經掌握了這篇文章,若是沒有建議你加入 收藏 再次閱讀:
-
什麼是 Mix-in,它與多繼承是什麼關係? -
Mix-in 在大多數編程語言中是如何落地的?分別說說 Java、Ruby、Swift? -
多繼承的缺點有什麼? -
在 CocoaPods 中是如何使用 Mix-in 特性的?
引用
-
《松本行弘的程序世界》- https://book.douban.com/subject/6756090/ -
《Ruby基礎教程 第5版》- https://book.douban.com/subject/27166893/ -
「廖 雪峯 Python 教程 - 多重 繼承」- https://www.liaoxuefeng.com/wiki/1016959663602400/1017502939956896
本文分享自微信公衆號 - 一瓜技術(tech_gua)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。