【问题标题】:In Ruby is it possible to define constants in a subclass from a superclass?在 Ruby 中,是否可以在超类的子类中定义常量?
【发布时间】:2020-09-23 09:24:44
【问题描述】:

假设我有一个由许多子类扩展的 Ruby 类。我希望这些子类中的每一个都有一个名为FRIENDLY_NAME 的常量。显然,我可以编辑每个类并添加常量。但是,如果它没有在子类中指定,有没有办法在超类的子类中定义一个常量?

例如,当FRIENDLY_NAME 尚未定义时,也许我可以将每个子类的“FRIENDLY_NAME”设置为子类的类名解模化和下划线。

【问题讨论】:

  • FWIW,我可以在超类中使用 self.inherited 中的 child.const_set(:FRIENDLY_NAME, 'foo') 之类的东西,但这给了我一个警告,即我正在重新定义一个常量,即使我还没有定义它。 “警告:已初始化常量”
  • 更正:警告来自我确实设置FRIENDLY_NAME常量的类。问题是超类中的self.extended 在子类中定义常量之前执行,如果常量不存在,我将在那里设置常量。因此,当我试图设置它的真实值时,警告实际上来自子类。 ://
  • 这很有趣,但不是常量的好例子。你实际上是在跳过箍来尝试让它表现出方法继承的工作方式,所以我会使用非常简单的类级方法,几乎​​所有 Ruby 开发人员都能理解,并且不需要鲜为人知的特殊知识像TracePoint这样的Ruby功能:gist.github.com/meagar/0f8379ec706949594e76f9c91c7d6ec1
  • 老实说,你是对的,类方法正是我要做的。不过,就像你说的,这很有趣。

标签: ruby class constants


【解决方案1】:

关于TracePoint!

为了扩展我原来的问题,我有一个超类,我想多次扩展:

class BaseClass
end

我希望该类的所有扩展版本都具有FRIENDLY_NAME 常量。当在子类中明确指定时,这将是常量的值。例如:

class Subclass1 < BaseClass
  FRIENDLY_NAME = 'Bob seems like a friendly name!'
end

因此:

pry(main)> Subclass1::FRIENDLY_NAME
=> "Bob seems like a friendly name!"

但是,也许我有另一个没有定义常量的子类。例如:

class Subclass2 < BaseClass
end

这就是我在没有常量的情况下得到的(显然):

pry(main)> Subclass2::FRIENDLY_NAME
NameError: uninitialized constant Subclass2::FRIENDLY_NAME
from /bundle/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/active_support.rb:79:in `block in load_missing_constant'
Caused by NameError: uninitialized constant Subclass2::FRIENDLY_NAME
from /bundle/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/active_support.rb:60:in `block in load_missing_constant'

如何添加该常量?您可以将self.inherited 添加到超类。每次扩展超类时都会调用此方法。 Module 上还有一个名为 const_set 的方法,所以我可以像这样自动设置 child 上的常量:

class BaseClass
  def self.inherited(child)
    child.const_set(:FRIENDLY_NAME, child.name)
  end
end

这适用于Subclass2

pry(main)> Subclass2::FRIENDLY_NAME
=> "Subclass2"

但是,它会为 Subclass1 产生丑陋的警告

(pry):10: warning: already initialized constant Subclass1::FRIENDLY_NAME
(pry):3: warning: previous definition of FRIENDLY_NAME was here

最初我尝试使用const_defined? 方法检查FRIENDLY_NAME 常量是否已经定义。问题是,self.inherited 方法被调用之前在子类中定义常量。基本上,只要 Ruby 看到 &lt; BaseClass,它就会运行 self.inherited

最终我找到了this SO question about how to execute code after a class has been defined。答案是使用TracePoint

TracePoint 是一个允许我们在代码中处理事件的类。这些可能是在执行一行代码时、调用方法时、引发错误时等等。我们关心的是:end 事件,它在类或模块定义完成时发生。

知道了这一点,我们可以像这样更新我们的 self.inherited 方法:

class BaseClass
  def self.inherited(child)
    TracePoint.trace(:end) do |t|
      if child == t.self
        unless child.const_defined?(:FRIENDLY_NAME)
          child.const_set(:FRIENDLY_NAME, child.code)
        end
      end
    end
  end
end

现在,Subclass1Subclass2 都有一个 FRIENDLY_NAME 常量,但都不会产生警告!

【讨论】:

    猜你喜欢
    • 2011-06-09
    • 2014-10-25
    • 2012-03-27
    • 2019-09-20
    • 2022-01-05
    • 1970-01-01
    • 2015-03-21
    • 1970-01-01
    • 2017-11-02
    相关资源
    最近更新 更多