Dynamically replace method implementation on an object in Ruby

非 Y 不嫁゛ 提交于 2019-12-22 04:25:07

问题


I'd like to replace the implementation of a method for an object with a block that the user specifies. In JavaScript, this is easily accomplished:

function Foo() {
    this.bar = function(x) { console.log(x) }
}
foo = new Foo()
foo.bar("baz")
foo.bar = function(x) { console.error(x) }
foo.bar("baz")

In C# it is also quite easy

class Foo
{
    public Action<string> Bar { get; set; }
    public Foo()
    {
        Bar = x => Console.WriteLine(x);
    }
}

var foo = Foo.new();
foo.Bar("baz");
foo.Bar = x => Console.Error.WriteLine(x);
foo.Bar("baz");

But how can I do the same in Ruby? I have a solution that stores a lambda in an instance variable and the method calls the lambda, but I don't really like the overhead and syntax

class Foo
  def initialize
    @bar = lambda {|x| puts x}
  end

  def bar x
    @bar.call x
  end

  def bar= blk
    @bar = blk
  end
end

foo = Foo.new
foo.bar "baz"
foo.bar= lambda {|x| puts "*" + x.to_s}
foo.bar "baz"

I'd like to have a syntax like that:

foo.bar do |x|
    puts "*" + x.to_s
end
foo.bar "baz"

I came up with the following code

class Foo
  def bar x = nil, &blk
    if (block_given?)
      @bar = blk
    elsif (@bar.nil?)
      puts x
    else
      @bar.call x
    end
  end
end

But this is kinda ugly for more than one parameter and still doesn't feel 'right'. I could also define a set_bar method, but i don't like that either :).

class Foo
  def bar x
    if (@bar.nil?)
      puts x
    else
      @bar.call x
    end
  end

  def set_bar &blk
    @bar = blk
  end
end

So question is: Is there a better way do to do this and if not, what way would you prefer

Edit: @welldan97's approach works, but i loose the local variable scope, i.e.

prefix = "*"
def foo.bar x
    puts prefix + x.to_s
end

doesn't work. I suppose I have to stick with lambda for that to work?


回答1:


use def:

foo = Foo.new
foo.bar "baz"

def foo.bar x
  puts "*" + x.to_s
end

foo.bar "baz"

yes, that simple

Edit: To not loose the scope you can use define_singleton_method(as in @freemanoid answer):

 prefix = "*"

 foo.define_singleton_method(:bar) do |x|
   puts prefix + x.to_s
 end

 foo.bar 'baz'



回答2:


You can implement what you want like this:

class Foo; end

foo = Foo.new
prefix = '*'
foo.send(:define_singleton_method, :bar, proc { |x| puts prefix + x.to_s })
foo.bar('baz')
"*baz" <<<<<-- output

This is absolutely normal and correct in ruby.



来源:https://stackoverflow.com/questions/9786495/dynamically-replace-method-implementation-on-an-object-in-ruby

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!