Oggetti mutabili vs immutabili


173

Sto cercando di orientarmi tra oggetti mutevoli e immutabili. L'uso di oggetti mutabili genera molta cattiva qualità (ad es. Restituendo una matrice di stringhe da un metodo) ma ho difficoltà a capire quali sono gli impatti negativi di questo. Quali sono le migliori pratiche sull'uso di oggetti mutabili? Dovresti evitarli quando possibile?


stringè immutabile, almeno in .NET, e penso anche in molti altri linguaggi moderni.
Domenic,

4
dipende da cosa sia realmente una stringa nella lingua - erlang 'stringhe' sono solo una matrice di ints e haskell "stringhe" sono matrici di caratteri.
Chii,

4
Le stringhe di Ruby sono un array mutabile di byte. Mi fa assolutamente impazzire il fatto che tu possa cambiarli sul posto.
Daniel Spiewak,

6
Daniel - perché? Ti ritrovi a farlo per caso?

5
@DanielSpiewak La soluzione per far impazzire che puoi cambiare le stringhe sul posto è semplice: non farlo. La soluzione per essere impazziti perché non puoi cambiare le stringhe sul posto non è così semplice.
Kaz

Risposte:


162

Bene, ci sono alcuni aspetti in questo.

  1. Oggetti mutabili senza identità di riferimento possono causare bug in momenti strani. Ad esempio, considera un Personbean con un equalsmetodo basato sul valore :

    Map<Person, String> map = ...
    Person p = new Person();
    map.put(p, "Hey, there!");
    
    p.setName("Daniel");
    map.get(p);       // => null
    

    L' Personistanza viene "persa" nella mappa se utilizzata come chiave perché la sua hashCodee l'uguaglianza erano basate su valori mutabili. Quei valori sono cambiati al di fuori della mappa e tutti gli hash sono diventati obsoleti. Ai teorici piace arpionare su questo punto, ma in pratica non ho trovato che fosse un problema troppo grande.

  2. Un altro aspetto è la "ragionevolezza" logica del tuo codice. Questo è un termine difficile da definire, che comprende tutto, dalla leggibilità al flusso. In generale, dovresti essere in grado di guardare un pezzo di codice e capire facilmente cosa fa. Ma più importante di questo, dovresti essere in grado di convincerti che fa ciò che fa correttamente . Quando gli oggetti possono cambiare in modo indipendente attraverso diversi "domini" di codice, a volte diventa difficile tenere traccia di ciò che è dove e perché (" azione spettrale a distanza "). Questo è un concetto più difficile da esemplificare, ma è qualcosa che viene spesso affrontato in architetture più grandi e più complesse.

  3. Infine, gli oggetti mutabili sono killer in situazioni simultanee. Ogni volta che accedi a un oggetto mutevole da thread separati, devi occuparti del blocco. Ciò riduce la velocità effettiva e rende il codice molto più difficile da mantenere. Un sistema sufficientemente complicato fa esplodere questo problema così tanto da renderlo quasi impossibile da mantenere (anche per gli esperti di concorrenza).

Gli oggetti immutabili (e più in particolare le raccolte immutabili) evitano tutti questi problemi. Una volta che hai capito come funzionano, il tuo codice si svilupperà in qualcosa che è più facile da leggere, più facile da mantenere e meno probabilità di fallire in modi strani e imprevedibili. Gli oggetti immutabili sono ancora più facili da testare, non solo per la loro facile derisione, ma anche per i modelli di codice che tendono ad applicare. In breve, sono buone pratiche dappertutto!

Detto questo, difficilmente sono uno zelota in questa materia. Alcuni problemi non modellano bene quando tutto è immutabile. Ma penso che dovresti provare a spingere il più possibile il codice in quella direzione, supponendo ovviamente che stai usando un linguaggio che lo rende un'opinione sostenibile (C / C ++ lo rende molto difficile, così come Java) . In breve: i vantaggi dipendono in qualche modo dal tuo problema, ma tenderei a preferire l'immutabilità.


11
Ottima risposta Una piccola domanda però: il C ++ non ha un buon supporto per l'immutabilità? La funzionalità di correttezza const non è sufficiente?
Dimitri C.

1
@DimitriC .: Il C ++ in realtà ha alcune caratteristiche più fondamentali, in particolare una migliore distinzione tra posizioni di memoria che dovrebbero incapsulare lo stato non condiviso da quelle che incapsulano l'identità dell'oggetto.
Supercat,

2
Nel caso della programmazione Java, Joshua Bloch porta buone spiegazioni su questo argomento nel suo famoso libro Effective Java (Item 15)
dMathieuD

27

Oggetti immutabili vs. collezioni immutabili

Uno dei punti più fini del dibattito sugli oggetti mutabili vs. immutabili è la possibilità di estendere il concetto di immutabilità alle collezioni. Un oggetto immutabile è un oggetto che spesso rappresenta una singola struttura logica di dati (ad esempio una stringa immutabile). Quando si ha un riferimento a un oggetto immutabile, il contenuto dell'oggetto non cambierà.

Una collezione immutabile è una collezione che non cambia mai.

Quando eseguo un'operazione su una raccolta mutabile, quindi cambio la raccolta in atto e tutte le entità che hanno riferimenti alla raccolta vedranno la modifica.

Quando eseguo un'operazione su una raccolta immutabile, un riferimento viene restituito a una nuova raccolta che riflette il cambiamento. Tutte le entità che hanno riferimenti a versioni precedenti della raccolta non vedranno la modifica.

Le implementazioni intelligenti non devono necessariamente copiare (clonare) l'intera raccolta per fornire quell'immutabilità. L'esempio più semplice è lo stack implementato come un elenco collegato singolarmente e le operazioni push / pop. È possibile riutilizzare tutti i nodi della raccolta precedente nella nuova raccolta, aggiungendo un solo nodo per il push e non clonando nodi per il pop. L'operazione push_tail su un elenco collegato singolarmente, d'altra parte, non è così semplice o efficiente.

Variabili / riferimenti immutabili vs. mutabili

Alcuni linguaggi funzionali prendono il concetto di immutabilità per obiettare i riferimenti stessi, consentendo solo una singola assegnazione di riferimento.

  • In Erlang questo vale per tutte le "variabili". Posso assegnare oggetti a un riferimento solo una volta. Se dovessi operare su una raccolta, non sarei in grado di riassegnare la nuova raccolta al vecchio riferimento (nome della variabile).
  • Scala incorpora anche questo nel linguaggio con tutti i riferimenti che vengono dichiarati con var o val , vals è solo una singola assegnazione e promuove uno stile funzionale, ma varia permettendo una struttura di programma più simile a C o Java.
  • È richiesta la dichiarazione var / val, mentre molte lingue tradizionali usano modificatori opzionali come final in java e const in C.

Facilità di sviluppo vs. prestazioni

Quasi sempre la ragione per usare un oggetto immutabile è promuovere la programmazione libera da effetti collaterali e semplici ragionamenti sul codice (specialmente in un ambiente altamente concorrente / parallelo). Non devi preoccuparti che i dati sottostanti vengano modificati da un'altra entità se l'oggetto è immutabile.

Lo svantaggio principale è rappresentato dalle prestazioni. Ecco un articolo su un semplice test che ho fatto in Java confrontando alcuni oggetti immutabili e mutabili in un problema di giocattolo.

I problemi di prestazioni sono controversi in molte applicazioni, ma non in tutti, motivo per cui molti pacchetti numerici di grandi dimensioni, come la classe Numpy Array in Python, consentono aggiornamenti sul posto di array di grandi dimensioni. Ciò sarebbe importante per le aree di applicazione che fanno uso di operazioni a matrice e vettoriali di grandi dimensioni. Questi grandi problemi legati al parallelo dei dati e ad alta intensità computazionale ottengono una grande accelerazione operando sul posto.


12

Controlla questo post sul blog: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . Spiega perché gli oggetti immutabili sono meglio che mutabili. In breve:

  • gli oggetti immutabili sono più semplici da costruire, testare e utilizzare
  • oggetti veramente immutabili sono sempre sicuri per i thread
  • aiutano a evitare l'accoppiamento temporale
  • il loro utilizzo è privo di effetti collaterali (nessuna copia difensiva)
  • il problema di mutabilità dell'identità è evitato
  • hanno sempre un fallimento nell'atomicità
  • sono molto più facili da memorizzare nella cache

10

Gli oggetti immutabili sono un concetto molto potente. Riducono molto l'onere di cercare di mantenere coerenti oggetti / variabili per tutti i clienti.

Puoi usarli per oggetti di basso livello, non polimorfici - come una classe CPoint - che sono usati principalmente con la semantica del valore.

Oppure puoi usarli per interfacce polimorfiche di alto livello - come una IFunction che rappresenta una funzione matematica - che viene utilizzata esclusivamente con la semantica degli oggetti.

Il più grande vantaggio: immutabilità + semantica dell'oggetto + puntatori intelligenti rendono la proprietà dell'oggetto un problema, tutti i client dell'oggetto hanno la loro copia privata per impostazione predefinita. Implicitamente questo significa anche un comportamento deterministico in presenza di concorrenza.

Svantaggio: se utilizzato con oggetti contenenti molti dati, il consumo di memoria può diventare un problema. Una soluzione a questo potrebbe essere quella di mantenere le operazioni su un oggetto simbolico e fare una valutazione pigra. Tuttavia, ciò può quindi portare a catene di calcoli simbolici, che possono influenzare negativamente le prestazioni se l'interfaccia non è progettata per ospitare operazioni simboliche. Qualcosa da evitare in questo caso è restituire enormi quantità di memoria da un metodo. In combinazione con operazioni simboliche concatenate, ciò potrebbe comportare un massiccio consumo di memoria e un peggioramento delle prestazioni.

Quindi gli oggetti immutabili sono sicuramente il mio modo principale di pensare al design orientato agli oggetti, ma non sono un dogma. Risolvono molti problemi per i clienti degli oggetti, ma ne creano anche molti, specialmente per gli implementatori.


Penso di aver frainteso la Sezione 4 per il massimo vantaggio: immutabilità + semantica dell'oggetto + puntatori intelligenti rendono la proprietà dell'oggetto un punto controverso. Quindi è discutibile? Penso che tu stia usando "moot" in modo errato ... Visto che la frase successiva è che l'oggetto ha implicato un "comportamento deterministico" dal suo comportamento "moot" (discutibile).
Benjamin,

Hai ragione, ho usato 'moot' in modo errato. Considera che è cambiato :)
QBziZ il

6

Devi specificare la lingua di cui stai parlando. Per linguaggi di basso livello come C o C ++, preferisco usare oggetti mutabili per risparmiare spazio e ridurre la memoria. Nei linguaggi di livello superiore, gli oggetti immutabili rendono più facile ragionare sul comportamento del codice (in particolare il codice multi-thread) perché non c'è "azione spettrale a distanza".


Stai suggerendo che i thread sono quanticamente intrecciati? Questo è piuttosto un tratto :) I fili sono in realtà, se ci pensate, abbastanza vicino ad essere intrappolati. Una modifica apportata da un thread influisce sull'altro. +1
ndrewxie,

4

Un oggetto mutabile è semplicemente un oggetto che può essere modificato dopo che è stato creato / creato un'istanza, rispetto a un oggetto immutabile che non può essere modificato (vedere la pagina Wikipedia sull'argomento). Un esempio di ciò in un linguaggio di programmazione sono gli elenchi e le tuple di Pythons. Gli elenchi possono essere modificati (ad es. Nuovi elementi possono essere aggiunti dopo la creazione) mentre le tuple non possono.

Non credo davvero che ci sia una risposta chiara su quale sia la migliore per tutte le situazioni. Entrambi hanno i loro posti.


1

Se un tipo di classe è mutabile, una variabile di quel tipo di classe può avere un numero di significati diversi. Ad esempio, supponiamo che un oggetto fooabbia un campo int[] arre contenga un riferimento a un numero di int[3]trattenimento {5, 7, 9}. Anche se il tipo di campo è noto, ci sono almeno quattro cose diverse che può rappresentare:

  • Un riferimento potenzialmente condiviso, a tutti i cui detentori interessa solo che incapsuli i valori 5, 7 e 9. Se si foodesidera arrincapsulare valori diversi, è necessario sostituirlo con un array diverso che contenga i valori desiderati. Se si desidera fare una copia di foo, si può dare alla copia un riferimento arro un nuovo array contenente i valori {1,2,3}, a seconda di quale sia più conveniente.

  • L'unico riferimento, ovunque nell'universo, a una matrice che incapsula i valori 5, 7 e 9. set di tre posizioni di memoria che al momento contengono i valori 5, 7 e 9; se lo foodesidera incapsula i valori 5, 8 e 9, può modificare il secondo elemento in quell'array o creare un nuovo array contenente i valori 5, 8 e 9 e abbandonare quello precedente. Si noti che se si voleva fare una copia di foo, si deve sostituire nella copia arrcon un riferimento a un nuovo array per foo.arrrimanere come unico riferimento a tale array ovunque nell'universo.

  • Un riferimento a un array di proprietà di qualche altro oggetto a cui è stato esposto fooper qualche motivo (ad esempio, forse vuole fooarchiviare alcuni dati lì). In questo scenario, arrnon incapsula il contenuto dell'array, ma piuttosto la sua identità . Poiché la sostituzione arrcon un riferimento a un nuovo array ne cambierebbe totalmente il significato, una copia di foodovrebbe contenere un riferimento allo stesso array.

  • Un riferimento a una matrice di cui fooè l'unico proprietario, ma per cui i riferimenti sono conservati da un altro oggetto per qualche motivo (ad esempio, desidera avere l'altro oggetto per archiviare i dati lì - il rovescio della medaglia del caso precedente). In questo scenario, arrincapsula sia l'identità dell'array che i suoi contenuti. Sostituire arrcon un riferimento a un nuovo array cambierebbe totalmente il suo significato, ma avere un arrriferimento a un clone foo.arrviolerebbe l'assunto che fooè l'unico proprietario. Non è quindi possibile copiare foo.

In teoria, int[]dovrebbe essere un bel tipo semplice ben definito, ma ha quattro significati molto diversi. Al contrario, un riferimento a un oggetto immutabile (ad es. String) Generalmente ha un solo significato. Gran parte del "potere" di oggetti immutabili deriva da questo fatto.


1

Le istanze mutabili vengono passate per riferimento.

Le istanze immutabili vengono passate per valore.

Esempio astratto Supponiamo che esista un file chiamato txtfile nel mio HDD. Ora, quando mi chiedi txtfile , posso restituirlo in due modi:

  1. Creare un collegamento al file txt e passare il collegamento a te, oppure
  2. Prendi una copia per txtfile e pas copia per te.

Nella prima modalità, txtfile restituito è un file modificabile, perché quando si apportano modifiche al file di scelta rapida, si apportano anche modifiche al file originale. Il vantaggio di questa modalità è che ogni scorciatoia restituita richiede meno memoria (su RAM o in HDD) e lo svantaggio è che tutti (non solo io, il proprietario) hanno le autorizzazioni per modificare il contenuto del file.

Nella seconda modalità, il file txt restituito è un file immutabile, poiché tutte le modifiche al file ricevuto non si riferiscono al file originale. Il vantaggio di questa modalità è che solo io (proprietario) posso modificare il file originale e lo svantaggio è che ogni copia restituita richiede memoria (nella RAM o nell'HDD).


Questo non è strettamente vero. Le istanze immutabili possono infatti essere passate per riferimento e spesso lo sono. La copia viene eseguita principalmente quando è necessario aggiornare l'oggetto o la struttura dei dati.
Jamon Holmgren,

0

Se si restituiscono riferimenti di una matrice o stringa, il mondo esterno può modificare il contenuto di quell'oggetto e quindi renderlo come oggetto modificabile (modificabile).


0

I mezzi immutabili non possono essere cambiati, e mutevole significa che puoi cambiare.

Gli oggetti sono diversi dai primitivi in ​​Java. Le primitive sono di tipo incorporato (booleano, int, ecc.) E gli oggetti (classi) sono tipi creati dall'utente.

I primitivi e gli oggetti possono essere mutabili o immutabili quando definiti come variabili membro all'interno dell'implementazione di una classe.

Molte persone pensano che le primitive e le variabili oggetto che hanno un modificatore finale davanti a loro siano immutabili, tuttavia, questo non è esattamente vero. Quindi final non significa quasi immutabile per le variabili. Vedi esempio qui
http://www.siteconsortium.com/h/D0000F.php .


0

Immutable object- è uno stato dell'oggetto che non può essere modificato dopo la creazione. L'oggetto è immutabile quando tutti i suoi campi sono immutabili

Discussione sicura

Il vantaggio principale di un oggetto immutabile è che è un ambiente naturale per ambienti concorrenti. Il problema più grande in concorrenza è shared resourceche può essere modificato qualsiasi thread. Ma se un oggetto è immutabile, si read-onlytratta dell'operazione thread-safe. Qualsiasi modifica di un oggetto immutabile originale restituisce una copia

senza effetti collaterali

Come sviluppatore sei completamente sicuro che lo stato dell'oggetto immutabile non può essere modificato da nessun luogo (di proposito o meno)

compilazione ottimizzazione

Migliorare la prestazione

Svantaggio:

La copia di un oggetto è un'operazione più pesante rispetto alla modifica di un oggetto mutabile, ecco perché ha un certo impatto sulle prestazioni

Per creare un immutableoggetto devi usare:

  1. Livello di lingua. Ogni lingua contiene strumenti per aiutarti. Ad esempio Java ha finale primitives, Swift ha lete struct[Informazioni] . La lingua definisce un tipo di variabile. Ad esempio Java ha primitivee referencedigita, Swift ha valuee referencedigita [Informazioni su] . Per gli oggetti immutabili è più conveniente primitivese valuedigitare quale crea una copia per impostazione predefinita. Per quanto riguarda il referencetipo è più difficile (perché sei in grado di cambiare lo stato dell'oggetto da esso) ma possibile. Ad esempio è possibile utilizzare il clonepattern a livello di sviluppatore

  2. Livello sviluppatore. Come sviluppatore non dovresti fornire un'interfaccia per cambiare stato

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.