Ex1. CocoaPods 中的 Ruby 特性之 Mix-in

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

如今,咱們給動物加上加上 RunnableFlyable 的功能,當咱們定義好這兩個描述能力的類,使用多繼承來描述每一個動物便可: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。那麼多繼承到底有什麼樣的問題呢?

在「松本行弘的程序世界」中,做者列舉了如下三點:

  1. 結構複雜化 - 若是是單繼承,一個類的父類是什麼,父類的父類是什麼,這些十分明確。由於單一繼承關係中,是一棵多叉樹結構。可是若是是多重繼承,繼承關係就十分複雜了。
  2. 優先順序模糊 - 假若有 A、C 同時繼承了基類,B 繼承了 A,而後 D 又同時繼承了 B 和 C,因此此時 D 繼承父類方法的順序應該是 D ⇒ B ⇒ A ⇒ C 仍是 D ⇒ B ⇒ C ⇒ A?又或者是其餘順序?如此優先順序十分模糊。
  3. 功能衝突 - 由於多重繼承有多個父類,因此當不一樣的父類中更有相同的方法時就會產生衝突。若是 B 和 C 同時又有相同的方法時,D 繼承的是哪一個實現就會產生衝突。

可是單一繼承又會有上文提到的缺陷。那麼咱們要如何平衡這個問題呢?其實方法很簡單,引入「受限制的多重繼承」特性便可。

拋開各個編程語言只討論面向對象思想,繼承關係在最終的表現結果上每每只有兩種含義:

  • 類有哪些方法 - 子類對於父類屬性描述的繼承;
  • 類的方法具體的實現是什麼樣的 - 子類對於父類方法實現邏輯的繼承;

在靜態語言中,這二者的區別更加的明顯,幾乎都是以關鍵字來作含義的隔離。例如 Java 中用 extend 實現單一繼承,使用 implements 來間接實現多重繼承;在 Swift 中,咱們也會使用 classprotocol 來區別兩種場景。

可是僅僅是區分了上述兩種繼承含義,這並不完美。Java 中用 implements 來實現多重繼承,雖然避免來功能的衝突性,可是 implements 是沒法共享的(這裏的前提是 Java 8 以前,在 Java 8 以後,interface 可使用 default 關鍵字增長默認實現),若是想實現共享就要用組合模式來調用別的 class 從而實現共通功能,十分麻煩

在如此背景下咱們來介紹 Ruby 中的 Mix-in 模式。

Mix-in 以及其意義

上面說到,咱們須要提供一種「受限制的多重繼承」的特殊的繼承方式,咱們將這種繼承簡化稱呼爲規格繼承。簡單來說,規格繼承就是指不但會將方法名繼承下去,而且能夠定義某些繼承方法的默認實現。

若是你是 Swift 玩家,那麼會馬上想到,這就是 protocolextension 默認實現。 是的,Mix-in 就是這個含義。在 Ruby 中 Mix-in 的語法是經過 moduleinclude 方式來實現的,咱們來舉個例子說明一下。

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 也爲其定義了 moduleinclude 關鍵字的語法糖。從 Mix-in 模式裏,咱們能夠了解多繼承的一些缺點,而且說明了 Mix-in 是爲了解決什麼問題。最後稍微引入了 Duck Typing 這種程序設計思想,有興趣的朋友能夠自行研究。

知識點問題梳理

這裏羅列了一些問題用來考察你是否已經掌握了這篇文章,若是沒有建議你加入 收藏 再次閱讀:

  1. 什麼是 Mix-in,它與多繼承是什麼關係?
  2. Mix-in 在大多數編程語言中是如何落地的?分別說說 Java、Ruby、Swift?
  3. 多繼承的缺點有什麼?
  4. 在 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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索