动态替换Ruby中对象的方法实现

我想用一个用户指定的块替换对象的方法的实现。 在JavaScript中,这很容易实现:

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") 

在C#中它也很容易

 class Foo { public Action 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"); 

但是我怎么能在Ruby中做同样的事情呢? 我有一个解决方案,将lambda存储在一个实例变量中,该方法调用lambda,但我真的不喜欢开销和语法

 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" 

我想要这样的语法:

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

我想出了以下代码

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

但是这对于多个参数来说有点难看,但仍然感觉不对。 我也可以定义一个set_bar方法,但我不喜欢这样:)。

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

所以问题是:有没有更好的方法来做到这一点,如果没有,你更喜欢什么方式

编辑:@ welldan97的方法有效,但我松散了局部变量范围,即

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

不起作用。 我想我必须坚持使用lambda才能工作?

使用def

 foo = Foo.new foo.bar "baz" def foo.bar x puts "*" + x.to_s end foo.bar "baz" 

是的,那么简单

编辑:为了不松开范围,您可以使用define_singleton_method (如@freemanoid答案):

  prefix = "*" foo.define_singleton_method(:bar) do |x| puts prefix + x.to_s end foo.bar 'baz' 

你可以像这样实现你想要的东西:

 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 

这在ruby中绝对正常和正确。