Ruby Quick Start (3) -- Module, Mix-in

《Ruby语言入门教程v1.0》笔记(3)



第六章 模块、命名空间、Mix-in

1. Ruby中的模块

在程序中,相关的、不相关的代码的组合,叫作模块。一般情况下,我们总是把功能相关的代码放在一个模块里。

把功能相关的程序代码放在一个模块里, 体现了模块的第一个作用: 可以被其它程序代码重复使用。

数学中常用的函数, Ruby 中的 Math 模块都提供了。 每个使用 Math 模块的程序员无须再重复编写这些常用的函数与常数。

puts Math.sqrt(2)    # 1.4142135623731
puts Math::PI    # 3.14159265358979

定义模块用 module...end

模块与类非常相似,但是:

  • 模块不可以有实例对象;
  • 模块不可以有子类。

2. Ruby中的命名空间

如果你觉得 Ruby 标准包里的 Math 模块提供的 sqrt 方法不好,不能够设置迭代区间和精度,你重写了一个 sqrt 方法。你的同事在他的程序里需要调用你的 sqrt 方法,也要调用标准 Math 模块提供的 sqrt 方法,怎么办呢?

模块的第二个作用:提供了一个命名空间(namespace) ,防止命名冲突。

module Me
  def sqrt(num, rx = 1, e = 1e - 10)
    num *= 1.0
    (num - rx * rx).abs < e ? rx : sqrt(num, (num / rx + rx) / 2, e)
  end
end
include Math
puts sqrt(293)    # 17.1172427686237
# puts sqrt(293, 5, 0.01)  错误,Math模块中的sqrt不存在这种调用方法
include Me
puts sqrt(293)    # 17.1172427686237
puts sqrt(293, 5, 0.01)    # 17.1172429172153

也可不用include,而通过模块名来访问模块方法:

module Me
  def sqrt(num, rx = 1, e = 1e - 10)
    num *= 1.0
    (num - rx * rx).abs < e ? rx : sqrt(num, (num / rx + rx) / 2, e)
  end
end
module Me2
  def Me2.sqrt(*num)    # 这里参数的星号表示可以接受数组参数
    "This is text sqrt. "
  end
  PI = 3.14
end
puts Math.sqrt(1.23)    # 1.10905365064094
puts Math::PI           # 3.14159265358979
puts Me2.sqrt(55, 66, 77, 88, 99)    # This is text sqrt.
puts Me2::PI    # 3.14
include Me
puts sqrt(456, 7, 0.01)    # 21.3541565188558


3. Ruby中的糅和(Mix-in) 与多重继承

Ruby 本身是单继承,不是通过接口实现多重继承。但是通过 Mix-in 模块,可以实现多重继承的优点。

模块的第三个作用:实现了类似多重继承的功能

我们有一个 Student 类,有着 Person 类的属性和方法,还会做数学题——求平方根。已经有了 Me 模块,只要 Mix-inStudent 类里就可以了。

module Me
  def sqrt(num, rx = 1, e = 1e - 10)
    num *= 1.0
    (num - rx*rx).abs <e ?  rx : sqrt(num, (num/rx + rx)/2, e)
  end
end
class Person
  def talk
    puts "I'm talking."
  end
end
class Student < Person
  include Me
end
aStudent = Student.new
aStudent.talk    # I'm talking.
puts aStudent.sqrt(20.7, 3.3)    # 4.54972526643248

通过“ < 父类名 ” ,一个子类可以得到父类的属性和方法;

通过“ include 模块名 ” ,一个子类可以得到某个模块的常量和方法;

类不能被 include

include 方法相对应的,还有一个 extend 方法。如果并不是 Student 类的每个对象都会求平方根,只有某一个学生会,如何办到呢?

module Me
  def sqrt(num, rx=1, e=1e-10)
    num*=1.0
    (num - rx*rx).abs <e ? rx : sqrt(num, (num/rx + rx)/2, e)
  end
end
class Student
end
aStudent = Student.new
aStudent.extend(Me)    # 为Student类的一个实例aStudent包含Me模块
puts aStudent.sqrt(93.1, 25)    # 9.64883412646315

include 方法为一个类的所有对象包含某个模块;extend 方法为一个类的某个对象包含某个模块。



4. require 和 load

上面的程序中先写了 Me 模块,然后 include Me 模块,实现了 Mix-in 功能,但是,这样没能做到代码复用

我们将 Me 模块和 Person 类分别写在不同的文件(Me.rbPerson.rb)中,这时候 Student 类如何使用 Me 模块和 Person 类呢?这里要用到 require 方法。

require "Me"
require "Person"
class Student < Person
  include Me
end
aStudent = Student.new
aStudent.talk    # I'm talking.
puts aStudent.sqrt(77,2)    # 8.77496438739435

requireload 用于包含文件;includeextend 则用于包含模块。

require 加载文件一次,load 加载文件多次。

require 加载文件时可以不加后缀名,load 加载文件时必须加后缀名。

require 一般情况下用于加载库文件,而 load 用于加载配置文件。

利用 load 多次加载文件的特性,可以用来实现程序的无缝升级和系统的热部署。程序功能改变了,你只需要重新 load 一次,其它代码与它再次交互的时候,这个程序实际上已经不是原来的程序了。