Domanda

Sto cercando di aggirare gli oggetti mutabili 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?

È stato utile?

Soluzione

Bene, ci sono un paio di aspetti in questo. Gli oggetti mutabili numero uno senza identità di riferimento possono causare bug in momenti strani. Ad esempio, considera un bean Person con un metodo uguale a basato sul valore:

Map<Person, String> map = ...
Person p = new Person();
map.put(p, "Hey, there!");

p.setName("Daniel");
map.get(p);       // => null

L'istanza Person viene " persa " nella mappa quando usato come chiave perché è hashCode e l'uguaglianza era basata su valori mutabili. Quei valori sono cambiati al di fuori della mappa e tutti gli hash sono diventati obsoleti. I teorici amano arpionare su questo punto, ma in pratica non ho riscontrato che si tratti di un problema eccessivo.

Un altro aspetto è la logica "ragionevolezza" del tuo codice. Questo è un termine difficile da definire, che comprende tutto, dalla leggibilità al flusso. Generalmente, 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 codici "domini", 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 complesse.

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 produttività e rende il codice drammaticamente 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à.

Altri suggerimenti

Oggetti immutabili contro 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 hai 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 la modifica. 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 adottano il concetto di immutabilità per obiettare i riferimenti stessi, consentendo solo una singola assegnazione di riferimenti.

  • 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 nella lingua con tutti i riferimenti che vengono dichiarati con var o val , vals essendo solo un compito e promuovendo uno stile funzionale, ma varia permettendo struttura del programma c-like o java-like.
  • È 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 di effetti collaterali e un semplice ragionamento 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 giocattoli .

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

Controlla questo post sul blog: http://www.yegor256.com/ 2014/06/09 / oggetti-devono-essere-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 thread-safe
  • aiutano a evitare l'accoppiamento temporale
  • il loro utilizzo è privo di effetti collaterali (nessuna copia difensiva)
  • si evita il problema della mutabilità dell'identità
  • hanno sempre un'atomicità di fallimento
  • sono molto più facili da memorizzare nella cache

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

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.

Vantaggio più grande: 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 adattarsi alle operazioni simboliche. Qualcosa da evitare in questo caso è restituire enormi blocchi di memoria da un metodo. In combinazione con operazioni simboliche concatenate, ciò potrebbe comportare un massiccio consumo di memoria e un degrado delle prestazioni.

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.

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'è alcuna azione "spettrale a distanza".

Un oggetto mutabile è semplicemente un oggetto che può essere modificato dopo che è stato creato / istanziato, rispetto a un oggetto immutabile che non può essere modificato (vedi 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 il loro posto.

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 foo abbia un campo int [] arr , e contenga un riferimento a un int [3] che contiene i numeri {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 foo desidera incapsulare arr valori diversi, deve sostituirlo con un array diverso che contiene i valori desiderati. Se si desidera creare una copia di foo , è possibile fornire alla copia un riferimento a arr o un nuovo array contenente i valori {1,2,3}, a seconda di è 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 foo vuole che incapsuli i valori 5, 8 e 9, può cambiare il secondo elemento in quell'array o creare un nuovo array con i valori 5, 8 e 9 e abbandonare il vecchio uno. Si noti che se si desidera creare una copia di foo , nella copia è necessario sostituire arr con un riferimento a un nuovo array per foo.arr per rimanere come unico riferimento a quell'array in qualsiasi parte dell'universo.

  • Un riferimento a un array di proprietà di alcuni altri oggetti che lo hanno esposto a foo per qualche motivo (ad es. forse vuole foo per memorizzare alcuni dati lì). In questo scenario, arr non incapsula il contenuto dell'array, ma piuttosto la sua identità . Poiché la sostituzione di arr con un riferimento a un nuovo array ne cambierebbe totalmente il significato, una copia di foo dovrebbe contenere un riferimento allo stesso array.

  • Un riferimento a un array di cui foo è l'unico proprietario, ma a cui i riferimenti sono conservati da altri oggetti per qualche motivo (ad es. vuole avere l'altro oggetto per archiviare dati lì - il rovescio della medaglia del caso precedente). In questo scenario, arr incapsula sia l'identità dell'array che il suo contenuto. Sostituire arr con un riferimento a un nuovo array cambierebbe totalmente il suo significato, ma avere arr di un clone fare riferimento a foo.arr violerebbe l'assunto che foo è l'unico proprietario. Non è quindi possibile copiare foo.

In teoria, int [] dovrebbe essere un bel tipo semplice e 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 della "potenza" di oggetti immutabili deriva da questo fatto.

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

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

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

Le primitive 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 finale non significa quasi immutabile per le variabili. Vedi esempio qui
http://www.siteconsortium.com/h/D0000F.php .

Le

mutabili istanze vengono passate per riferimento.

Le

immutabili istanze 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. Crea una scorciatoia per file txt e passa a te, oppure
  2. Prendi una copia per txtfile e copialo per te.

Nella prima modalità, txtfile restituito è un file mutabile, 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).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top