2011-10-12 7 views
5

En Ruby, usted puede hacer esto:¿Cómo se implementa el método "público/protegido/privado" y cómo puedo 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 

donde ahora F1 y F3 y pública, F2 y F4 es privado. ¿Qué está sucediendo internamente que le permite invocar un método de clase que luego cambia la definición del método? ¿Cómo puedo implementar la misma funcionalidad (aparentemente para crear mi propio Java como anotaciones)

por ejemplo ...

class Thing 
    fun 
    def f1 
    puts "hey" 
    end 

    notfun 
    def f2 
    puts "hey" 
    end 
end 

y divertido y notfun cambiarían las siguientes definiciones de funciones.

Gracias

Respuesta

8

A veces puede empujar a Ruby en una taza espressso. Veamos cómo.

Aquí está un módulo de FunNotFun ...

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 

... que se puede utilizar para extender una clase ...

class Thing 

    extend FunNotFun 

    fun 
    def f1 
    puts "hey" 
    end 

    notfun 
    def f2 
    puts "hey" 
    end 
end 

... con este resultado:

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

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

Pero vea debajo de la línea una mejor manera.


Anotaciones (véase la respuesta de normalocity) son menos problemas y, al ser un idioma común Ruby, se comunicará con mayor facilidad la intención de su código. Así es como hacerlo con anotaciones:

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 

En uso, la anotación se produce después se define el método, en lugar de antes:

class Thing 

    extend FunNotFun 

    def f1 
    puts "hey" 
    end 
    fun :f1 

    def f2 
    puts "hey" 
    end 
    notfun :f2 

end 

El resultado es el mismo:

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

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

ah, esto es más de lo que tenía en mente –

+0

¿El primer enfoque es seguro para subprocesos? –

+1

@Semyon, no si tiene varios hilos que agregan métodos a la misma clase al mismo tiempo. Sin embargo, casi siempre, agregar métodos se hace con solo un hilo. –

1

suena como usted quiere escribir extensiones para el lenguaje Ruby en sí, que es posible. No es algo que se puede explicar brevemente, pero este enlace debe empezar:

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

Esta referencia, que tienen que ver con las anotaciones en Ruby, también podría ser útil/relevante:

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

+1

¿Realmente sería necesario extender el rubí para que sea posible? Parece que debería ser factible con alguna metaprogramación inteligente o algo así, pero supongo que puede no ser el caso. –

1

Aquí hay una solución de rubí puro para que vayas en la dirección correcta. Bisagras en method_added. Tenga cuidado de evitar la recursión mediante el uso de una cláusula de 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 
Cuestiones relacionadas