【问题标题】:Dynamically replace method implementation on an object in Ruby在 Ruby 中动态替换对象上的方法实现
【发布时间】:2012-03-20 12:11:40
【问题描述】:

我想用用户指定的块替换对象方法的实现。在 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<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");

但是我怎样才能在 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 才能工作?

【问题讨论】:

    标签: ruby metaprogramming


    【解决方案1】:

    使用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'
    

    【讨论】:

    • 哦,那可能太容易了 *facepalm* :)
    • 嗯,我将范围放宽到局部变量,即 prefix="*"; def foo.bar x; puts prefix + x.to_s; end 不起作用:/
    • @welldan97,如何替换特定对象的方法“setter”?
    • @gaussblurinc,如果你想重新定义一个 setter 方法,它几乎是一样的foo = {}; foo.define_singleton_method(:bar=) { |x| self[:bar] = x }; foo.bar = 5; foo # =&gt; { bar: 5 },如果我理解正确的话
    • @welldan97,是的,你明白我的意思。但我现在无法理解你:) 你创建的是一个结构,而不是一个对象。我的意思是我想为特定对象重新定义方法“setter”,而不是结构
    【解决方案2】:

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

    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​​ 中是绝对正常和正确的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-01
      • 1970-01-01
      • 2019-07-11
      • 2013-09-27
      • 2013-10-25
      • 2013-01-01
      • 2019-07-26
      • 2010-09-26
      相关资源
      最近更新 更多