代码之家  ›  专栏  ›  技术社区  ›  Chowlett

将实例方法委托给类方法

  •  10
  • Chowlett  · 技术社区  · 14 年前

    在鲁比,假设我有一个班 Foo 允许我对我收集的大量FOO进行分类。所有的foo都是绿色和球形的,这是一个基本的自然法则,所以我定义了类方法如下:

    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下时,我理解这会使方法丢失,从而产生一些聪明有趣的事情)。

    你会推荐什么?

    8 回复  |  直到 10 年前
        1
  •  2
  •   Aidan Cully    14 年前

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

    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 但是这里应该很安全。

        2
  •  23
  •   Wayne Conrad    13 年前

    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
    
        3
  •  8
  •   Vadym Tyemirov    10 年前

    如果是纯红宝石,则使用 Forwardable 是正确的答案吗

    如果是铁轨的话我会用的 delegate ,例如

    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"
    
        4
  •  4
  •   Aidan Cully    14 年前

    您可以使用一个模块:

    module FooProperties
      def colour ; "green" ; end
      def is_spherical? ; true ; end
    end
    
    class Foo
      extend FooProperties
      include FooProperties
    end
    

    有点难看,但比使用 method_missing . 我会尝试在其他答案中加入其他选项…

        5
  •  4
  •   mikej heading_to_tahiti    14 年前

    从设计的角度来看,我认为,即使所有人的答案都一样 Foos 颜色和球形?是的实例的属性 Foo 因此应该定义为实例方法而不是类方法。

    但是,我可以看到一些情况下你希望这种行为,例如当你 Bars 在您的系统中,所有这些都是蓝色的,并且您在代码中的某个地方通过了一个类,希望在调用之前知道实例的颜色。 new 在课堂上。

    而且,您正确地认为ActiveRecord确实广泛地利用了 method_missing 例如,对于动态查找器,因此如果您沿着该路径走下去,则需要确保您的方法丢失,如果它确定方法名不是它可以自己处理的方法名,则需要从超类中调用该方法。

        6
  •  3
  •   rampion    11 年前

    我认为最好的方法是 the Dwemthy's array method .

    我要查一下,再填一些细节,但这是骨架

    编辑 耶!工作!

    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"
    
        7
  •  2
  •   Alexey    13 年前

    您也可以这样做:

    def self.color your_args; your_expression end
    
    define_method :color, &method(:color)
    
        8
  •  1
  •   Jaime Bellmyer    14 年前

    这听起来有点像是在逃避,但实际上很少有必要这样做,当你可以轻松地调用foo.color时。例外情况是,如果有许多类定义了颜色方法。@var可能是几个类中的一个,您希望显示颜色而不管。

    在这种情况下,我会问你自己,你在哪里更多地使用这个方法——在课堂上,还是在模型上?它几乎总是一个或另一个,把它作为一个实例方法没有任何错误,即使它在所有实例中都是相同的。

    在极少数情况下,您希望两种方法都“可调用”,您可以执行@var.class.color(不创建特殊方法)或创建这样的特殊方法:

    DEF颜色 self.class.颜色 结束

    我肯定会避免catch all(方法丢失)解决方案,因为它可以让您真正考虑每个方法的用法,以及它是否属于类或实例级别。