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

让我们将Example.new; Example.new定义为:

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

在调用2939853936825730030048时,我得到了warning: method redefined; discarding old say_hello。我得出结论,这一定是因为它在实际的类中定义了一个方法(从语法上讲是有意义的)。 而且,如果方法中有多个具有不同值的Example实例,那当然会造成灾难性的后果。

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

Aaron Yodaiken asked 2020-06-26T10:17:43Z
3个解决方案
72 votes

您需要获取对实例的singleton类的引用,该类包含所有实例特定的内容,并在其上定义方法。 在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
thorncp answered 2020-06-26T10:18:11Z
39 votes

首先,一个小风格提示:

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

通过使用Ruby 1.9中的新proc文字,可以使外观更好看:

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

但是您不需要。 Ruby有一种叫做块的东西,基本上就是一段代码,您可以将其作为参数传递给方法。 实际上,您已经使用过块,因为Example只是一种以块为参数并返回initialize的方法。但是,无论如何,self已经采用了块,因此无需将块传递给ex1即可将其转换为 ex2传递给self.class,然后将其转换回一个块:

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

正如您已经注意到的,您正在错误的类上定义方法。 您正在Example类上定义它,因为在initializeselfex2之类的实例方法内部是当前对象(例如@mikej的ex2),这意味着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语言使用对象进行封装和状态化。

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

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

这两个参数都来自完全相反的方向,但它们到达的目的地相同:

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

  def say_hello
    @test
  end
end
Jörg W Mittag answered 2020-06-26T10:19:32Z
8 votes

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

    string = "String"
    string.instance_eval do
      def new_method
        self.reverse
      end
    end
Selvamani answered 2020-06-26T10:19:52Z
translate from https://stackoverflow.com:/questions/3026943/is-there-a-way-to-create-methods-just-for-the-instance-of-a-ruby-class-from-insi