2009-10-14 10 views
25

Me llevó un tiempo entender cómo funcionan los métodos privados en Ruby, y realmente me parece muy incómodo. ¿Alguien sabe si existen buenas razones para que los métodos privados se manejen tal como son? ¿Son solo razones históricas? O razones de implementación? ¿O hay buenas razones lógicas sólidas (es decir, semánticas)?¿Hay buenas razones para que 'privado' funcione de la manera en que lo hace en Ruby?

Por ejemplo: comportamiento

class Person 
    private 
    attr_reader :weight 
end 

class Spy < Person 
private 
    attr_accessor :code 
public 
    def test 
    code   #(1) OK: you can call a private method in self 
    Spy.new.code #(2) ERROR: cannot call a private method on any other object 
    self.code  #(3) ERROR!!! cannot call a private method explicitly on 'self' 
    code="xyz" #(4) Ok, it runs, but it actually creates a local variable!!! 
    self.code="z" #(5) OK! This is the only case where explicit 'self' is ok 
    weight  #(6) OK! You can call a private method defined in a base class 
    end 
end 
  • de Ruby en las líneas (1), (2) y (5) parece razonable.
  • El hecho de que (6) está bien es un poco extraño, especialmente viniendo de Java y C++. ¿Hay alguna buena razón para esto?
  • ¡Realmente no entiendo por qué (3) falla! Una explicación, alguien?
  • El problema en la línea (4) parece una ambigüedad en la gramática, que no tiene nada que ver con 'privado'.

¿Alguna idea?

+1

Tengo curiosidad por saber el razonamiento detrás de las decisiones de diseño también. Mi respuesta es solo una explicación que aclara tus conceptos erróneos. Explica proporcionar un poco del cómo, pero no el por qué. – EmFi

Respuesta

30

Puede que le resulte útil leer definición de public, private and protected. de rubí (Pasar a la control de acceso)

privada de Ruby es análoga a la de Java protegido. No hay ningún equivalente Ruby de privado de Java. EDITAR: Esta solución ahora proporciona un método para simular el ideal de privacidad de Java en objetos Ruby.

Privado se define como métodos/variables que pueden llamarse solo implícitamente. Esta es la razón por la cual los enunciados 2 y 3 fallan. En otras palabras, los límites de los métodos/variables al contexto de una clase o subclase en la que están definidos. La herencia pasa los métodos privados a las subclases y, por lo tanto, se puede acceder con un yo implícito. (Explicando por qué funciona la declaración 6)

Creo que está buscando algo más cerca de lo protegido. Que se comporta de manera similar a los accesadores de Java a los que no se les da visibilidad (p. Ej .: pública, privada, protegida) Al cambiar el privado en Spy para proteger los 6 de su trabajo de extractos. Los métodos protegidos pueden invocarse mediante cualquier instancia de la clase definitoria o sus subclases. Llamar explícita o implícitamente a sí mismo son afirmaciones válidas para métodos protegidos siempre que la persona que llama sea la clase del objeto que responde a la llamada o herede de ella.

class Person 
    private 
    attr_reader :weight 
end 

class Spy < Person 
protected 
    attr_accessor :code 
public 
    def test 
    code   #(1) OK: you can call a private method in self 
    Spy.new.code #(2) OK: Calling protected method on another instance from same class family or a descendant. 
    self.code  #(3) OK: Calling protected method on with explicit self is allowed with protected 
    code="xyz" #(4) Ok, it runs, but it actually creates a local variable!!! 
    self.code="z" #(5) OK! This is the only case where explicit 'self' is ok 
    weight  #(6) OK! You can call a private method defined in a base class 
    end 
end 

s = Spy.new 
s.test # succeeds 
s.code #(7) Error: Calling protected method outside of the class or its descendants. 

En cuanto a la declaración 4. Tiene razón al suponer que esto es para evitar ambigüedades. Es más una salvaguarda al daño potencial de la naturaleza dinámica de ruby. Asegura que no puede anular los usuarios abriendo nuevamente la clase más tarde. Una situación que puede surgir, por ejemplo, al evaluar un código contaminado.

Solo puedo especular sobre las decisiones de diseño que llevaron a estos comportamientos. Para la mayoría, creo que se reduce a la naturaleza dinámica del lenguaje.

P.S. Si realmente quieres darle a las cosas la definición java de private. Solo disponible para la clase en la que está definido, ni siquiera subclases. Puede agregar un método self.herited a sus clases para eliminar las referencias a los métodos a los que desea limitar el acceso.

hacer el peso atribuyen inaccesible desde subclases:

class Person 
    private 
    attr_reader :weight 

    def initialize 
    @weight = 5 
    end 

    def self.inherited(subclass) 
    subclass.send :undef_method, :weight 
    end 
end 

class Spy < Person 
private 
    attr_accessor :code 
public 
    def test 
    weight  
    end 
end 

Person.new.send(:weight) # => 5 
Spy.new.send(:weight) #=> Unhelpful undefined method error 

Se puede tener más sentido para reemplazar la llamada undef_method a algo como esto:

def self.inherited(subclass) 
    subclass.class_eval %{ 
     def weight 
     raise "Private method called from subclass. Access Denied" 
     end 
    } 
    end 

que proporciona un error mucho más servicial y el misma funcionalidad.

El envío es necesario para evitar llamar a métodos privados para otras clases. Solo se usa para probar que las cosas funcionan realmente.

Lo que en retrospectiva, hace inútiles las protecciones privadas. Si realmente quieres proteger tus métodos, deberás anular el envío para bloquearlos. El siguiente código hace que en base a los private_methods del objeto:

def send_that_blocks_private_methods(method, *args) 
    if private_methods.include?(method.to_s) 
    raise "Private method #{method} cannot called be called with send." 
    else 
    send_that_allows_private_methods(method, *args) 
    end 
end 

alias_method :send_that_allows_private_methods, :send 
alias_method :send, :send_that_blocks_private_methods 
private :send_that_allows_private_methods 

puede especificar un class_variable de private_methods desea bloquear el acceso a su lugar de negar el acceso a todos los métodos privados. También puede hacer que el envío sea privado, pero hay usos legítimos de llamar al envío desde fuera de un objeto.

+0

+1 Gracias por su respuesta: es muy claro. – MiniQuark

+0

"Lo cual, en retrospectiva, hace que la privacidad y la protección sean inútiles". Realmente no. Ellos tienen valor de comunicación. Si hago público 'Foo # things', estoy diciendo" use this "; si es privado, digo "no dependas de esto, es un detalle de implementación que podría cambiarse". Si Ruby te va a permitir volver a abrir 'Foo' y ** redefinir **' # things', también podría permitirte ** acceder ** al método. Pero hace que uses 'enviar' para que sepas que estás haciendo algo inesperado. Creo que esta es solo la filosofía de Ruby. –

+0

¿Es eso 'send_that_blocks_private_methods' similar a [' Object # public_send'] (https://ruby-doc.org/core-2.4.1/Object.html#method-i-public_send)? –

Cuestiones relacionadas