Ruby 手册 | 12 - Ruby 语言的动态特性

208 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情

四、动态定义方法

define_method 和 def 功能类似,不同的是 def 是在代码编写时定义方法的关键字,而 def_method 则是在运行时定义方法的关键字。define_method 是模块的私有方法,无法在外部调用,虽然 module_eval 也可以动态定义方法,但是 define_method 是专门用来定义方法的,而 module_eval 还可以用来执行其他的代码。

class Bus

  def self.new_method(name, &block)
    define_method(name, &block)
  end
end

Bus.new_method(:my_new_method){puts "这是运行时定义的方法"}
bus = Bus.new
bus.my_new_method # 这是运行时定义的方法

上述代码中定义了一个 Bus 类以及一个类方法 new_method,而 new_method 方法成为 defined_method 可以在外部低啊用的接口,随后为 Bus 动态创建了一个实例方法。其中 &block 表示引用一个代码块。

五、动态删除定义

Ruby 的动态性还体现在所有定义的东西都可以被动态的删除,删除操作具有破坏性,使用一定要谨慎,可能会引发程序的崩溃。

取消方法定义的关键字是 undef,Module 类中也提供了 remove_method、undef_method 和 remove_const 方法用于删除方法和常数。

remove_method 和 undef_method 方法的区别在于 remove_method 只会删除当前的方法的定义,而 undef_method 则会比较彻底,连来自父类的方法也会被删除。

def test_func
  puts "test_func 被调用"
end

undef test_func

class ZuluClass
  def method_a
    puts "method_a 被调用"
  end

  def method_b
    puts "method_b 被调用"
  end
end

class SubZuluClass < ZuluClass
  def method_a
    puts "method_a 被调用"
  end

  def method_b
    puts "method_b 被调用"
  end

  remove_method :method_a
  undef_method :method_b

end

subZulu = SubZuluClass.new
subZulu.method_a
subZulu.method_b

执行上述方法,输出结果如下:

method_a 被调用
Traceback (most recent call last):
/ex5.rb:33:in `<main>': undefined method `method_b' for #<SubZuluClass:0x00007fbf18864ed8> (NoMethodError)

remove_method 和 remove_const 都是私有方法,只能在类或者模块内部使用。

六、const_missing 和 method_missing 方法

当调用一个不存在的常量或者方法的时候,通常 Ruby 会报错 NoMethodError,如果使用 const_missing 和 method_missing 这可以自定义响应调用的常量和方法不存在的响应。

class Module
  def const_missing(name)
    puts "常量 #{name} 未定义"
  end

  def method_missing(name, *args)
    puts "方法 #{name} 未定义"
  end
end

puts String.sqrt
puts String :: Unknown_Const

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

方法 sqrt 未定义

常量 Unknown_Const 未定义


const_missing 和 method_missing 还可以动态实现无穷多个有规则的方法。

class Student
  attr_accessor :name, :sex, :age, :grade

  def initialize(_name, _sex, _age, _grade)
    self.name = _name
    self.sex = _sex
    self.age = _age
    self.grade = _grade
  end

  def to_s
    self.name
  end
end

class School < Array

  def find_student(by, value)
    self.find_all{|s| s.send(by) == value}
  end

  def add_student(student)
    self << student
  end

  def method_missing(name, argument)
    match = /^find_student_by_([a-z]+)$/.match(name.to_s)
    if match
      find_student match[1], argument

    else
      raise NoMethodError
    end
  end
end

school = School.new
school.add_student(Student.new("阿三", "M", 13, "八年级"))
school.add_student(Student.new("阿四", "F", 14, "九年级"))
school.add_student(Student.new("阿五", "M", 15, "十年级"))
school.add_student(Student.new("阿六", "F", 16, "十一年级"))

puts "查询名为 阿三 的 学生"
puts school.find_student_by_name("阿三")

puts "查询性别为 M 的 学生"
puts school.find_student_by_sex("M")

puts "查询年龄为 15 的 学生"
puts school.find_student_by_age(15)

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

查询名为 阿三 的 学生
阿三
查询性别为 M 的 学生
阿三
阿五
查询年龄为 15 的 学生
阿五

代码中并没有定义多个维度信息查询学生的方法,而是使用 method_missing 截获方法的调用然后使用 send 方法动态的获得属性并进行查找,实现了所有属性的查找方法。如果 Student 类再添加新的属性,如果身高体重等,不需要对 School 类做任何更改,School 类即可获得根据身高体重等属性查询学生的方法。