2011-10-12 17 views
5

In ruby, si può fare questo:Come viene implementato il metodo "pubblico/protetto/privato" e come posso emularlo?

class Thing 
    public 
    def f1 
    puts "f1" 
    end 

    private 
    def f2 
    puts "f2" 
    end 

    public 
    def f3 
    puts "f3" 
    end 

    private 
    def f4 
    puts "f4" 
    end 
end 

dove ora f1 e f3 e pubblico, f2 ed F4 è privato. Cosa sta succedendo internamente che ti permette di invocare un metodo di classe che poi cambia la definizione del metodo? Come posso implementare la stessa funzionalità (apparentemente per creare il mio java come annotazioni)

per esempio ...

class Thing 
    fun 
    def f1 
    puts "hey" 
    end 

    notfun 
    def f2 
    puts "hey" 
    end 
end 

e divertente e notfun cambierebbero le seguenti definizioni di funzione.

Grazie

risposta

8

A volte puoi spingere Ruby in una tazza espresso. Vediamo come.

Ecco un FunNotFun modulo ...

module FunNotFun 

    def fun 
    @method_type = 'fun' 
    end 

    def notfun 
    @method_type = 'not fun' 
    end 

    def method_added(id) 
    return unless @method_type 
    return if @bypass_method_added_hook 
    orig_method = instance_method(id) 
    @bypass_method_added_hook = true 
    method_type = @method_type 
    define_method(id) do |*args| 
     orig_method.bind(self).call(*args).tap do 
     puts "That was #{method_type}" 
     end 
    end 
    @bypass_method_added_hook = false 
    end 

end 

... che è possibile utilizzare per estendere una classe ...

class Thing 

    extend FunNotFun 

    fun 
    def f1 
    puts "hey" 
    end 

    notfun 
    def f2 
    puts "hey" 
    end 
end 

... con questo risultato:

Thing.new.f1 
# => hey 
# => That was fun 

Thing.new.f2 
# => hey 
# => That was not fun 

Ma vedi sotto la linea per un modo migliore.


NOTE (vedi risposta di normalocity) sono meno problemi e, essendo un idioma comune Ruby, sarà più facilmente comunicare l'intento del codice. Ecco come farlo con annotazioni:

module FunNotFun 

    def fun(method_id) 
    wrap_method(method_id, "fun") 
    end 

    def notfun(method_id) 
    wrap_method(method_id, "not fun") 
    end 

    def wrap_method(method_id, type_of_method) 
    orig_method = instance_method(method_id) 
    define_method(method_id) do |*args| 
     orig_method.bind(self).call(*args).tap do 
     puts "That was #{type_of_method}" 
     end 
    end 
    end 

end 

In uso, l'annotazione viene dopo il metodo è definito, piuttosto che prima:

class Thing 

    extend FunNotFun 

    def f1 
    puts "hey" 
    end 
    fun :f1 

    def f2 
    puts "hey" 
    end 
    notfun :f2 

end 

Il risultato è lo stesso:

Thing.new.f1 
# => hey 
# => That was fun 

Thing.new.f2 
# => hey 
# => That was not fun 
+0

ah, questo è più quello che avevo in mente –

+0

È il primo approccio thread-safe? –

+1

@Semyon, Non se si hanno più thread che aggiungono metodi alla stessa classe contemporaneamente. Quasi sempre, l'aggiunta di metodi viene eseguita da un solo thread, però. –

1

Suona come si desidera scrivere estensioni al linguaggio Ruby in sé, che è possibile. Non è qualcosa che può essere spiegato brevemente, ma questo link dovrebbe iniziare:

http://ruby-doc.org/docs/ProgrammingRuby/html/ext_ruby.html

Questo riferimento, avendo a che fare con le annotazioni in Ruby, potrebbe anche essere utile/rilevante:

http://martinfowler.com/bliki/RubyAnnotations.html

+1

Sarebbe davvero necessario estendere il rubino affinché ciò sia possibile? Sembra che dovrebbe essere fattibile con qualche metaprogrammazione intelligente o qualcosa del genere, ma immagino che potrebbe non essere il caso. –

1

Ecco una soluzione di puro rubino per ottenere la giusta direzione. È su method_added. Fai attenzione a evitare la ricorsione usando una clausola di guardia.

module Annotations 
    def fun 
    @state = :fun 
    end 

    def not_fun 
    @state = :not_fun 
    end 

    def inject_label(method_name) 
    state = @state 
    define_method(:"#{method_name}_with_label") do |*args, &block| 
     puts "Invoking #{method_name} in state #{state}" 
     send(:"#{method_name}_without_label", *args, &block) 
    end 

    alias_method :"#{method_name}_without_label", :"#{method_name}" 
    alias_method :"#{method_name}", :"#{method_name}_with_label" 
    end 

    def self.extended(base) 
    base.instance_eval do 
     def self.method_added(method_name) 
     return if method_name.to_s =~ /_with(out)?_label\Z/ 
     @seen ||= {} 
     unless @seen[method_name] 
      @seen[method_name] = true 
      inject_label(method_name) 
     end 
     end 
    end 
    end 
end 

class Foo 
    extend Annotations 

    fun 

    def something 
    puts "I'm something" 
    end 

    not_fun 

    def other 
    puts "I'm the other" 
    end 
end 
Problemi correlati