2010-01-25 9 views
31

¿Hay alguna forma de hacer que las variables de instancia sean "privadas" (definición C++ o Java) en ruby? En otras palabras, quiero que el siguiente código genere un error.¿Cómo hacer que las variables de instancia sean privadas en Ruby?

class Base 
    def initialize() 
    @x = 10 
    end 
end 

class Derived < Base 
    def x 
    @x = 20 
    end 
end 

d = Derived.new 
+1

Esto parece una solicitud poco común, ¿cuál es el caso de uso para un patrón así? Quizás sabes algo que yo no sé, que sería útil en el futuro. –

+2

Viniendo del mundo C++, me parece natural tener variables privadas en la clase base a las que no se puede acceder en la clase derivada y me da la seguridad de que no se modificarán en la clase derivada. En el ejemplo anterior, puedo estar seguro de que el único lugar donde se modificará @x es en la clase "Base" si es posible convertirlo en una variable de instancia privada. – prasadvk

+0

¿Podría darme un caso de uso más específico para él? No tiene por qué ser complicado. Siento que si entendí un problema para el cual no quisiera que un objeto tuviera acceso a sus propios espacios, podría ayudar a la discusión. –

Respuesta

33

Como la mayoría de las cosas en Ruby, variables de instancia no son verdaderamente "privado" y se puede acceder por cualquier persona con d.instance_variable_get :@x.

A diferencia de Java/C++, las variables de instancia en Ruby son siempre privadas. Nunca son parte de la API pública, como lo son los métodos, ya que solo se puede acceder con ese getter verboso. Entonces, si hay cordura en su API, no tiene que preocuparse de que alguien abuse de sus variables de instancia, ya que usarán los métodos en su lugar. (Por supuesto, si alguien quiere ir salvaje y tener acceso a métodos privados o variables de instancia, no hay una forma de detenerlos.)

La única preocupación es si alguien accidentalmente sobrescribe una variable de instancia cuando se extienden a su clase. Eso se puede evitar usando nombres poco probables, tal vez llamándolo @base_x en su ejemplo.

+11

no es el problema aquí que en su código puede modificar la variable '@ x' de la clase derivada? Eso es contrario a lo que ocurre en C++, donde una clase derivada _no puede_ acceder a miembros de datos privados. Entonces, si bien es cierto que las 'variables de instancia en ruby ​​son privadas', lo importante es que es un tipo diferente de privado al significado de privado en C++ – horseyguy

+7

Creo que en lenguaje C++, uno diría 'variables de instancia en ruby ​​son siempre protegido '. Si bien no es un análogo perfecto, es más preciso que el significado C++ de privado. –

+1

suspiro, sip ... únete al club de lenguajes de scripting que no implementan correctamente el soporte de OOP. – nus

5

A diferencia de los métodos que tienen diferentes niveles de visibilidad, las variables de instancia de Ruby son siempre privadas (desde fuera de los objetos). Sin embargo, las variables de instancia de los objetos internos siempre son accesibles, ya sea desde el padre, la clase hija o los módulos incluidos.

Dado que probablemente no haya forma de alterar el acceso de Ruby @x, no creo que pueda tener ningún control sobre él. Escribir @x simplemente seleccionaría directamente esa variable de instancia, y dado que Ruby no proporciona control de visibilidad sobre las variables, convivir con ella, supongo.

Como dice @marcgg, si no desea que las clases derivadas toquen las variables de su instancia, no lo use en absoluto o encuentre una manera inteligente de ocultarlo de las clases derivadas.

26

Nunca utilice variables de instancia directamente. Solo use accessors. Se puede definir el lector como público y lo privado escritor por:

class Foo 
    attr_reader :bar 

    private 

    attr_writer :bar 
end 

Sin embargo, tenga en cuenta que private y protected no significan lo que usted piensa que significan. Los métodos públicos se pueden invocar contra cualquier receptor: named, self o implicit (x.baz, self.baz o baz). Los métodos protegidos solo pueden invocarse con un receptor propio o implícitamente (self.baz, baz). Los métodos privados solo pueden invocarse con un receptor implícito (baz).

En resumen, se está acercando al problema desde un punto de vista que no es de Ruby. Utilice siempre descriptores de acceso en lugar de variables de instancia. Use public/protected/private para documentar su intención, y suponga que los consumidores de su API son adultos responsables.

+0

La parte sobre accesibilidad y receptores realmente ayudó a aclarar algunos problemas que tuve en el pasado. – Andy

+0

"Nunca use variables de instancia directamente ..." ¿Por qué no? Son una parte central del lenguaje. Yo diría que depende de tu situación y del problema que estás tratando de resolver. – mastaBlasta

+0

Es una regla de oro. Por supuesto, 'attr_reader' y' attr_writer' usan variables de instancia detrás de las escenas. Y es posible que desee utilizarlos directamente para la memorización transparente ('@_foo || = begin; # slow operation; end'). Pero si usa variables de instancia directamente, no puede conectar su comportamiento al obtener o establecer sus valores sin cambiar el código en ningún otro lugar (incluido el código que los subclasifica). Tampoco obtienes una excepción si escribes mal una '@ isntance_variable', mientras que si haces un' self.mtehod() '. No son más "centrales" que '@@ class_variables', que también están prohibidas. –

1

No es posible hacer lo que quiera, porque las variables de instancia no están definidas por la clase, sino por el objeto.

Si utiliza la composición en lugar de la herencia, no tendrá que preocuparse por sobrescribir las variables de instancia.

+0

+1. en la mayoría de los casos, la composición proporciona una solución más flexible. Sería bueno si la clase derivada no tuviera acceso a las variables de miembros privados para protegerse contra el caso en el que el desarrollador reutiliza accidentalmente un nombre de variable, pero, de nuevo, la predeclaración variable no es obligatoria en ruby ​​de todos modos. –

+0

La primera declaración de Andrew es tan cierta y una que los programadores provenientes de Java/C++ deberían tatuarse en sus manos. Las clases no 'declaran' variables de instancia. Las variables de instancia se agregan a los objetos a medida que se ejecuta el programa. Si los métodos que crean una variable de instancia no se invocan, el objeto nunca tendrá esa variable de instancia. – ComDubh

-1

Sé que esto es antiguo, pero me encontré con un caso en el que no deseaba impedir el acceso a @x, pero sí quería excluirlo de los métodos que usan la reflexión para la serialización.Específicamente utilizo YAML::dump a menudo con fines de depuración, y en mi caso @x era de la clase Class, que YAML::dump se niega a volcar.

En este caso yo había considerado varias opciones

  1. Dirigiéndose a esto sólo por yaml redefiniendo "to_yaml_properties"

    def to_yaml_properties 
        super-["@x"] 
    end 
    

    pero esto hubiera funcionado sólo para yaml y si otros volquetes (to_xml ?) no sería feliz

  2. Dirigiéndose a todos los usuarios de reflexión mediante la redefinición de "variables de instancia"

    def instance_variables 
        super-["@x"] 
    end 
    
  3. Además, he encontrado this en una de mis búsquedas, pero no lo he probado que el anterior parece más sencillo para mis necesidades

Así, mientras que éstos pueden no ser exactamente lo que el PO dijo necesitaba, si otros encuentran esta publicación mientras buscan la variable que se excluirá del listado, en lugar de acceder, entonces estas opciones pueden ser valiosas.

+0

Sugiero hacer esta pregunta como una pregunta separada y responderla usted mismo. Responder aquí crea un ruido extra. – Kelvin

+0

@ Kelvin Respondí aquí porque no estaba del todo claro POR QUÉ el OP quería hacer esto, pero esto lo habría ayudado si sus motivos fueran similares a los míos. Nunca dijo sus razones, si lo hizo y su objetivo completo era diferente, entonces lo habría eliminado. Tal como está, ayudaría a cualquiera que llegue a esta pregunta tratando de resolver un caso de uso específico. No creo que sea correcto que haga una pregunta de la cual ya sé la respuesta (obviamente, responder preguntas propias está bien) – nhed

11

Existen dos elementos diferentes de este comportamiento. La primera es almacenar x en un valor de solo lectura, y la segunda es protegiendo el getter de ser alterado en subclases.


de sólo lectura valor

Es posible en Ruby para almacenar los valores de sólo lectura en tiempo de inicialización. Para hacer esto, usamos el comportamiento de cierre de los bloques de Ruby.

class Foo 
    def initialize (x) 
    define_singleton_method(:x) { x } 
    end 
end 

El valor inicial de x está ahora bloqueado en el interior del bloque se utilizó para definir el getter #x y nunca se puede acceder excepto llamando foo.x, y nunca puede ser alterado.

foo = Foo.new(2) 
foo.x # => 2 
foo.instance_variable_get(:@x) # => nil 

en cuenta que no se almacena como la variable de instancia @x, sin embargo, todavía está disponible a través del captador hemos creado usando define_singleton_method.


Protección del comprador

En Rubí, casi cualquier método de cualquier clase se pueden sobrescribir en tiempo de ejecución. Hay una forma de evitar esto utilizando el gancho method_added.

class Foo 
    def self.method_added (name) 
    raise(NameError, "cannot change x getter") if name == :x 
    end 
end 

class Bar < Foo 
    def x 
    20 
    end 
end 

# => NameError: cannot change x getter 

Este es un método muy severo para proteger el captador.

Requiere que se añade cada captador protegido al method_added gancho de forma individual, y aún así, se tendrá que añadir otro nivel de protección a method_addedFoo y sus subclases para evitar un codificador de sobrescribir el método method_added sí.

Es mejor aceptar que el reemplazo de código en tiempo de ejecución es una realidad cuando se usa Ruby.

+2

Tenga en cuenta que la definición de un método invalidará el caché de métodos de ruby. Si está creando muchos de estos, podría afectar negativamente el rendimiento. – Kelvin

+0

@ Kelvin, ese es un gran punto, gracias. Cualquier persona interesada en aprender más acerca de esta penalización de rendimiento en Ruby debe consultar este excelente informe: https://github.com/charliesome/charlie.bz/blob/master/posts/things-that-clear-rubys-method-cache. Maryland –

Cuestiones relacionadas