【问题标题】:Class method vs constant in Ruby/RailsRuby/Rails 中的类方法与常量
【发布时间】:2013-04-09 13:53:30
【问题描述】:

我正在实现一个包含集合硬编码下拉列表的表单,我想知道最好的解决方案是什么,我知道下面公开的两种方式都可以工作,但我还是做了如下操作:

class Example

  # Options for Example.
  self.options
    [ 'Yes', 'No', 'Not sure' ]
  end
end

Example.options 调用,但我知道也可以执行以下操作:

class Example

  # Options for Example.
  OPTIONS = [ 'Yes', 'No', 'Not sure' ]
end

将使用Example::OPTIONS 调用。

问题是,这些是好方法还是根本不重要?

【问题讨论】:

  • 普遍接受的方法是后者,因为带有 cmets 的大写字母是一种向用户说明这是一个常量的文档形式
  • 与可能的答案无关,我建议在字符串上使用符号,除非您的代码特别需要字符串。因此[:yes, :no, :not_sure]
  • @CharlesCaldwell 或更短的%i[yes no not_sure]
  • 在我看来,这个问题与Constants or class variables in ruby? 有关,它更好地解释了选项背后的心态。常量保存在源代码中定义的常量值,变量保存一个变化的值,一个方法应该一些事情(除了返回一个硬编码的数组)。

标签: ruby-on-rails ruby


【解决方案1】:

后者更好。如果是方法,每次调用都会创建一个新的数组和新的字符串,很浪费资源。

【讨论】:

  • 太棒了,+10,如果可以的话 ;-)
  • +1 同样调用Example.methods 会告诉options 是一个方法,当你知道它是一个类常量时。这是编写一致代码的另一个技巧。
  • 虽然在方法中缓存数组很容易...@options ||= [...]
  • 写了另一个答案,因为我不同意常量是更好的方法。
  • 方法提供的灵活性在这里比节省一分钱更重要(恕我直言,显然)
【解决方案2】:

TL;DR:这取决于。这些值是要在课堂外使用的吗?他们能变得有活力吗?他们可以改变子类吗?

正如@sawa 所写,该方法(以这种方式编写)的缺点是每次都会创建一个新数组和字符串。

更好的写法是:

class Example
  def self.options
    @options ||= ['Yes', 'No', 'Not sure']
  end
end

数组存储在实例变量@options中,避免每次都创建一个新数组。

这样写,方法很像常量。

一个关键的区别是如果 Example 是子类,那么细化 options 方法会比常量 OPTIONS 更自然:

class Parent < Example
  def self.options
    @options ||= [*super, 'Extra']
  end
end

用常量做类似的事情是困难的。想象一下,您的选项列表用于类方法中,如下所示:

class Example
  OPTIONS = ['Yes', 'No', 'Not sure']

  def self.foo(arg)
     puts "Available options:",
          self::OPTIONS  # The self:: is needed here
     # ...
  end
end

class Parent < Example
  OPTIONS = [*superclass::OPTIONS, 'Extra']
end

关于常量的棘手之处在于self::OPTIONSOPTIONS 并不总是相同,而self.optionsoptions 是相同的。通常在不指定范围的情况下使用常量(例如OPTIONS 而不是self::OPTIONS),在这种情况下继承根本不起作用。

请注意,该方法使您有机会在不更改 API 的情况下使结果动态化(即根据其他情况返回不同的结果)。

最后说明:我建议在您的阵列上调用 freeze,以避免任何人修改它。

【讨论】:

  • 你仍然可以用常量做类似的事情:Example::Options = ["Yes", "No", "Not sure"]; Parent::Options = [*Example::Options, "Extra"]。唯一的区别是您必须显式编写父类而不是使用super
  • @sawa:确实可以(并且您可以使用superclass 而不是显式编写父类),但访问常量可能会很棘手。编辑了我的答案。
  • 问题中的相关思路是直接调用Example::OPTIONSParent::OPTIONSOPTIONS等常量。我不明白你为什么要定义 self.foo 来打电话给他们。
  • 这是一个使用options/OPTIONS的类方法的例子。
【解决方案3】:

我通常做的是混合使用上述技术:

class Player
  JURISDICTIONS = %i(de uk ru)

  def self.jurisdictions
    JURISDICTIONS
  end
end

它有几个优点:

  • 它提供了一个干净的接口,封装了一个常量(你调用Player.jurisdictions而不是Player::JURISDICTIONS)。
  • 稍后可以通过更改方法来添加其他逻辑。
  • 该方法可以在测试中存根。

恕我直言,性能在这里并不重要。

更新: 常量可以使用private_constant方法隐藏(http://ruby-doc.org/core-2.3.0/Module.html#method-i-private_constant

【讨论】:

  • 你有一个错字,应该是self。
【解决方案4】:

为了进一步完善 Artur 的建议,我将使用类变量来隐藏常量的可见性。

class Player
  @@jurisdictions = %i(de uk ru)

  def self.jurisdictions
    @@jurisdictions
  end
end

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-12
  • 1970-01-01
  • 2011-12-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多