Динамически заменить реализацию метода на объекте в Ruby - PullRequest
8 голосов
/ 20 марта 2012

Я бы хотел заменить реализацию метода для объекта блоком, который указывает пользователь.В 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?У меня есть решение, которое хранит лямбду в переменной экземпляра, а метод вызывает лямбду, но мне не очень нравятся накладные расходы и синтаксис

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

не работает.Я полагаю, я должен придерживаться лямбда, чтобы это работало?

Ответы [ 2 ]

16 голосов
/ 20 марта 2012

использовать 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'
3 голосов
/ 08 апреля 2013

Вы можете реализовать то, что вы хотите, как это:

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.

...