绑定方法到实例

Binding method to instance

如果方法和实例都作为符号传递给方法,如果实例不是符号,是否有方法将现有方法绑定到对象的现有实例?

例如:

    def some_method
      #do something
    end

    some_instance = Klass.new(something)

    def method_that_binds(:some_method, to: :some_instance)
      #how do I do that?
    end

假设我们有一个 class A 方法 a 和一个局部变量 c.

class A
  def a; 10 end
end

c = '5'

我们想将方法 A#a 添加到 c

这是可以做到的

c.singleton_class.send :define_method, :b, &A.new.method(:a)
p c.b # => 10

说明。

将方法添加到对象实例而不是其 class 的一种方法是在其单例 class 中定义它(每个 ruby 对象都有)。

我们可以通过调用对应的方法c.signleton_class得到c的单例class。

接下来我们需要在它的 class 中动态定义一个方法,这通常可以通过使用 define_method 来完成,它将方法名称作为它的第一个参数(在我们的例子中 :b) 和一个块。现在,将方法转换为块可能看起来有点棘手,但想法相对简单:我们首先通过调用 Object#method and then by putting the & before A.new.method(:a) we tell the interpreter to call the to_proc method on our object (as our returned object is an instance of the Method, the Method#to_proc 将方法转换为 Method 实例)然后返回proc 将被翻译成 define_method 期望作为其第二个参数的块。

如果您想将方法 just 添加到 some_instance 即它在 Klass 的其他实例上不可用,那么可以使用define_singleton_method(文档 here。)

some_instance.define_singleton_method(:some_method, method(:some_method))

此处第一次使用符号 :some_method 是您希望方法在 some_instance 上使用的名称,第二次用作 method 的参数是创建一个Method 来自现有方法的对象。

如果您想使用与现有方法相同的名称,您可以将其包装在您自己的方法中,例如:

def add_method(obj, name)
  obj.define_singleton_method(name, method(name))
end

你的要求有点不寻常,但基本上可以按照你说的去做:

class Person; end
harry = Person.new
barry = Person.new

def test
  puts 'It works!'
end

define_method :method_that_binds do |a_method, to|
  eval(to[:to].to_s).singleton_class.send(:define_method, a_method, &Object.new.method(a_method))
end

method_that_binds :test, to: :harry
harry.test
# It works! will be sent to STDOUT
barry.test
# undefined method 'test'

这实际上并没有使用命名参数,而是接受带有 to 键的散列,但你可以看到你可以按照你想要的方式调用它。它还假定您定义的方法是在 Object.

上全局定义的

你想要的API并不容易工作,因为你必须知道从哪个范围你想访问局部变量。我不太清楚为什么要传递局部变量的名称而不是传递局部变量的内容……毕竟,局部变量 出现在调用站点。

无论如何,如果除了名称之外还传入范围,这可以很容易地完成:

def some_method(*args)
  puts args
  puts "I can access some_instance's ivar: #@private_instance_var"
end

class Foo; def initialize; @private_instance_var = :foo end end

some_instance = Foo.new

def method_that_binds(meth, to:, within:, with: [])
  self.class.instance_method(meth).bind(within.local_variable_get(to)).(*with)
end

method_that_binds(:some_method, to: :some_instance, within: binding, with: ['arg1', 'arg2'])
# arg1
# arg2
# I can access some_instance's ivar: foo

如您所见,我还添加了一种将参数传递给方法的方法。没有那个扩展,它变得更加简单:

def method_that_binds(meth, to:, within:)
  self.class.instance_method(meth).bind(within.local_variable_get(to)).()
end

但是您必须将作用域 (Binding) 传递到方法中。