美文网首页
Ruby 模块(Mix-in)

Ruby 模块(Mix-in)

作者: changsanjiang | 来源:发表于2017-10-23 20:26 被阅读86次

模块是 Ruby的特色功能之一. 如果说类表现的是实物的实体(数据)及其行为(处理), 那么模块表现的就只是事物的行为部分. 模块与类有以下两点不同:

  • 模块不能拥有实例
  • 模块不能被继承

利用 Mix-in 扩展功能

Mix-in 就是将模块混合到类中. 在类的定义中, 使用 include, 模块中的方法, 常量就都能被类使用.
如下, 我们把 MyClass1 和 MyClass2共通的功能定义在 MyModule 中. 虽然有点类似于类的继承, 但 Mix-in 可以更加灵活地解决下面的问题.

  • 虽然两个类拥有相似的功能, 但是不希望把他们作为相同的种类(Class)来考虑
  • Ruby 不支持父类的多重继承, 无法继承多个类
module MyModule
  # 共通的方法
end

class MyClass1
  include MyModule
  # MyModule 中独有的方法
end

class MyClass2
  include MyModule
  # MyModule 中独有的方法
end

提供命名空间

所谓命名空间( namespace ), 就是对方法, 常量, 类等名称进行区分及管理的单位. 由于模块提供各自独立的命名空间, 因此 A 模块中的 foo 方法与 B 模块中的 foo 方法就会被程序认为两个不同的方法. 同样, A模块中的 FOO 常量与B模块中的 FOO 常量也是两个不同的常量.
我们使用模块名.方法名的形式调用模块中定义的方法, 这一的方法称为模块函数.

A.foo
B.foo

A.FOO
B.FOO

另外, 如果没有定义与模块内的方法, 常量等同名的名称, 那么引用时就可以省略模块名.


创建模块

我们使用 module 语句来创建模块. 语法与创建类时几乎相同, 模块名的首字母必须大写.

module 模块名
  模块定义
end

示例:


module HelloModule
    Version = '1.0'         # 定义常量
    
    def hello(name)         # 定义方法
        puts "Hello, #{name}."
    end
    
    module_function :hello  # 指定 hello 方法为模块函数, 为模块私有
    
end

p HelloModule::Version  #=> "1.0"
HelloModule.hello("San")    #=> Hello, San.

include HelloModule     # 包含模块
p Version               #=> "1.0"
hello("Jiang")          #=> Hello, Jiang.


常量

和类一样, 在模块中定义的常量可以通过模块名访问.

p HelloModule::Version    #=> "1.0"

方法的定义

和类一样, 我们也可以在 module 语句中定义方法.
然而, 如果只定义了方法, 虽然在模块内部与包含此模块的语句中都可以直接调用, 但却不能以"模块名.方法名"的形式调用. 如果希望把方法作为模块函数公开给外部使用, 就需要用到 module_function 方法. module_function 的参数是表示方法名的符号.

def hello(name)
  puts "Hello, #{name}."
end

module_function :hello

以"模块名.方法名"的形式调用时, 如果在方法中调用self(接收者), 就会获得该模块的对象.

module FooModule
  def foo
    p self
  end

  module_function :foo
end

FooModule.foo #=> FooModule

此外, 如果类 Mix-in 了模块, 就相当于为该类添加了实例方法. 在这种情况下, self 代表的就是被 Mix-in 的类的对象.
即使是相同的方法, 在不同的上下文调用时, 其含义也会不一样, 因此对于 Mix-in 的模块, 我们要注意根据实际情况判断是否使用模块函数功能. 一般不建议在定义为模块函数的方法中使用self.


Mix-in

我们来讨论一下Mix-in, 先看示例:

module M

    def meth
        "meth"
    end
    
end

class C

include M # 包含 M 模块

end

p C.new.meth #=> "meth"

类C 包含模块M 后, 模块M 中定义的方法就可以作为类C 的示例方法供程序调用.
另外, 如果想知道类是否包含某个模块, 可以使用 include? 方法.

p C.include?(M)    #=> true

类C 的实例在调用方法时, Ruby会按类C -> 模块M -> 类C的父类Object 这个顺序查找该方法, 并执行第一个找到的方法. 被包含的模块的作用就类似于虚拟的父类.
我们用 ancestors 方法和 superclass 方法调查类的继承关系.

p C.ancestors #=> [C, M, Object, Kernel, BasicObject]
p C.superclass #=> Object

通过 ancestors 可以获得继承关系的列表, 进而也可以看出, 被包含的模块M 也被认为是类C 的一个"祖先". 而 superclass 方法则直接返回类C 的父类.

备注: ancestors方法的返回值中的 Kernel 是Ruby内部的一个核心模块, Ruby 程序运行时所需的共通函数都封装在此模块中. 例如 p方法, raise方法 等都是由 Kernel 模块提供的模块函数.

虽然 Ruby采用的是不允许具有多个父类的单一继承模型, 但是通过利用 Mix-in, 就即可以保持单一继承的关系, 又可以同时让多个类共享功能.

在 Ruby标准类库中, Enumerable 模块就是利用 Mix-in 扩展功能的一个典型例子. 使用 each方法的类中包含 Enumerable 模块后, 就可以 each_with_index, collect等对元素进行排序处理的方法. Array, Hash, IO 类等都包含了 Enumerable 模块. 这些类虽然没有继承这样的血缘关系, 但是从可以使用 each 方法遍历元素这一点来看, 可以说他们都拥有了某种相似甚至相同的属性.

Enumerable 模块和各类的关系.png

单一继承的优点就是简单, 不会因为过多的继承而导致类之间的关系变得复杂. 但是另一个方面, 有时我们又会希望更加积极地重用已有的类, 或者把多个类的特性合并为更高级的类, 在那样的情况下, 灵活使用单一继承和 Mix-in, 既能使类结构简单易懂, 又能灵活地应对各种需求.


查找方法的规则

我们来了解一下使用 Mix-in 时方法的查找顺序.


同继承关系一样, 原类中已经定义了同名的方法时, 优先使用该方法.

module M

    def meth
        "M#meth"
    end

end

class C
    include M       # 包含M
    def meth
        "C#meth"
    end
end

p C.new.meth      #=> "C#meth"

在同一个类中包含多个模块时, 优先使用最后一个包含的模块

module M1

    def meth
        "M1#meth"
    end

end

module M2
    
    def meth
        "M2#meth"
    end
    
end

class C
    include M1       # 包含M1
    include M2       # 包含M2
end

p C.new.meth      #=> "M2#meth"


嵌套 include 时, 查找顺序也是线性的, 此时的关系如下所示:

module M1

    def meth
        "M1#meth"
    end

end

module M2
    
    def meth
        "M2#meth"
    end
    
end

module M3
    
    include M2      # 包含M2
    
end

class C
    include M1       # 包含M1
    include M3       # 包含M3
end

p C.ancestors   #=> [C, M3, M2, M1, Object, Kernel, BasicObject]

嵌套 include 时的关系.png

相同的模块被包含两次以上时, 第二次以后的会被省略.

module M1

    def meth
        "M1#meth"
    end

end

module M2
    
    def meth
        "M2#meth"
    end
    
end

class C
    include M1       # 包含M1
    include M2       # 包含M2
    include M1       # 包含M1
end

p C.ancestors   #=> [C, M2, M1, Object, Kernel, BasicObject]

extend 方法

include 可以帮助我们突破继承的限制, 通过模块扩展类的功能; 而 extend 则可以帮助我们跨过类, 直接通过模块扩展对象的功能.

module Edition
    def edition(n)
        "#{self} 第#{n}版"
    end
end


class MyStr < String
end

str = "Ruby基础教程 "
str.extend(Edition) # 将模块 Mix-in 进对象
p str.edition(5) #=> "Ruby基础教程  第5版"



MyStr.extend(Edition) # 所有的类本身都是 Class 类的对象. 
p MyStr.edition(44) #=> "Ruby基础教程  第44版", 请继续往下看. 

类与 Mix-in

在Ruby中, 所有类本身都是Class类的对象. 之前我们介绍过接收者为类本身的方法就是类方法. 也就是说, 类方法就是类对象的实例方法. 这样的方法有以下两种:

  • Class 类的实例方法
  • 类对象的单例方法

继承类后, 这些方法就会作为类方法被子类继承. 对子类定义单例方法, 实际上也就是定义新的类方法.

除了之前介绍的定义类方法语法外, 使用 extend 方法也同样能为类对象追加类方法.

module ClassMethods
    def cmethod
        "class method"
    end
end

module InstanceMethods
    def imethod
        "instance method"
    end
end

class MyClass
    
    # 使用 extend 方法定义类方法
    extend ClassMethods
    
    # 使用 include 定义实例方法
    include InstanceMethods
    
end

p MyClass.cmethod       #=> "class method"
p MyClass.new.imethod   #=> "instance method"


备注: 在 Ruby 中, 所有方法的执行, 都需要通过作为接收者的某个对象的调用. 换句话说, Ruby 的方法(包括单例方法)都一定属于某个类, 并且作为接收者的对象的实例方法被程序调用. 从这个角度来说, 人们只是为了便于识别接收者的类型, 才分别使用了"实例方法"和"类方法"这样的说法.

相关文章

  • Ruby 模块(Mix-in)

    模块是 Ruby的特色功能之一. 如果说类表现的是实物的实体(数据)及其行为(处理), 那么模块表现的就只是事物的...

  • Ruby中的Module

    Ruby中没有Java语言中的interface,Ruby只支持单继承。Ruby通过Mix-in提供对类的扩展,其...

  • module

    module(模块),主要两大功能:1、提供命名空间2、 Mix-in 扩展功能(模块混入)特性:1、不能拥有实例...

  • Ruby 模块

  • Ruby学习总结

    ruby的计算符合正常编程逻辑比如: Math是Ruby内建的数学模块。在Ruby中,模块提供了两种角色:一种角色...

  • Sass注释中文报错问题

    找到ruby的安装目录,里面也有sass模块,如这个路径: C:\Ruby\lib\ruby\gems\1.9.1...

  • Ruby 解析 json

    环境配置 在使用 Ruby 编码或解码 JSON 数据前,我们需要先安装 Ruby JSON 模块。在安装该模块前...

  • Ruby小技巧:Each with object

    Ruby Bits: Each with object Enumerable是Ruby世界的核心模块。如果熟悉了它...

  • Ruby边学边记(二)

    Ruby边学边记(二) Ruby模块 模块在Ruby里面有两个作用 把功能相同的方法放在同一个名称之下 方便调用方...

  • Ruby 语法 (模块)

    模块案例 与类不同的是,模块没有实例对象的概念,使用MyModule.new会报错 加载和混入模块(require...

网友评论

      本文标题:Ruby 模块(Mix-in)

      本文链接:https://www.haomeiwen.com/subject/rwvguxtx.html