有没有办法从该实例内部为Ruby类的实例创建方法?

Aar*_*ken 47 ruby

让我们class Example定义为:

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

在打电话给Example.new; Example.new我得到一个warning: method redefined; discarding old say_hello.我总结说,这必须是因为它在实际的类中定义了一个方法(从语法上来说这是有意义的).当然,如果Example在他们的方法中存在多个具有不同值的实例,那将是灾难性的.

有没有办法从该实例内部为类的实例创建方法?

tho*_*ncp 72

您需要获取对实例的单例类的引用,该类包含所有特定于实例的内容,并在其上定义方法.在红宝石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
Run Code Online (Sandbox Code Playgroud)

但是,Ruby 1.9提供了一种更简单的方法.

Ruby 1.9

class Example
  def initialize(test='hey')
    define_singleton_method :say_hello, lambda { test }
  end
end
Run Code Online (Sandbox Code Playgroud)

  • 我冒昧编辑答案以使用术语“单例类”,因为它是官方名称。事实上,Ruby 1.9.2 引入了`Object#singleton_class` (3认同)

Jör*_*tag 39

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

self.class.send(:define_method, :say_hello, lambda { test })
Run Code Online (Sandbox Code Playgroud)

你可以通过在Ruby 1.9中使用新的proc文字来使这个看起来更好一些:

self.class.send(:define_method, :say_hello, -> { test })
Run Code Online (Sandbox Code Playgroud)

但你不需要那样做.Ruby有一些称为块的东西,它们基本上是一段代码,可以作为参数传递给方法.实际上,您已经使用了块,因为lambda它只是一个将块作为参数并返回一个的方法Proc.但是,无论如何define_method 已经占用了一个块,没有必要传递一个块,lambda它将它转换为Proc它传递给define_method它的块然后将它转换回块:

self.class.send(:define_method, :say_hello) { test }
Run Code Online (Sandbox Code Playgroud)

正如您已经注意到的那样,您正在错误的类上定义方法.正在定义它的Example类,由于内侧的实例方法一样initialize,self是当前的对象(即,ex1ex2在@ mikej的示例),这意味着self.classex1的类,这是Example.所以,你一遍又一遍地覆盖相同的方法.

这会导致以下不必要的行为:

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

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

(class << self; self end).send(:define_method, :say_hello) { test }
Run Code Online (Sandbox Code Playgroud)

这按预期工作:

ex1 = Example.new('ex1')
ex2 = Example.new('ex2')
ex1.say_hello            # => ex1
ex2.say_hello            # => ex2
Run Code Online (Sandbox Code Playgroud)

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

define_singleton_method(:say_hello) { test }
Run Code Online (Sandbox Code Playgroud)

现在,这可以按照你想要的方式工作,但这里有一个更高级别的问题:这不是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
Run Code Online (Sandbox Code Playgroud)


Sel*_*ani 8

我知道这是两年前被问到的,但我想补充一个答案..instance_eval将有助于向实例对象添加方法

    string = "String"
    string.instance_eval do
      def new_method
        self.reverse
      end
    end
Run Code Online (Sandbox Code Playgroud)