为什么我在这里可以像调用实例方法一样调用 class 方法?

Why am I able to call the class method as if it were an instance method here?

我正在查看这个例子:

class SQLObject
  def self.columns
    return @columns if @columns
    columns = DBConnection.execute2(<<-SQL).first
      SELECT
        "#{table_name}".*
      FROM
        "#{table_name}"
      LIMIT
        0
    SQL
    columns.map!(&:to_sym)
    @columns = columns
  end

  def self.table_name
    @table_name ||= self.name.underscore.pluralize
  end

  def insert
    column_symbols = self.class.columns.drop(1)
    column_names = column_symbols.map(&:to_s).join(", ")
    question_marks = (['?'] * column_symbols.count).join(", ")
    DBConnection.execute(<<-SQL, *attribute_values)
      INSERT INTO
        #{self.class.table_name} (#{column_names})
      VALUES
        (#{question_marks})
    SQL
    self.id = DBConnection.last_insert_row_id
  end
end

而且我很困惑为什么可以在"self.columns"方法中调用table_name方法,就好像它是一个实例方法一样。 "table_name" 方法不是 class 方法吗?因此,它不应该在 "self.columns" 方法中也被称为 "self.class.table_name" 吗?

当在抽象 class 中时,self 指的是正确的 class,而不是对象。这就是为什么您可以在不显式告诉 self

的情况下访问该方法的原因

简短回答:self 是 Ruby 中的隐式接收者。如果您将其遗漏,消息将发送至 self。换句话说,never 必须在消息发送中说 self.foo,因为 fooalwaysself.foo.

相同

长答案:Ruby 中没有“class 方法”这样的东西。你所谓的“class方法”实际上只不过是定义在一个对象上的单例方法,而这个对象恰好是一个实例的 class Class.

实际上,Ruby中也没有单例方法单例方法实际上只是一个常规的旧实例方法,它定义在单例class 一个对象。

[注意:这并不是说您不应该使用这些术语。说 "class method" 显然比 "instance method of the singleton class of an object of class Class" 更简单并且更能传达意图。另请注意,还有 Object#singleton_methods 等方法。 Ruby社区中显然存在这些术语,但重要的是要了解这些概念存在于Ruby 语言。它们是一种交流工具,而不是实际的 Ruby 概念。]

单例class 是与单个对象关联的class。每个对象都有 one 单例 class 并且该对象是 only 实例("singleton instance",因此得名)它的单例 class.

事实上,对象的继承层次总是从它的单例class开始,即class指针[=一个对象的181=]总是指向它的单例class,而单例class的superclass指针则指向class 创建了这个对象。你只是在继承链中看不到单例class,因为在计算继承链时像Object#class"skip over"单例class这样的方法。它,但是,总是在方法查找期间使用。

事实上,方法查找其实很简单(唯一的例外是Module#prepend,我将忽略这个解释):

  1. 取消引用接收者的 class 指针
  2. 如果 class 的方法 table 有一个消息名称的方法,调用它。停止。
  3. 如果不是,取消引用 class 的 superclass 指针
  4. 转到#2。
  5. 如果您到达 class 而没有超级 class,请使用消息 method_missing(original_message_name, ...), unless the message name is already method_missing, in which case raise a NoMethodError 重新启动算法。

所以,这一切都归结为两个问题:

  • 在定义方法时,你是在哪个class(或模块)中定义的?
  • 发送消息时,你要向哪个对象发送消息?

当您定义一个使用def foo.bar的方法时,Ruby将首先计算表达式foo然后定义一个方法bar 在结果对象的单例 class 上(definee)。如果您不提供 foo,即您只说 def bar,则 bar 定义在 , which is a little bit of a tricky concept. Normally, it is the closest lexically enclosing module definition. If there is no enclosing module definition, i.e. you are at the top-level, the default definee is Object and the method visibility is private.

当您使用foo.bar发送消息时,Ruby将首先计算表达式foo,然后发送消息bar 到结果对象( 接收器)。如果您不提供 foo,即如果您只说 bar,则 默认接收者 self.

这让我们想到下一个问题:

  • 什么是 self

在方法定义主体中,self 是导致调用此方法的消息发送的接收方。因此,如果您发送消息 foo.bar,那么在方法 bar 的定义中对 self 的任何引用都将在该方法的这一次执行期间评估为 foo .

在模块或 class 定义体中,self 是 class 或方法本身。 (这就是为什么使用 def self.bar 定义单例 class 的 单例方法 实例方法在模块或 class 定义体中工作。)

在一个块或 lambda 文字中,self 是词法捕获的,即它是 self 在块或 lambda 文字被写入的地方。 但是,有几个方法改变了 self 的计算方式,最显着的是 BasicObject#instance_eval, BasicObject#instance_exec, Module#module_eval, Module#module_exec, Module#class_eval, Module#class_exec,…方法系列。

如果您在代码中的任何一点始终知道这三个问题的答案(我在哪里定义方法,方法调用的接收者是什么,self 是什么),那么你已经基本理解了Ruby.

最重要的部分

所以,把它们放在一起:

在使用 def self.columns 定义方法时,我们处于 class 定义体中,因此 self 指的是 class 对象本身( SQLObject).

语法 def foo.barfoo 的单例 class 上定义了一个方法 bar。在这种情况下,您在 SQLObject 的单例 class 上定义方法 columns。这意味着对象 SQLObject 是宇宙中唯一将响应消息 columns.

的对象

这两个也适用于self.table_name的定义。

self.columns的方法体内部,self会动态引用接收者。

当你发送消息 columnsSQLObject 时,即你写 SQLObject.columns,然后在 SQLObject::columns 方法的主体内,接收者(即 self) 将被设置为 SQLObject。 (在这种情况下,接收者将始终是 SQLObject,因为这是一个单例方法 class。)

所以,在self.columns的方法体内,当你写消息发送table_name时,这是一个带有隐式接收者的消息发送。由于隐式接收者是 self,这相当于 self.table_nameself此时绑定到SQLObject,所以这相当于SQLObject.table_name。也就是说,消息被发送到SQLObject.

现在,根据我上面概述的算法,方法查找首先从 SQLObject 检索 class 指针。如前所述,class 指针始终指向单例 class。因此,我们在 SQLObject 的单例 class 中查看是否找到名为 table_name 的方法,我们找到了!

方法查找完成,一切正常。