Ruby 两段元编程代码的疑问

runup · 2015年12月24日 · 最后由 runup 回复于 2015年12月26日 · 2890 次阅读

问题 1:代码中的 obj.method_one 被去掉之后,执行该代码,会显示 undefied method 错误,那么 obj.method_one 这段代码的作用是什么,是向这个类说明,我已经定义了一个方法么?是否出现方法中嵌套方法都需要先用实例对象调用了嵌套外面的方法之后才能调用里面的方法?代码出自元编程 104 页。

class MyClass
  def method_one
    def method_two
      puts "hello!"
    end
  end
end

obj = MyClass.new
obj.method_one
obj.method_two

问题 2:在 Load.instance_eval{ @time_class = FakeTime }中直接用 FakeTime 这个类给实例变量@time_class赋值,我的理解里面只有类中的实例方法通过实例变量的调用才能运行,返回该方法的值。在元编程第一版中的 page101 中有这么一句话,定义 ruby 类实际上是在运行一段普通的代码,那么执行@time_class = FakeTime 是否就是可以理解为在类中定义类方法,调用该类的话,类方法直接被执行,并且返回该类方法的值,但是在类中的实例方法只有实例化对象调用了之后才能执行,并返回实例方法中的返回值。是否可以这么理解?代码出自元编程 109 页。

class Load
  def initialize(book)
    @book = book
    @time = Load.time_class.now
  end

  def self.time_class
    @time_class || Time
  end
end

class FakeTime
  def self.now
    'Mon apr .....'
  end
end

require 'test/unit'

class TestLoan < Test::Unit::TestCase
  def test_conversion_to_string
    Load.instance_eval{ @time_class = FakeTime }
    loan = Loan.new( "war and peace")
    assert_equal "Mon apr .....", load.to_s
  end
end

问题二,其实是在定义类实例变量而已。

调用方法时 Ruby 做两件事

  • 找到这个方法
  • 执行这个方法

注意,在 MyClass 没有实例化对象之前,MyClass 的实例方法,只有 method_one,这点可以用以下语句证实

MyClass.instance_methods(false)

obj 是 MyClass 对象 1、若直接执行 obj.method_two,首先向右一步,在 MyClass 中查找 method_two,没查到,然后在 MyClass 的祖先链上面查找,也没查到,所以最终在 method_missing 方法中会报undefined method `method_two'

2、若先执行 obj.method_one,在 MyClass 中查找到 method_one,于是在执行 method_one 的方法调用的同时,定义 MyClass 的实例方法 method_two,此时 method_two 才会加入到 MyClass 的实例方法列表中,所以,这时再 new 一个 MyClass 的对象, 可以用新的对象直接调用 method_two 实例方法,而不用再执行 method_one 方法

obj2 = MyClass.new
obj2.method_two

以上会直接输出 “hello!”

第一个应该很少有人会这么用,我觉得可以理解为 define_method。因为这时使用 def 这个方法,recieve 还是 MyClass 这个类,所以等同于在该类下面直接定义一个方法。

如果在 Ruby 中,定义一个类相当于是执行一段代码,那么在类中定义实例方法其实也可以理解为执行一段代码。具体的实现应该要看 CRuby 的源码还有底层的书,我猜 Ruby 解释器在类中扫描到 def 方法时,会将该方法写入符号表,至少让执行器知道该类包含这个方法,以及此方法的内存地址。但是这个过程中,方法内部的代码并没有被执行,所以必须要手工调用一次 method_one,method_two 才会被定义。

#2 楼 @luolinae86 懂了,method_one 在被调用的时候同时定义了 method_one 方法,这样子 method_one 方法才能成为实例方法,感谢。

#3 楼 @adamshen “定义一个类就是执行一段代码” 这句话如何理解? 如下代码:

class Demo
  def instance_method
    puts "this is the instance method"
  end

  def self.class_method
    puts "this is the class method"
  end

  class_method
end

obj = Demo.new
obj.instance_method

在类中定义了实例方法和类方法,类中对类方法调用之后,执行成功。在类外部生成实例化对象,实例化对象调用实例方法,执行成功。 所以定义一个类就是执行一段代码是否可以理解为:在类的作用域内,执行代码,比如执行 class_method 这个类作用域中的方法,而对于类作用域外的代码,则不执行,对于标示了 def 的代码块,这个代码块其实不在类的作用域内,因为作用域分割符号为 class,module,def。这样的理解是否合适?

#1 楼 @qinfanpeng 能不能帮我看看上面的理解是否合适?感谢。

感觉没啥问题。

class Demo
  puts 'Hello world'
end

# 上面的代码会输出 Hello world

#1 楼 @qinfanpeng 问题二涉及到类实例变量? 我觉得这里没有涉及到类实例变量吧,见如下代码:

class Demo
  def self.class_method
    puts "this is the class method"
  end
end

Demo

在终端执行下面代码,是没有返回值的,那为何@time_class = FakeTime 可以赋值成功?

#8 楼 @runup 这里的关键是 Load.instance_eval。instance_eval 打开并修改当前类。@qinfangpeng。是这样理解么?

#9 楼 @torubylist 原问题已经补全代码,添加了 Load 类,我明白是在修改当前类,我是想问直接调用 FakeTime 这个类给当类实例变量赋值,但是直接调用的 FakeTime 是否有返回值?

11 楼 已删除

#2 楼 @luolinae86 在原编程书中 115 页找到一段代码,原理是不是也是同样的?代码如下:

class Book
  def lend_to(user)
    puts "lending to #{user}"
  end

  def self.deprecated(old_method, new_method)
    define_method(old_method) do |xargs, &block|
      warn "Waring: #{old_method} () is deprecated, Use #{new_method}"
      send(new_method, xargs, &block)
    end
  end

  deprecated(:LEND_TO_USER, :lend_to)
end

b = Book.new
b.LEND_TO_USER("bill")

#10 楼 @runup 你可以看这一章后面一节,4.2 小测验。不知道我的理解对不对。可以讨论下。这里说是给匿名类重命名的事。那非匿名类难道不能重命名么?同样可以的。我刚才在 irb 里面实践了一下是可以的。不知道这样理解对不对。@qinfanpeng@Rei

#13 楼 @torubylist 是第一版的 4.2 节么?

#14 楼 @runup 我看的是第二版。

#15 楼 @torubylist 第二版页码多少?

#16 楼 @runup 就在你这个程序的下面就是了。4.2 小测验:Taboo 类,我自己打印的。没有页码。

#13 楼 @torubylist 你这里说的匿名类指的是?

第一个问题 @luolinae86 说得挺清楚了,第二个问题的话,instance_eval 不是打开类,class_eval 才是打开类,instance_eval 是改变闭包中 self 的指向,把 self 指向消息接受者,你的例子里就是 Load,所以可以访问 Load 的实例变量 @time_class。而由于闭包可以捕获声明环境中的变量所以可以捕获 FakeTime,然后把 FakeTime 这个对象(也是类)赋值给 @time_class ,就 OK 了。

#20 楼 @sheepy1 他的意思是 FakeTime 这个对象的返回值是空。是这个意思么?@runup

#19 楼 @qinfanpeng c = Class.new(Array), 这就是匿名类。

#22 楼 @torubylist 第一次在 Ruby 里听说匿名类:

# 本质上说,这两种方式没啥区别
c = Class.new(Array)  

class c < Array

end

匿名类就是没有名字的类 😄

c = Class.new(Array)
c.name  #=>nil

第一个问题@luolinae86 他解释得很清楚了。第二问题,我觉得就是将一个类赋值给了一个实例变量(@time_class)而已。

第二个问题终于搞明白了,之前出现困惑的原因是因为我认为下面的代码:

@time_class = FakeTime

是通过 FakeTime 的返回值对@time_class进行赋值,因为 FakeTime 没有返回值,因此这样的赋值没有意义。但实际上真正的理解是类 FakeTime 直接赋值给实例变量@time_class,然后通过后面的@time_class.time_class.now,也就是 FakeTime.time_class.now 获取固定的时间值。

#25 楼 @liukai 明白了,谢谢

需要 登录 后方可回复, 如果你还没有账号请 注册新账号