【问题标题】:Is there a way to create methods just for the instance of a Ruby class from inside that instance?有没有办法从该实例内部为 Ruby 类的实例创建方法?
【发布时间】:2010-06-11 23:53:56
【问题描述】:

让有class Example定义为:

class Example
  def initialize(test='hey')
    self.class.send(:define_method, :say_hello, lambda { test })
  end
end

在拨打Example.new; Example.new 时,我会收到warning: method redefined; discarding old say_hello。我得出结论,这一定是因为它在实际类中定义了一个方法(从语法来看,这是有道理的)。当然,如果Example 的多个实例在它们的方法中具有不同的值,那将证明是灾难性的。

有没有一种方法可以从实例内部为类的实例创建方法?

【问题讨论】:

    标签: ruby


    【解决方案1】:

    您需要获取对实例的单例类的引用,该类包含所有特定于实例的内容,并在其上定义方法。在 ruby​​ 1.8 中,它看起来有点乱。 (如果您找到更清洁的解决方案,请告诉我!)

    Ruby 1.8

    class Example
      def initialize(test='hey')
        singleton = class << self; self end
        singleton.send :define_method, :say_hello, lambda { test }
      end
    end
    

    然而,Ruby 1.9 提供了一种更简单的方法。

    Ruby 1.9

    class Example
      def initialize(test='hey')
        define_singleton_method :say_hello, lambda { test }
      end
    end
    

    【讨论】:

    • 我冒昧地编辑了答案以使用术语singleton class,因为它是正式名称。事实上,Ruby 1.9.2 引入了Object#singleton_class
    • @Marc:哦,太好了。还没有太多时间玩 1.9.2。不过,我很高兴有人最终确定了一个名字。
    【解决方案2】:

    从外部定义实例方法:

    example = Example.new
    def example.say_hello
      puts 'hello'
    end
    

    从内部:

    class Example
      def initialize(word='hey')
        @word = word
        def self.say_hello
          puts "example: #{@word}"
        end
      end
    end
    

    在 ruby​​ 2.5 上测试

    【讨论】:

      【解决方案3】:

      首先,一个小的风格提示:

      self.class.send(:define_method, :say_hello, lambda { test })
      

      您可以使用 Ruby 1.9 中的新 proc 字面量使它看起来更漂亮:

      self.class.send(:define_method, :say_hello, -> { test })
      

      但你不需要那个。 Ruby 有一个叫做 blocks 的东西,它基本上是一段代码,你可以将它作为参数传递给一个方法。事实上,您已经使用了块,因为lambda 只是一个将块作为参数并返回Proc 的方法。但是,define_method 已经 接受了一个块,无需将块传递给 lambda 将其转换为 Proc 并传递给 define_method 然后将其转换回成一个块:

      self.class.send(:define_method, :say_hello) { test }
      

      正如您已经注意到的,您在错误的类上定义了方法。您在Example 类上定义它,因为在initializeself 等实例方法内部是当前对象(即@mikej 示例中的ex1ex2),这意味着self.classex1 的班级,即Example。所以,你一遍又一遍地覆盖同一个方法。

      这会导致以下不良行为:

      ex1 = Example.new('ex1')
      ex2 = Example.new('ex2') # warning: method redefined; discarding old say_hello
      ex1.say_hello            # => ex2 # Huh?!?
      

      相反,如果你想要一个单例方法,你需要在单例类上定义它:

      (class << self; self end).send(:define_method, :say_hello) { test }
      

      这按预期工作:

      ex1 = Example.new('ex1')
      ex2 = Example.new('ex2')
      ex1.say_hello            # => ex1
      ex2.say_hello            # => ex2
      

      在 Ruby 1.9 中,有一个方法可以做到这一点:

      define_singleton_method(:say_hello) { test }
      

      现在,它按照您希望的方式工作,但这里有一个更高级别的问题:这不是 Ruby 代码。它是 Ruby 语法,但它不是 Ruby 代码,而是 Scheme。

      现在,Scheme 是一门出色的语言,用 Ruby 语法编写 Scheme 代码当然不是一件坏事。它打败了用 Ruby 语法编写 Java 或 PHP 代码,或者就像昨天 StackOverflow 问题中的情况一样,用 Ruby 语法编写 Fortran-57 代码。但它不如用 Ruby 语法编写 Ruby 代码。

      Scheme 是一种函数式语言。函数式语言使用函数(更准确地说,函数闭包)进行封装和状态。但 Ruby 不是函数式语言,它是面向对象的语言,OO 语言使用 objects 进行封装和状态。

      因此,函数闭包变成了对象,捕获的变量变成了实例变量。

      我们也可以从一个完全不同的角度来解决这个问题:您正在做的是定义一个 singleton 方法,该方法的目的是定义特定于一个对象。但是您正在为类的 每个 实例定义该单例方法,并且您正在为该类的每个实例定义 same 单例方法。我们已经有了一种为类的每个实例定义行为的机制:实例方法。

      这两个论点来自完全相反的方向,但它们到达了同一个目的地:

      class Example
        def initialize(test='hey')
          @test = test
        end
      
        def say_hello
          @test
        end
      end
      

      【讨论】:

        【解决方案4】:

        我知道两年前有人问过这个问题,但我想再补充一个答案。 .instance_eval 将有助于向实例对象添加方法

            string = "String"
            string.instance_eval do
              def new_method
                self.reverse
              end
            end
        

        【讨论】:

          猜你喜欢
          • 2010-09-06
          • 1970-01-01
          • 1970-01-01
          • 2012-12-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-07-19
          • 2011-12-11
          相关资源
          最近更新 更多