Module的使用方法

模块提供了一种组织常量,类和方法的手段。你可以使用模块来提供一个名字空间以避免名字冲突,你也可以使用模块来提供 mixin 的功能。

名字空间

当程序代码越来越多,工程越来越大,开发者不可避免的会将一些常用的代码以库或别的形式重用。一般情况下,我们可以用类来组织代码,但有时候使用类组织代码并不是十分合适。这样在一个大工程中,就有可能发生名字冲突。

 

例如,开发者 A 在文件 a.rb 中写了如下代码,用来输出自己代码文件的版本信息,

           def print_version

       # …

       end

 

同一个项目中的另一个开发者 B 在文件 b.rb 中用同样的方法来实现同样的功能,

           def print_version

       # …

       end

 

第三个开发者 C 需要使用 a.rb 和 b.rb 中的一些方法,这样,当他使用 print_version 方法时,就产生了名字冲突,到底他调用的是哪一个文件中的 print_version 方法呢?

 

我们可以使用模块机制来解决这样的名字冲突。定义一个模块相当于定义了一个名字空间,名字空间内的元素在全局空间并不直接可见。

 

开发者 A 定义模块 A_FILE ,

       module A_FILE

           def print_version

           # …

           end

       end

开发者 B 定义模块 A_FILE ,

       module B_FILE

           def print_version

           # …

           end

       end

这样对于开发者 C ,可以这样使用 print_version

       require ‘A’

       require ‘B’

A.  print_version

B.  print_version

 

类和模块的区别是,模块不能生成实例,而类不能被 include 。

Mix-in 的意思是混合插入、糅合,就像在冰淇淋中混合多种配料可以做成美味的混合冰淇淋一样,在类中混合插入各种模块就可以添加相应的功能。模块还有另一个重要的作用,可以使用模块来实现多继承,可以在类中包含模块,这样模块中的所有方法和类中其他方法一样可以使用。

Matz 坚信滥用多重继承会导致继承关系的混乱,因此 Ruby 中不允许使用多重继承。同时为充分发挥继承功能的优势, Ruby 支持两种继承关系: 1. 使用 is-a 语句的继承; 2. 使用 Mix-in 来共享并继承模块中的功能。

 

    module Debug

       Define print_info

           print "Class: #{self.class.name} Object ID: #{self.id}"

       end

    end

 

    class A

       include Debug

       #...

    end

 

    class B

       include Debug

       #...

    end

 

    obj1 = A.new

    obj2 = B.new

    obj1.print_info

    obj2.print_info

 

通过这样的手段我们可以实现多继承的功能, 这样的模块我们称为 mixin 。

 

在Ruby 中,Object ,Class 和Module 是三个特殊的类。

Class 是一个Module ,而Module 是一个Object ,所以说Class 是一个Object ,因此,所有的数据都是Object 。

 

  使用 mixin

   Comparable

Comparable mixin 提供了比较的功能。要使用 Comparable mixin 必须提供 <=> 方法, <=> 的返回值为 -1 , 0 , +1 用来表示元素之间的小于,等于,大于的关系。

class Person

  include Comparable

  attr :age

  def <=>(aPerson)

     @age <=> aPerson.age

  end

  def initialize(name, gender, age)

     @name = name

     @gender = gender

     @age = age

  end

end

 

aPerson = Person.new("Tom", "male", 18)

bPerson = Person.new("Mike", "male", 10)

cPerson = Person.new("Henry", "male", 40)

puts aPerson > bPerson

puts aPerson < bPerson

puts aPerson >= bPerson

puts aPerson <= bPerson

puts aPerson == bPerson

puts aPerson.between?(bPerson, cPerson)

 

执行结果为 :

true

false

true

false

false

true

 

   Enumerable

Enumerable mixin 提供了遍历,查找和排序的功能。 要使用 Enumerable mixin 必须提供 each 方法,标准做法是在 each 方法内对每一个元素使用 yield 操作。如果使用了 Enumerable mixin 中的 max , min ,或 sort ,那么还必须提供 <=> 方法,用来实现元素之间的比较关系。

 

以下是一个使用 Enumerable mixin 的例子, IntegerFinder 是一个查找字符串中整数的类。

class IntegerFinder

       include Enumerable

 

        def initialize(aString)

           @string = aString

       end

 

def each

           @string.scan(/[1-9]\d*/) { |integer| yield integer }

       end

end

 

aDigitFinder = IntegerFinder.new("This is 123, 234, 98 and 10")

aDigitFinder.collect {|i| print i, " "}

aArray = aDigitFinder.find_all {|i|  i.to_i > 50 }

puts "\n", aArray.to_s

 

执行结果为:

    123 234 98 10

12323498

 

Enumerable mixin 中含有许多与集合遍历查找相关的方法,许多标准类也使用了 Enumerable mixin ,借助 Enumerable mixin 中的方法可以方便的实现一些强大的功能,请看以下一些例子:

 

# 察看数组中的所有单词的长度是否大于 4

%w{ ant bear cat}.all? {|word| word.length >= 4}   #=> false

 

    # 返回 range 中所有不符合条件的元素

(1..10).reject {|i|  i % 3 == 0 } #=> [1, 2, 4, 5, 7, 8, 10]

 

# 求 5 到 10 的和

#inject 方法第一次会把 Range 中的前两个元素作为参数传递给 sum 和 n ,

# 以后每次会把 sum 设置为块计算后的返回值。

(5..10).inject {|sum, n| sum + n }                    #=> 45

 

# 找出数组中最长的单词

longest = %w{ cat sheep bear }.inject do |memo,word|

    memo.length > word.length ? memo : word

end

longest                                         #=> "sheep"

 

  Singleton

在设计模式中, Singleton 技术通常称为单件,是一种常见的设计技术,它保证在系统的某个类在任一时刻最多只有一个实例在运行。你可以参见设计模式这本书获得有关单件技术更详细的信息。在 Ruby 中,使用 Singleton Mix-in ,你可以很容易的实现单件类。

 

       在单件类中不能使用 new 方法,因为在单件类中 new 方法的属性是私有的。需要使用 instance 方法得到类的实例对象。

 

require 'singleton'

 

class SingletonClassTest

        attr_accessor :data

        include Singleton

end

 

# a = SingletonClassTest.new # 错误, new 方法为私有方法

a = SingletonClassTest.instance

b = SingletonClassTest.instance

 

puts a.inspect

puts b.inspect

 

a.data = 8

puts b.data

 

执行结果为:

#<SingletonClassTest:0x2ab9360>

#<SingletonClassTest:0x2ab9360>

8

 

可以看到, a 和 b 其实指向同一个对象。

 

  Require, load 和 include

在 Ruby 中,可以使用 load 和 require 来包含另一个文件。每次运行到 load 时, load 后的文件会被载入并执行。

4.times do |i|

        File.open("temp.rb","w") do |f|

           f.puts "def test"

           f.puts "#{i}"

            f.puts "end"

        end

        load "temp.rb"

        puts test

end

执行结果为:

0

1

2

3

在上面的小程序里 load "temp.rb" 执行了 4 次,每一次 temp.rb 文件都不同,所以 test 方法执行后的结果也不同。

使用 Load 方法的这种特性可以实现一些强大的功能,例如:

l  可以用来处理配置文件,在程序运行期间配置文件可以被动态改变。

l  可以用来实现程序的无缝升级,在升级时你不需要重启程序,只要将所需要的代码重新 load 。

 

Require 和 load 不同,它只加载文件一次,即在第一次执行到 require 时载入,以后碰到 require 同一文件时自动忽略。已经被加载的文件保存在 $” 中。另外, require 还可以用来加载二进制格式的库。 Require 可以使用绝对路径或相对路径,如果使用了相对路径,那么系统会在 $: 变量包含的目录中搜寻。 Require 和 load 的另一个不同是当包含文件是 Ruby 代码时, require 可以不加后缀名。 Require 将当前所有加载的文件保存在 $" 变量中。

 

注意,在当前版本中, $” 是一个数组,保存着使用 require 已经加载过的文件。但如果 require 使用不同的路径去包含同一个文件,这个文件就有可能被加载多次。

    File.open("temp.rb","w") do |f|

       f.puts "def test"

       f.puts "1"

       f.puts "end"

end

require "temp"

puts test

 

File.open("temp.rb","w") do |f|

       f.puts "def test"

       f.puts "2"

       f.puts "end"

end

 

require "./temp"

puts test

 

执行结果为:

1

2

 

这样就违背了 require 只加载一次的初衷,一些人认为这是一个 bug ,这个问题在 Ruby 的后续版本中可能被修复。所以,不要使用不同的路径去加载同一个文件。

 

require, load,include 都是 Kernel 模块中的方法,他们的区别如下:

l  require , load 用于包含文件, include 则用于包含的模块。

l  require 加载一次, load 可加载多次。

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值