将实例方法委托给类方法

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

  def self.is_spherical?
    true
  end
end
这让我做到了
Foo.colour # "green"
但不是
my_foo = Foo.new
my_foo.colour # Error!
尽管
my_foo
显然是绿色的。 显然,我可以定义一个实例方法
colour
,它调用
self.class.colour
,但如果我有许多这样的基本特征,那就很难实现。 我也大概可以通过定义
method_missing
尝试任何丢失的方法的类做到这一点,但我不清楚这是否是我应该做的或丑陋的黑客攻击,或如何做到这一点安全(尤其是因为我确实下Rails中的ActiveRecord,我理解它使用method_missing做了一些聪明的乐趣。 你会推荐什么?     
已邀请:
您可以定义一个passthrough工具:
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)
我一般不喜欢使用
eval
,但这应该是非常安全的。     
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
    
如果它是纯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"
    
你可以使用一个模块:
module FooProperties
  def colour ; "green" ; end
  def is_spherical? ; true ; end
end

class Foo
  extend FooProperties
  include FooProperties
end
有点难看,但比使用ѭ7更好。我会尝试将其他选项放在其他答案中......     
从设计的角度来看,我认为,即使所有
Foos
,颜色和球形的答案都是一样的吗?是
Foo
实例的属性,因此应该定义为实例方法而不是类方法。 但是我可以看到一些你想要这种行为的情况,例如:如果你的系统中有
Bars
,所有这些都是蓝色的,你会在你的代码中的某个地方通过一个类,并想知道在你上课之前调用
new
之前实例的颜色。 另外,你是正确的,ActiveRecord确实广泛使用
method_missing
,例如对于动态查找器,所以如果你沿着那条路走下去,你需要确保你的method_missing从超类中调用一个,如果它确定方法名称不是它可以自己处理的那个。     
我认为最好的方法是使用Dwemthy的数组方法。 我要查阅并填写细节,但这是骨架 编辑:耶!工作!
class Object
  # class where singleton methods for an object are stored
  def metaclass
    class<<self;self;end
  end
  def metaclass_eval &block
    metaclass.instance_eval &block
  end
end
module Defaults
  def self.included(klass, defaults = [])
    klass.metaclass_eval do
      define_method(:add_default) do |attr_name|
        # first, define getters and setters for the instances
        # i.e <class>.new.<attr_name> and <class>.new.<attr_name>=
        attr_accessor attr_name

        # open the class's class
        metaclass_eval do
          # now define our getter and setters for the class
          # i.e. <class>.<attr_name> and <class>.<attr_name>=
          attr_accessor attr_name
        end

        # add to our list of defaults
        defaults << attr_name
      end
      define_method(:inherited) do |subclass|
        # make sure any defaults added to the child are stored with the child
        # not with the parent
        Defaults.included( subclass, defaults.dup )
        defaults.each do |attr_name|
          # copy the parent's current default values
          subclass.instance_variable_set "@#{attr_name}", self.send(attr_name)
        end
      end
    end
    klass.class_eval do
      # define an initialize method that grabs the defaults from the class to 
      # set up the initial values for those attributes
      define_method(:initialize) do
        defaults.each do |attr_name|
          instance_variable_set "@#{attr_name}", self.class.send(attr_name)
        end
      end
    end
  end
end
class Foo
  include Defaults

  add_default :color
  # you can use the setter
  # (without `self.` it would think `color` was a local variable, 
  # not an instance method)
  self.color = "green"

  add_default :is_spherical
  # or the class instance variable directly
  @is_spherical = true
end

Foo.color #=> "green"
foo1 = Foo.new

Foo.color = "blue"
Foo.color #=> "blue"
foo2 = Foo.new

foo1.color #=> "green"
foo2.color #=> "blue"

class Bar < Foo
  add_defaults :texture
  @texture = "rough"

  # be sure to call the original initialize when overwriting it
  alias :load_defaults :initialize
  def initialize
    load_defaults
    @color = += " (default value)"
  end
end

Bar.color #=> "blue"
Bar.texture #=> "rough"
Bar.new.color #=> "blue (default value)"

Bar.color = "red"
Bar.color #=> "red"
Foo.color #=> "blue"
    
你也可以这样做:
def self.color your_args; your_expression end

define_method :color, &method(:color)
    
这听起来像是一个警察,但实际上很少需要这样做,当你可以轻松地调用Foo.color。例外情况是您有许多定义了颜色方法的类。 @var可能是几个类中的一个,并且您想要显示颜色。 在这种情况下,我会问你自己在哪里使用这种方法 - 在课堂上或在模特身上?它几乎总是一个或另一个,并且使它成为一个实例方法没有任何问题,即使它在所有实例中都是相同的。 在极少数情况下,您希望方法“可调用”两者,您可以执行@ var.class.color(不创建特殊方法)或创建一个特殊方法,如下所示: def颜色     self.class.color   结束 我肯定会避免使用catch-all(method_missing)解决方案,因为它可以帮助您真正考虑每种方法的用法,以及它是属于类还是实例级别。     

要回复问题请先登录注册