2012-02-13 12 views
9

dispiace per questa domanda niubbo ... diciamo abbiamo:rubino: come impedire la modifica di una variabile di istanza matrice attraverso un lettore attributo

class TestMe 
attr_reader :array 

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

fine

è poi possibile fare:

>> 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"] 
  • questo va chiaramente contro l'incapsulamento, doesn'it?
  • esiste un modo per proteggere rapidamente la variabile di matrice dalla modifica?
  • ... o devo implementare un lettore di copia profonda ogni volta che la mia variabile di istanza ha metodi "distruttivi"?

EDIT ho letto da qualche parte che è "cattivo OO" per esporre una variabile di istanza di matrice. Se è vero, perché?

+2

Penso che la tua domanda iniziale risponda alla tua seconda domanda sul perché esporre la variabile di istanza dell'array non è così buona. –

+0

@ KL-7: Totalmente: D –

risposta

11

Non si può fare molto con attr_reader, perché genera il seguente codice:

def array; @array; end 

Se non si vuole esporre esempio array, è possibile tornare Enumeratore di questo array (esterno iterator). Enumerator è una buona astrazione iteratore e non consente di modificare la matrice originale.

def array; @array.to_enum; end 

Che cosa buona per l'incapsulamento e ciò che non dipende dall'astrazione che la classe presenta. Generalmente questo non è un bene per l'incapsulamento per esporre lo stato interno di un oggetto, incluso l'array interno. Si consiglia di esporre alcuni metodi che operano su @array invece di esporre @array (o anche il suo iteratore) stesso. A volte va bene esporre l'array - guarda sempre all'astrazione che la tua classe presenta.

+1

molte risposte valide, ma ho scelto il tuo a causa della tua spiegazione su OO. "lo stato interno di un oggetto" mi ha chiarito ... sono autodidatta, quindi ho ancora difficoltà a ripensare a questi concetti. Grazie. –

+0

+1, mi piace l'idea di chiamare 'to_enum' sull'array. –

1

Qualsiasi istanza può diventare immutabile da congelamento chiamando su di esso:

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 ... forse non sono stato abbastanza chiaro. Cosa succede se voglio che il mio array sia ancora mutevole, ma solo attraverso uno scrittore e non attraverso il lettore? –

+2

@m_x Quindi non usare 'attr_reader' e definire getter e setter personalizzati. –

5

ne dite di restituire una copia della matrice originale getter:

class TestMe 

    attr_writer :array 

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

    def array 
    @array.dup 
    end 

end 

In questo caso non è possibile modificare direttamente array originale ma con writer degli attributi è possibile sostituirlo con quello nuovo (se necessario).

1

Se si desidera che l'array rimanga mutabile, ma non quando viene restituito tramite il lettore, non restituire l'array, ma solo un wrapper che espone metodi "sicuri".

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

Questo è contro l'incapsulamento, ma siamo in grado di risolvere il problema sintonizzando adeguatamente il getter method di quell'attributo.

class TestMe 

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

def array 
    Array.new(@array) 
end 

end 
0

Si incapsula creando un metodo con lo stesso nome della variabile di istanza ma facendolo terminare con il segno di uguale.Nel tuo esempio sarebbe:

def array= 
.. 
end 

In questo metodo si fa qualunque cosa è che si vuole fare prima di assegnare nuovi valori alla matrice

1

La creazione di un metodo specifico lettore di attributo che copia le opere degli attributi originali bene, ma tieni presente che @array.dupArray.new(@array) eseguirà una copia profonda . Il che significa che se hai una matrice di matrici (come [[1, 2], [3, 4]]), nessuno dei valori dell'array sarà protetto dalle modifiche. Per eseguire un deepcopy rubino, il modo più semplice ho trovato è stato questo:

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

Marshall.dump trasforma qualsiasi oggetto in una stringa che può successivamente essere decodificato per ottenere l'oggetto posteriore (il processo è chiamato serializzazione). In questo modo, si ottiene una copia profonda dell'oggetto dato. Facile ma un po 'sporco, devo ammettere.

Problemi correlati