简体   繁体   English

将实例方法委托给类方法

[英]Delegating instance methods to the class method

In Ruby, suppose I have a class Foo to allow me to catalogue my large collection of Foos. 在Ruby中,假设我有一个Foo类允许我对我的大量Foos进行编目。 It's a fundamental law of nature that all Foos are green and spherical, so I have defined class methods as follows: 所有Foos都是绿色和球形的,这是一个基本的自然法则,所以我定义了类方法如下:

class Foo
  def self.colour
    "green"
  end

  def self.is_spherical?
    true
  end
end

This lets me do 这让我做到了

Foo.colour # "green"

but not 但不是

my_foo = Foo.new
my_foo.colour # Error!

despite the fact that my_foo is plainly green. 尽管my_foo显然是绿色的。

Obviously, I could define an instance method colour which calls self.class.colour , but that gets unwieldy if I have many such fundamental characteristics. 显然,我可以定义一个调用self.class.colour的实例方法colour ,但如果我有许多这样的基本特征,那就变得难以处理。

I can also presumably do it by defining method_missing to try the class for any missing methods, but I'm unclear whether this is something I should be doing or an ugly hack, or how to do it safely (especially as I'm actually under ActiveRecord in Rails, which I understand does some Clever Fun Stuff with method_missing). 我也可以通过定义method_missing为任何缺失的方法尝试类来做到这一点,但我不清楚这是我应该做的事情还是丑陋的黑客,或者如何安全地做到这一点(特别是因为我实际上是在Rails中的ActiveRecord,我理解它使用method_missing做了一些聪明的有趣的东西)。

What would you recommend? 你会推荐什么?

The Forwardable module that comes with Ruby will do this nicely: 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

If it's plain Ruby then using Forwardable is the right answer 如果它是普通的Ruby,那么使用Forwardable就是正确的答案

In case it's Rails I would have used delegate , eg 如果它是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"

You could use a module: 你可以使用一个模块:

module FooProperties
  def colour ; "green" ; end
  def is_spherical? ; true ; end
end

class Foo
  extend FooProperties
  include FooProperties
end

A little ugly, but better than using method_missing . 有点难看,但比使用method_missing更好。 I'll try to put other options in other answers... 我会尝试将其他选项放在其他答案中......

From a design perspective, I would argue that, even though the answer is the same for all Foos , colour and spherical? 从设计的角度来看,我认为,尽管所有Foos ,颜色和球形的答案都是一样的吗? are properties of instances of Foo and as such should be defined as instance methods rather than class methods. Foo实例的属性,因此应该定义为实例方法而不是类方法。

I can however see some cases where you would want this behaviour eg when you have Bars in your system as well all of which are blue and you are passed a class somewhere in your code and would like to know what colour an instance will be before you call new on the class. 然而,我可以看到一些你想要这种行为的情况,例如当你的系统中有Bars并且所有这些都是蓝色的时候你会在你的代码中的某个地方传递一个类,并想知道你之前的实例是什么颜色在班上打电话给new

Also, you are correct that ActiveRecord does make extensive use of method_missing eg for dynamic finders so if you went down that route you would need to ensure that your method_missing called the one from the superclass if it determined that the method name was not one that it could handle itself. 另外,你是正确的,ActiveRecord确实广泛使用了method_missing例如对于动态查找器,所以如果你沿着那条路走下去,你需要确保你的method_missing从超类中调用一个,如果它确定方法名不是它的那个可以处理自己。

I think that the best way to do this would be to use the Dwemthy's array method . 我认为最好的方法是使用Dwemthy的数组方法

I'm going to look it up and fill in details, but here's the skeleton 我要查阅并填写细节,但这是骨架

EDIT : Yay! 编辑 :耶! Working! 工作!

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"

You could define a passthrough facility: 您可以定义直通设施:

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)

I don't generally like using eval , but it should be pretty safe, here. 我一般不喜欢使用eval ,但这应该是非常安全的。

You can also do this: 你也可以这样做:

def self.color your_args; your_expression end

define_method :color, &method(:color)

This is going to sound like a bit of a cop out, but in practice there's rarely a need to do this, when you can call Foo.color just as easily. 这听起来像是一个警察,但实际上很少需要这样做,当你可以轻松地调用Foo.color。 The exception is if you have many classes with color methods defined. 例外情况是如果您有许多定义了颜色方法的类。 @var might be one of several classes, and you want to display the color regardless. @var可能是几个类中的一个,你想要显示颜色。

When that's the case, I'd ask yourself where you're using the method more - on the class, or on the model? 在这种情况下,我会问你自己在哪个地方使用这个方法 - 在课堂上或在模特身上? It's almost always one or the other, and there's nothing wrong with making it an instance method even though it's expected to be the same across all instances. 它几乎总是一个或另一个,并且使它成为一个实例方法没有任何问题,即使它在所有实例中都是相同的。

In the rare event you want the method "callable" by both, you can either do @var.class.color (without creating a special method) or create a special method like so: 在极少数情况下,您希望方法“可调用”两者,您可以执行@ var.class.color(不创建特殊方法)或创建一个特殊方法,如下所示:

def color self.class.color end def color self.class.color end

I'd definitely avoid the catch-all (method_missing) solution, because it excuses you from really considering the usage of each method, and whether it belongs at the class or instance level. 我肯定会避免使用catch-all(method_missing)解决方案,因为它可以帮助您真正考虑每种方法的用法,以及它是属于类还是实例级别。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM