Ruby 成员变量添加类级别的 attr_accessor 的一个疑惑

wppurking · 2013年01月06日 · 最后由 wppurking 回复于 2013年01月06日 · 4967 次阅读

在阅读 REMOVING CONFIG.THREADSAFE! 的时候,发现下面一段代码:

class UsersController < ApplicationController
  @counter = 0

  class << self
    attr_accessor :counter
  end

  trap(:INFO) {
    $stderr.puts "Count: #{UsersController.counter}"
  }

  def index
    counter = self.class.counter # read
    sleep(0.1)
    counter += 1                 # update
    sleep(0.1)
    self.class.counter = counter # write

    @users = User.all

    respond_to do |format|
      format.html # index.html.erb
      format.json { render json: @users }
    end
  end
end

我对上面的代码有一些不理解。我们知道 Rails 为每一个 Action 都会创建一个新的 Controller 实例, 那么这段代码其实想表达的应该上为 UserController 添加一个 @@counter = 0 然后再并发请求发现 @@counter 的值会与请求数量不一样。

问题:

  1. 首先我们知道 class << self 中是定义的类方法,如果定义的是类方法为什么还会去访问一个 @counter ?
  2. 如果成功定义了类方法,那其操作的应该是一个 @@counter, 那 @counter =0 这个初始化的作用是干嘛?

attr_accessor 就是 读写 @counter

@counter 是 User 这个类对象的成员变量

  1. ”类方法“操作的依然是 @counter 无误。
  2. @couinter = 0 如果省掉,第一次读取时就会返回 nil, 在后面的 counter += 1 时会抛错。

#2 楼 @doitian 恩,@counter 是 UsersController 的成员变量,Rails 每一个请求 Action 创建一个新的 UsersController 实例,那成员变量也应该被清理,重新初始化对吧?

这里,很显然效果类似操作一个 @@counter 类变量了

#4 楼 @wppurking UsersController 的成员变量,不是它的实例的成员变量。UsersController 作为 Class 的一个实例,它也是个对象。虽然你每次创建一个实例,UsersController 这个对象又没改变。也就是每个实例里访问 self.class 都是相同的。

#3 楼 @5long 懂了,从 Java 语系过来,Ruby 中这里的 "类方法" 或者说 静态方法 有很大区别啊. 恩,现象如你所述。

#5 楼 @doitian 哈,一语惊醒 "UsersController 的成员变量和它实例的成员变量" 一个例子

class A
    @a = 1
    @b = 3

    class << self
        attr_reader :a, :b
    end

    attr_reader :a, :b

    def initialize
        @a = 2
    end
end

puts A.a # 1
puts A.new.a # 2
puts A.new.b #   [什么都没有]

这里的 self.class 每次相同,是不是意思为每一个 UsersController 实例,其实都有一个 UsersController Class 的实例引用着?

还有一个问题,在 ruby 中经常可以看到的 class << self 这种代码该如何理解呢?例如,是不是可以理解为这里的 class 是一个类成员变量?而 << 是方法?self end. 如果这里的 class 是关键字那该如何理解?

class << self 这种语法专门为了访问 eigenclass 而存在的,不需要想得太复杂。<< 的右侧可以是任意表达式,比如:

o = Object.new
class << o
  def foo
    'foo'
  end
end

o.foo # => 返回 'foo'
Object.new.foo # => 抛出 NoMethodError 异常.

简单理解是是为某个对象添加新的方法。在这个块里定义的所有方法,只会添加到这个对象里。你的例子里 A 是 Class 的一个实例对象,在 class << self 里定义的所有方法,只添加到 A 里,而不会影响到其它 Class 的实例。以 String 的一个实例对象为例

str = "abc"
class << str
  def mysize
    size
  end
end

str.mysize #=> 3
"abc".mysize #=> undefined method `mysize'

深入了解可以读读 Ruby meta programming,或者搜索 Ruby eigenclass 相关的资料

#7 楼 @5long #9 楼 @doitian 太感谢你们,这问题讨论起来比自己想老久有效多啦。看到 eigenclass 猜应该是涉及到 ruby 的元编程方面了,接下来需要花些时间详细阅读下

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