2012-03-20 11 views
6

Vorrei sostituire l'implementazione di un metodo per un oggetto con un blocco specificato dall'utente. In JavaScript, questo è facilmente realizzabile:Sostituire dinamicamente l'implementazione del metodo su un oggetto in Ruby

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# è anche abbastanza facile

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

Ma come posso fare la stessa cosa in Ruby? Ho una soluzione che memorizza una lambda in una variabile di istanza ed il metodo chiama il lambda, ma non mi piace molto il sovraccarico e la sintassi

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" 

Mi piacerebbe avere una sintassi del genere:

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

mi si avvicinò con il seguente codice

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

Ma questo è un pò brutto per più di un parametro e ancora non si sente 'giusto'. Potrei anche definire un metodo set_bar, ma non mi piace neanche :).

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

    def set_bar &blk 
    @bar = blk 
    end 
end 

Quindi domanda è: C'è un modo migliore fare per fare questo e se no, quale modo si preferisce

Edit: @ approccio di welldan97 funziona, ma ho sciolto la portata variabile locale, vale a dire

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

non funziona. Suppongo che devo restare con lambda perché funzioni?

risposta

11

uso def:

foo = Foo.new 
foo.bar "baz" 

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

foo.bar "baz" 

sì, così semplice

Modifica: Per non perdere la portata è possibile utilizzare define_singleton_method (come in risposta @freemanoid):

prefix = "*" 

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

foo.bar 'baz' 
+1

oh beh, probabilmente era troppo facile \ * facepalm \ * :) – kev

+0

hmm, perdo l'ambito su variabili locali, ad esempio 'prefix =" * "; def foo.bar x; inserisce prefisso + x.to_s; fine' non funziona:/ – kev

+0

@ welldan97, come posso sostituire il metodo "setter" per un oggetto specifico? – gaussblurinc

3

Puoi implementare ciò che si desidera in questo modo:

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 

Questo è assolutamente normale e corretto in rubino.

Problemi correlati