2012-02-13 4 views
9

pena por esta pregunta novato ... digamos que tenemos:Ruby: cómo evitar la modificación de una variable de instancia de matriz a través de un lector de atributo

class TestMe 
attr_reader :array 

def initialize 
    @array = (1..10).to_a 
end 

final

entonces es posible hacer:

>> a = TestMe.new 
=> #<TestMe:0x00000005567228 @x=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]> 
>> a.array.map! &:to_s 
=> ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] 
>> a.array 
=> ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] 
  • esto va claramente en contra de la encapsulación, doesn'it?
  • ¿hay alguna forma de proteger rápidamente la variable de matriz para que no se modifique?
  • ... ¿o necesito implementar un lector de copia profunda cada vez que la variable de mi instancia tenga métodos "destructivos"?

EDITAR leí en alguna parte que es "malo OO" para exponer una variable de instancia de matriz. Si es verdad, ¿por qué?

+2

Creo que su pregunta original responde a su pregunta de segundos sobre por qué exponer la variable de instancia de matriz no es tan buena. –

+0

@ KL-7: Totalmente: D –

Respuesta

11

No se puede hacer mucho con attr_reader, porque genera el siguiente código:

def array; @array; end 

Si no desea exponer instancia de matriz, puede volver enumerador de esta matriz (externo iterador). Enumerator es una buena abstracción de iterador y no le permite modificar la matriz original.

def array; @array.to_enum; end 

Lo bueno para la encapsulación y lo que no depende de la abstracción de sus regalos de clase. En general, esto no es bueno para la encapsulación para exponer el estado interno de un objeto, incluida la matriz interna. Es posible que desee exponer algunos métodos que operan en el @array en lugar de exponer @array (o incluso su iterador). A veces, esto está bien para exponer matriz: siempre mire la abstracción que presenta su clase.

+1

muchas respuestas válidas, pero yo elegí la suya por su explicación en OO. "estado interno de un objeto" me lo dejó claro ... soy autodidacta, así que todavía me cuesta mucho entender estos conceptos. Gracias. –

+0

+1, me gusta la idea de llamar 'to_enum' en la matriz. –

1

Cualquier instancia puede llegar a ser inmutable por congelación llamando en él:

class TestMe 
attr_reader :array 

def initialize 
    @array = (1..10).to_a 
    @array.freeze 
end 
end 

a = TestMe.new 
a.array << 11 
# Error: can't modify frozen array 
+0

mmm ... tal vez no he sido lo suficientemente claro. ¿Qué sucede si quiero que mi matriz siga siendo mutable, pero solo a través de un escritor y no a través del lector? –

+2

@m_x Entonces no use 'attr_reader' y defina getters y setters personalizados. –

5

¿Qué hay de devolver una copia de la matriz original del captador:

class TestMe 

    attr_writer :array 

    def initialize 
    @array = (1..10).to_a 
    end 

    def array 
    @array.dup 
    end 

end 

En ese caso no se puede modificar directamente matriz original pero con el escritor de atributos puede reemplazarla por la nueva (si es necesario).

1

Si desea que la matriz permanezca mutable, pero no cuando se devuelve a través del lector, no devuelva la matriz, sino solo un contenedor que expone métodos "seguros".

require 'forwardable' 
class SafeArray 
    extend Forwardable 
    def initialize(array); @array = array; end 
    # add the other methods you want to expose to the following line 
    def_delegators :@array, :size, :each, :[], :map 
end 

class TestMe 
    def initialize 
    @array = (1..10).to_a 
    end 
    def array 
    @wrapper ||= SafeArray.new(@array) 
    end 
end 
0

Esto va en contra de encapsulación, pero podemos resolver el problema mediante la regulación adecuada del getter method de ese atributo.

class TestMe 

def initialize 
    @array = (1..10).to_a 
end 

def array 
    Array.new(@array) 
end 

end 
0

Encapsula creando un método con el mismo nombre que la variable de instancia pero que finaliza con el signo igual.En su ejemplo sería:

def array= 
.. 
end 

En ese método que haces lo que sea que quieres hacer antes de asignar nuevos valores a la matriz

1

Creación de un método lector de atributo específico que copia las obras de atributos originales bien, pero tenga en cuenta que ni@array.dupniArray.new(@array) realizará una copia profunda . Lo que significa que si tiene una matriz de matrices (como [[1, 2], [3, 4]]), ninguno de los valores de la matriz estará protegido contra cambios. Para realizar una deepcopy en rubí, la manera más fácil que encontré fue esto:

return Marshal.load(Marshal.dump(@array)) 

Marshall.dump transforma cualquier objeto en una cadena que luego puede ser decodificado para obtener el objeto de nuevo (el proceso se llama serialización). De esta manera, obtiene una copia profunda del objeto dado. Fácil pero un poco sucio, lo tengo que admitir.

Cuestiones relacionadas