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源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索