将实例方法委托给类方法

Cho*_*ett 11 ruby class-method instance-method

在Ruby中,假设我有一个类Foo允许我对我的大量Foos进行编目.所有Foos都是绿色和球形的,这是一个基本的自然法则,所以我定义了类方法如下:

class Foo
  def self.colour
    "green"
  end

  def self.is_spherical?
    true
  end
end
Run Code Online (Sandbox Code Playgroud)

这让我做到了

Foo.colour # "green"
Run Code Online (Sandbox Code Playgroud)

但不是

my_foo = Foo.new
my_foo.colour # Error!
Run Code Online (Sandbox Code Playgroud)

尽管事实上my_foo是明显的绿色.

显然,我可以定义一个colour调用的实例方法self.class.colour,但是如果我有很多这样的基本特征,那就很难实现.

我也可以通过定义method_missing为任何缺失的方法尝试类来做到这一点,但我不清楚这是我应该做的事情还是丑陋的黑客,或者如何安全地进行(特别是因为我实际上在ActiveRecord下在Rails中,我理解用method_missing做一些Clever Fun Stuff).

你会推荐什么?

Way*_*rad 23

Ruby附带的Forwardable模块可以很好地完成这项工作:

#!/usr/bin/ruby1.8

require 'forwardable'

class Foo

  extend Forwardable

  def self.color
    "green"
  end

  def_delegator self, :color

  def self.is_spherical?
    true
  end

  def_delegator self, :is_spherical?

end

p Foo.color                # "green"
p Foo.is_spherical?        # true
p Foo.new.color            # "green"
p Foo.new.is_spherical?    # true
Run Code Online (Sandbox Code Playgroud)

  • 请注意,如果你给`def_delegator`一个第一个参数的符号,它将在稍后评估.因此,如果您使用`FooChild`继承`Foo`,并且您希望FooChild的实例委托给`FooChild`而不是`Foo`,请使用`def delegator:self,:colour`来保持`self`不被评估为`Foo `.它确实似乎是一个eval:`def_delegator:'put'hi'; self"`将具有相同的效果,但将打印到标准输出. (5认同)
  • 关于以前的注释,你想要委托`:'self.class'`作为`:self`委托给实例,而不是被引用的类. (3认同)

Vad*_*rov 11

如果它是普通的Ruby,那么使用Forwardable就是正确的答案

如果它是Rails,我会使用委托,例如

class Foo
  delegate :colour, to: :class

  def self.colour
    "green"
  end
end

irb(main):012:0> my_foo = Foo.new
=> #<Foo:0x007f9913110d60>
irb(main):013:0> my_foo.colour
=> "green"
Run Code Online (Sandbox Code Playgroud)

  • 如果您在“ActiveSupport::Concern”关注点内进行委托,请将“delegate”调用放在“included”和“class_methods”块之外 (2认同)

Aid*_*lly 1

您可以定义一个直通设施:

module Passthrough
  def passthrough(*methods)
    methods.each do |method|
      ## make sure the argument is the right type.
      raise ArgumentError if ! method.is_a?(Symbol)
      method_str = method.to_s
      self.class_eval("def #{method_str}(*args) ; self.class.#{method_str}(*args) ; end")
    end
  end
end

class Foo
  extend Passthrough

  def self::colour ; "green" ; end
  def self::is_spherical? ; true ; end
  passthrough :colour, :is_spherical?
end

f = Foo.new
puts(f.colour)
puts(Foo.colour)
Run Code Online (Sandbox Code Playgroud)

我通常不喜欢使用eval,但在这里它应该非常安全。