Ruby 开发(五) - 模块(上)

135 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第12天,点击查看活动详情

一、模块的定义与使用

模块的定义

模块 相似都是由一组方法和常量组成,对于类来说模块没有实例,但是可以将模块的功能添加到类或者特定的对象中。

模块的定义与类相似,只是关键字不同,定义模块的关键字是 module,在类中可以定义模块,在模块中也可以定义类,还可以定义实例变量、实例方法、类变量、类方法和属性等

module 模块名
    # 模块体
end
module Zulu
  def yankee
    puts "这是模块 Zulu 中的方法成员"
  end

  def self.info
    puts "这是模块里面的类方法成员,可以直接通过模块名调用"
  end
end

Zulu.info
Zulu.yankee

执行上述代码,输出结果如下:

这是模块里面的类方法成员,可以直接通过模块名调用
Traceback (most recent call last):
/ex2.rb:14:in `<main>': undefined method `yankee' for Zulu:Module (NoMethodError)

在上述代码中首先定义了一个 Zulu 模块,在模块中定义了实例方法 yankee 和类方法 self.info,类方法可以通过模块名直接调用,但是通过模块名直接调用实例方法时却报错了,模块中的实例方法该如何调用呢?

模块是没有实例的,但是类有实例,因此可以将模块置于类中,通过实例化类来访问模块的实例成员,这种将模块混合在类中的操作也称为混合操作,需要使用 include 关键字来实现。

module Zulu
  def info
    puts "这是模块中的实例方法"
  end
end

class ZuluInclude
  include Zulu
end

zuluInclude = ZuluInclude.new
zuluInclude.info

执行上述代码,输出结果如下:

这是模块中的实例方法

使用模块实现命名空间

命名空间就是一种将程序库封装起来的方法,就像在各个程序中建立起的围墙,在同一个命名空间里面不允许出现相同的方法名。

module Xray
  class XrayCls
    def info
      puts "通过模块定义的命名空间 Xray 中的 info 方法"
    end
  end
end

module Whiskey
  class WhiskeyCls
    def info
      puts "通过模块定义的命名空间 Whiskey 中的 info 方法"
    end
  end
end

xray_obj = Xray::XrayCls.new
xray_obj.info

whiskey_obj = Whiskey::WhiskeyCls.new
whiskey_obj.info

执行上述代码,输出结果如下:

通过模块定义的命名空间 Xray 中的 info 方法
通过模块定义的命名空间 Whiskey 中的 info 方法

在上述代码中分别定义了 Xray 模块并在内部定义了 XrayCls 类以及实例方法 info,在 Whiskey 模块中定义了 WhiskeyCls 类以及实例方法 info,虽然方法名相同,但是在不同的模块中,也可以认为是在不同的命名空间中,所以不会报错,这也就是通过模块来实现了命名空间的功能。

二、BEGIN 块和 END 块

BEGIN 和 END

在一些 Java 或者 Python 的测试框架如 TestNG、JUNIT 和 unitest 以及 AOP 框架中,都可以实现前置通知、后置通知这样的功能,Ruby 中也可以通过 BEGIN 块和 END 块来实现类似前置通知和后置通知的功能

Ruby 中 BEGIN 块是注册初始化,并且先于该文件的任何语句来执行,END 块是注册结束,会在解释器释放文件的时候执行。

BEGIN {
  puts "第一"
}
BEGIN {
  puts "第二"
}
BEGIN {
  puts "第三"
}

puts "这里是中间内容"

END {
  puts "倒数第三"
}
END {
  puts "倒数第二"
}
END {
  puts "最后执行"
}

执行上述代码,输入内容如下:

第一
第二
第三
这里是中间内容
最后执行
倒数第二
倒数第三

当一个 Ruby 脚本中包含多个 BEGIN 代码块和 END 代码块以及正常的代码时,会首先执行 BEGIN 代码块中的内容,并且多个 BEGIN 代码块是按照文件中从上往下的顺序执行,接着会执行正常的代码,最后才会执行 END 代码块中的内容,有多个 END 代码块时,执行顺序按照该文件中从下往上的顺序执行,与多个 BEGIN 代码块的执行顺序相反。

at_exit

at_exit 块和 END 块类似,都是在程序执行结束后执行,但是 at_exit 的执行顺序与 END 的执行顺序在在文件中所处的位置刚好相反

BEGIN {
  puts "第一"
}
BEGIN {
  puts "第二"
}
BEGIN {
  puts "第三"
}

puts "这里是中间内容"

at_exit do
  puts "这是第一个 at_exit 块,位置处于 END 块上面,会晚于 END 块执行,多个 at_exit 块的执行顺序与位置相反"
end

at_exit do
  puts "这是第二个 at_exit 块,位置处于 END 块上面,会晚于 END 块执行,多个 at_exit 块的执行顺序与位置相反"
end

at_exit do
  puts "这是第三个 at_exit 块,位置处于 END 块上面,会晚于 END 块执行,多个 at_exit 块的执行顺序与位置相反"
end

END {
  puts "倒数第三"
}
END {
  puts "倒数第二"
}
END {
  puts "最后执行"
}

执行上述代码,输出结果如下:

第一
第二
第三
这里是中间内容
最后执行
倒数第二
倒数第三
这是第三个 at_exit 块,位置处于 END 块上面,会晚于 END 块执行,多个 at_exit 块的执行顺序与位置相反
这是第二个 at_exit 块,位置处于 END 块上面,会晚于 END 块执行,多个 at_exit 块的执行顺序与位置相反
这是第一个 at_exit 块,位置处于 END 块上面,会晚于 END 块执行,多个 at_exit 块的执行顺序与位置相反

at_exit 块在 END 块上面的时候,会晚于 END 块的执行

BEGIN {
  puts "第一"
}
BEGIN {
  puts "第二"
}
BEGIN {
  puts "第三"
}

puts "这里是中间内容"

END {
  puts "倒数第三"
}
END {
  puts "倒数第二"
}
END {
  puts "最后执行"
}

at_exit do
  puts "这是第一个 at_exit 块,位置处于 END 块下面,会早于 END 块执行,多个 at_exit 块的执行顺序与位置相反"
end

at_exit do
  puts "这是第二个 at_exit 块,位置处于 END 块下面,会早于 END 块执行,多个 at_exit 块的执行顺序与位置相反"
end

at_exit do
  puts "这是第三个 at_exit 块,位置处于 END 块下面,会早于 END 块执行,多个 at_exit 块的执行顺序与位置相反"
end

执行上述代码,输出结果如下:

第一
第二
第三
这里是中间内容
这是第三个 at_exit 块,位置处于 END 块下面,会早于 END 块执行,多个 at_exit 块的执行顺序与位置相反
这是第二个 at_exit 块,位置处于 END 块下面,会早于 END 块执行,多个 at_exit 块的执行顺序与位置相反
这是第一个 at_exit 块,位置处于 END 块下面,会早于 END 块执行,多个 at_exit 块的执行顺序与位置相反
最后执行
倒数第二
倒数第三

at_exit 块在 END 块下面的时候,会早于 END 块的执行