Se gli oggetti immutabili sono buoni, perché le persone continuano a creare oggetti mutabili? [chiuso]


250

Se gli oggetti immutabili¹ sono buoni, semplici e offrono vantaggi nella programmazione concorrente, perché i programmatori continuano a creare oggetti mutabili²?

Ho quattro anni di esperienza nella programmazione Java e, a mio modo di vedere, la prima cosa che le persone fanno dopo aver creato una classe è generare getter e setter nell'IDE (rendendolo così mutevole). C'è una mancanza di consapevolezza o possiamo cavarcela usando oggetti mutabili nella maggior parte degli scenari?


¹ L' oggetto immutabile è un oggetto il cui stato non può essere modificato dopo la sua creazione.
² L' oggetto mutabile è un oggetto che può essere modificato dopo la sua creazione.


42
Penso che, a parte i motivi legittimi (come menzionato da Péter di seguito), "sviluppatore pigro" è una ragione più comune di "stupido" sviluppatore ". E prima di" stupido sviluppatore "c'è anche" sviluppatore non informato ".
Joachim Sauer

201
Per ogni programmatore / blogger evangelico ci sono 1000 avidi lettori di blog che si reinventano immediatamente e adottano le ultime tecniche. Per ognuno di questi ci sono 10.000 programmatori là fuori con il loro naso alla pietra miliare che fanno un giorno di lavoro e portano il prodotto fuori dalla porta. Quei ragazzi stanno usando tecniche provate e affidabili che hanno funzionato per loro per anni. Attendono che le nuove tecniche siano ampiamente adottate e mostrino benefici effettivi prima di metterle in pratica. Non chiamarli stupidi, e sono tutt'altro che pigri, chiamali invece "occupati".
Binary Worrier,

22
@BinaryWorrier: gli oggetti immutabili non sono certo una "cosa nuova". Potrebbero non essere stati usati pesantemente per gli oggetti di dominio, ma Java e C # li avevano fin dall'inizio. Inoltre: "pigro" non è sempre una parolaccia, alcuni tipi di "pigro" sono un vantaggio assoluto per uno sviluppatore.
Joachim Sauer,

11
@Joachim: Penso che sia abbastanza ovvio che "pigro" è stato usato nel suo senso peggiorativo sopra :) Inoltre, gli oggetti immutabili (come Lambda Calculus e OOP in passato - sì, sono così vecchio) non devono essere nuovi per diventare improvvisamente il sapore del mese . Non sto sostenendo che sono una brutta cosa (non lo sono), o che non hanno il loro posto (ovviamente lo fanno), andate piano con la gente perché non hanno sentito l'ultima buona parola e stato convertito ferventemente (non incolpando te per il commento "pigro", so che hai provato a mitigarlo).
Binary Worrier,

116
-1, gli oggetti immutabili non sono "buoni". Solo più o meno appropriato per situazioni specifiche. Chiunque ti dica una tecnica o un'altra è oggettivamente "buono" o "cattivo" su un altro per tutte le situazioni, ti sta vendendo la religione.
GrandmasterB,

Risposte:


326

Sia gli oggetti mutabili che quelli immutabili hanno i loro usi, pro e contro.

Gli oggetti immutabili rendono la vita più semplice in molti casi. Sono particolarmente applicabili per i tipi di valore, in cui gli oggetti non hanno un'identità e possono quindi essere facilmente sostituiti. E possono rendere la programmazione concorrente più sicura e più pulita (la maggior parte dei bug notoriamente difficili da trovare sono in definitiva causati da uno stato mutevole condiviso tra i thread). Tuttavia, per oggetti di grandi dimensioni e / o complessi, la creazione di una nuova copia dell'oggetto per ogni singola modifica può essere molto costosa e / o noiosa. E per gli oggetti con un'identità distinta, cambiare un oggetto esistente è molto più semplice e intuitivo che crearne una nuova copia modificata.

Pensa a un personaggio del gioco. Nei giochi, la velocità è la massima priorità, quindi rappresentare i tuoi personaggi di gioco con oggetti mutabili molto probabilmente renderà il tuo gioco molto più veloce di un'implementazione alternativa in cui viene generata una nuova copia del personaggio di gioco per ogni piccola modifica.

Inoltre, la nostra percezione del mondo reale si basa inevitabilmente su oggetti mutevoli. Quando riempi la tua auto con benzina alla stazione di servizio, la percepisci sempre come lo stesso oggetto (cioè la sua identità viene mantenuta mentre il suo stato sta cambiando) - non come se la vecchia auto con un serbatoio vuoto fosse sostituita con una nuova consecutiva le istanze di automobili hanno il serbatoio gradualmente sempre più pieno. Quindi ogni volta che modelliamo un dominio del mondo reale in un programma, di solito è più semplice e più semplice implementare il modello di dominio usando oggetti mutabili per rappresentare entità del mondo reale.

A parte tutte queste ragioni legittime, purtroppo, la causa più probabile per cui le persone continuano a creare oggetti mutevoli è l'inerzia della mente, ovvero la resistenza al cambiamento. Nota che la maggior parte degli sviluppatori di oggi è stata addestrata molto prima che l'immutabilità (e il paradigma contenitivo, la programmazione funzionale) diventassero "di tendenza" nella loro sfera di influenza e non mantengano aggiornate le loro conoscenze sui nuovi strumenti e metodi del nostro commercio - infatti, molti di noi umani resistono positivamente a nuove idee e processi. "Ho programmato così per nn anni e non mi importa delle ultime stupide mode!"


27
Giusto. Soprattutto nella programmazione della GUI, gli oggetti mutabili sono molto utili.
Florian Salihovic,

23
Non è solo resistenza, sono sicuro che molti sviluppatori adorerebbero provare gli ultimi e i più grandi, ma con quale frequenza si sviluppano nuovi progetti nell'ambiente degli sviluppatori medi dove possono applicare queste nuove pratiche? Nessuno potrà mai scrivere un progetto per hobby solo per provare lo stato immutabile.
Steven Evers,

29
Due piccole avvertenze: (1) Prendi il personaggio in movimento. La Pointclasse in .NET, ad esempio, è immutabile, ma la creazione di nuovi punti a seguito di modifiche è semplice e quindi accessibile. L'animazione di un personaggio immutabile può essere resa molto economica disaccoppiando le "parti in movimento" (ma sì, alcuni aspetti sono quindi mutabili). (2) "oggetti grandi e / o complessi" possono benissimo essere immutabili. Le stringhe sono spesso grandi e di solito beneficiano dell'immutabilità. Una volta ho riscritto una classe di grafici complessi per renderli immutabili, rendendo il codice più semplice ed efficiente. In tali casi, la chiave è avere un costruttore mutevole.
Konrad Rudolph,

4
@KonradRudolph, buoni punti, grazie. Non intendevo escludere l'uso dell'immutabilità in oggetti complessi, ma l'implementazione di tale classe in modo corretto ed efficiente è lungi dall'essere un compito banale e lo sforzo extra richiesto potrebbe non essere sempre giustificato.
Péter Török,

6
Hai un buon punto su stato vs identità. Questo è il motivo per cui Rich Hickey (autore di Clojure) ha diviso i due in Clojure. Si potrebbe sostenere che l'auto che hai con 1/2 serbatoio di gas non è la stessa auto con 1/4 di serbatoio di gas. Hanno la stessa identità, ma non sono la stessa, ogni "segno di spunta" del tempo della nostra realtà crea un clone di ogni oggetto nel nostro mondo, i nostri cervelli poi semplicemente li uniscono con un'identità comune. Clojure ha riferimenti, atomi, agenti, ecc. Per rappresentare il tempo. E mappe, vettori ed elenchi per l'ora reale.
Timothy Baldridge,

130

Penso che tutti abbiano perso la risposta più ovvia. La maggior parte degli sviluppatori crea oggetti mutabili perché la mutabilità è l'impostazione predefinita nei linguaggi imperativi. La maggior parte di noi ha cose migliori da fare con il nostro tempo che modificare costantemente il codice lontano dalle impostazioni predefinite - più corretto o meno. E l'immutabilità non è una panacea più di ogni altro approccio. Rende alcune cose più semplici, ma altre molto più difficili come alcune risposte hanno già sottolineato.


9
Nella maggior parte degli IDE moderni che conosco, ci vuole praticamente lo stesso sforzo per generare solo getter, così come generare sia getter che setter. Anche se è vero che aggiungere final, constecc. Richiede un po 'di sforzo in più ... a meno che tu non abbia impostato un modello di codice :-)
Péter Török

10
@ PéterTörök non è solo uno sforzo aggiuntivo, è il fatto che i tuoi colleghi programmatori vorranno appenderti in effigie perché trovano il tuo stile di programmazione così estraneo alla loro esperienza. Ciò scoraggia anche questo genere di cose.
Onorio Catenacci,

5
Ciò potrebbe essere superato con maggiore comunicazione ed educazione, ad esempio è consigliabile parlare o fare una presentazione ai propri compagni di squadra su un nuovo stile di codifica prima di introdurlo effettivamente in una base di codice esistente. È vero che un buon progetto ha uno stile di codifica comune che non è solo un amalgama degli stili di codifica preferiti da ogni membro del progetto (passato e presente). Quindi introdurre o modificare gli idiomi di codifica dovrebbe essere una decisione del team.
Péter Török,

3
@ PéterTörök In un linguaggio imperativo gli oggetti mutabili sono il comportamento predefinito. Se vuoi solo oggetti immutabili, è meglio passare a un linguaggio funzionale.
emory

3
@ PéterTörök È un po 'ingenuo presumere che incorporare l'immutabilità in un programma non implichi altro che far cadere tutti i setter. Hai ancora bisogno di un modo per cambiare progressivamente lo stato del programma, e per questo hai bisogno di costruttori, immutabilità dei ghiacci o proxy mutabili, che richiedono tutti circa 1 miliardo di volte lo sforzo che ha fatto solo l'abbandono dei setter.
Asad Saeeduddin,

49
  1. C'è un posto per la mutabilità. I principi di progettazione basati sul dominio forniscono una solida comprensione di ciò che dovrebbe essere mutabile e ciò che dovrebbe essere immutabile. Se ci pensate, vi renderete conto che non è pratico concepire un sistema in cui ogni cambiamento di stato in un oggetto richiede la distruzione e la ricomposizione di esso, e per ogni oggetto che lo ha fatto riferimento. Con sistemi complessi questo potrebbe facilmente portare a cancellare e ricostruire completamente il grafico degli oggetti dell'intero sistema

  2. La maggior parte degli sviluppatori non produce nulla in cui i requisiti di prestazione siano abbastanza significativi da dover concentrarsi sulla concorrenza (o su molti altri problemi che sono universalmente considerati buone pratiche dagli informati).

  3. Ci sono alcune cose che semplicemente non puoi fare con oggetti immutabili, come avere relazioni bidirezionali. Una volta impostato un valore di associazione su un oggetto, la sua identità cambia. Quindi, imposti il ​​nuovo valore sull'altro oggetto e anche questo cambia. Il problema è che il riferimento del primo oggetto non è più valido, poiché è stata creata una nuova istanza per rappresentare l'oggetto con il riferimento. Continuando questo si tradurrebbe in infinite regressioni. Ho fatto un piccolo caso di studio dopo aver letto la tua domanda, ecco come appare. Hai un approccio alternativo che consente tale funzionalità mantenendo l'immutabilità?

        public class ImmutablePerson { 
    
         public ImmutablePerson(string name, ImmutableEventList eventsToAttend)
         {
              this.name = name;
              this.eventsToAttend = eventsToAttend;
         }
         private string name;
         private ImmutableEventList eventsToAttend;
    
         public string Name { get { return this.name; } }
    
         public ImmutablePerson RSVP(ImmutableEvent immutableEvent){
             // the person is RSVPing an event, thus mutating the state 
             // of the eventsToAttend.  so we need a new person with a reference
             // to the new Event
             ImmutableEvent newEvent = immutableEvent.OnRSVPReceived(this);
             ImmutableEventList newEvents = this.eventsToAttend.Add(newEvent));
             var newSelf = new ImmutablePerson(name, newEvents);
             return newSelf;
         }
        }
    
        public class ImmutableEvent { 
         public ImmutableEvent(DateTime when, ImmutablePersonList peopleAttending, ImmutablePersonList peopleNotAttending){
             this.when = when;     
             this.peopleAttending = peopleAttending;
             this.peopleNotAttending = peopleNotAttending;
         }
         private DateTime when; 
         private ImmutablePersonList peopleAttending;
         private ImmutablePersonList peopleNotAttending;
         public ImmutableEvent OnReschedule(DateTime when){
               return new ImmutableEvent(when,peopleAttending,peopleNotAttending);
         }
         //  notice that this will be an infinite loop, because everytime one counterpart
         //  of the bidirectional relationship is added, its containing object changes
         //  meaning it must re construct a different version of itself to 
         //  represent the mutated state, the other one must update its
         //  reference thereby obsoleting the reference of the first object to it, and 
         //  necessitating recursion
         public ImmutableEvent OnRSVPReceived(ImmutablePerson immutablePerson){
               if(this.peopleAttending.Contains(immutablePerson)) return this;
               ImmutablePersonList attending = this.peopleAttending.Add(immutablePerson);
               ImmutablePersonList notAttending = this.peopleNotAttending.Contains( immutablePerson ) 
                                    ? peopleNotAttending.Remove(immutablePerson)
                                    : peopleNotAttending;
               return new ImmutableEvent(when, attending, notAttending);
         }
        }
        public class ImmutablePersonList
        {
          private ImmutablePerson[] immutablePeople;
          public ImmutablePersonList(ImmutablePerson[] immutablePeople){
              this.immutablePeople = immutablePeople;
          }
          public ImmutablePersonList Add(ImmutablePerson newPerson){
              if(this.Contains(newPerson)) return this;
              ImmutablePerson[] newPeople = new ImmutablePerson[immutablePeople.Length];
              for(var i=0;i<immutablePeople.Length;i++)
                  newPeople[i] = this.immutablePeople[i];
              newPeople[immutablePeople.Length] = newPerson;
          }
          public ImmutablePersonList Remove(ImmutablePerson newPerson){
              if(immutablePeople.IndexOf(newPerson) != -1)
              ImmutablePerson[] newPeople = new ImmutablePerson[immutablePeople.Length-2];
              bool hasPassedRemoval = false;
              for(var i=0;i<immutablePeople.Length;i++)
              {
                 hasPassedRemoval = hasPassedRemoval || immutablePeople[i] == newPerson;
                 newPeople[i] = this.immutablePeople[hasPassedRemoval ? i + 1 : i];
              }
              return new ImmutablePersonList(newPeople);
          }
          public bool Contains(ImmutablePerson immutablePerson){ 
             return this.immutablePeople.IndexOf(immutablePerson) != -1;
          } 
        }
        public class ImmutableEventList
        {
          private ImmutableEvent[] immutableEvents;
          public ImmutableEventList(ImmutableEvent[] immutableEvents){
              this.immutableEvents = immutableEvents;
          }
          public ImmutableEventList Add(ImmutableEvent newEvent){
              if(this.Contains(newEvent)) return this;
              ImmutableEvent[] newEvents= new ImmutableEvent[immutableEvents.Length];
              for(var i=0;i<immutableEvents.Length;i++)
                  newEvents[i] = this.immutableEvents[i];
              newEvents[immutableEvents.Length] = newEvent;
          }
          public ImmutableEventList Remove(ImmutableEvent newEvent){
              if(immutableEvents.IndexOf(newEvent) != -1)
              ImmutableEvent[] newEvents = new ImmutableEvent[immutableEvents.Length-2];
              bool hasPassedRemoval = false;
              for(var i=0;i<immutablePeople.Length;i++)
              {
                 hasPassedRemoval = hasPassedRemoval || immutableEvents[i] == newEvent;
                 newEvents[i] = this.immutableEvents[hasPassedRemoval ? i + 1 : i];
              }
              return new ImmutableEventList(newPeople);
          }
          public bool Contains(ImmutableEvent immutableEvent){ 
             return this.immutableEvent.IndexOf(immutableEvent) != -1;
          } 
        }
    

2
@AndresF., Se hai un'idea diversa su come mantenere grafici complessi con relazioni bidirezionali usando solo oggetti immutabili, mi piacerebbe ascoltarlo. (Suppongo che possiamo concordare sul fatto che una raccolta / matrice sia un oggetto)
smartcaveman,

2
@AndresF., (1) La mia prima affermazione non era universale, quindi non era falsa. In realtà ho fornito un esempio di codice per spiegare come fosse necessariamente vero in alcuni casi che risultano essere molto comuni nello sviluppo di applicazioni. (2) Le relazioni bidirezionali richiedono mutabilità, in generale. Non credo che Java sia il colpevole. Come ho detto, sarei felice di valutare eventuali alternative costruttive che suggeriresti, ma a questo punto i tuoi commenti sembrano molto simili a "Ti sbagli perché l'ho detto".
smartcaveman,

14
@smartcaveman Circa (2), non sono d'accordo: in generale, una "relazione bidirezionale" è un concetto matematico ortogonale alla mutabilità. Come di solito implementato in Java, richiede mutabilità (sono d'accordo con te su questo punto). Tuttavia, posso pensare a un'implementazione alternativa: una classe di relazione tra i due oggetti, con un costruttore Relationship(a, b); al punto di creare la relazione, entrambe le entità ae bgià esistono, e anche la relazione stessa è immutabile. Non sto dicendo che questo approccio sia pratico in Java; solo che è possibile.
Andres F.

4
@AndresF., Quindi, in base a quello che stai dicendo, se Rè il Relationship(a,b)ed entrambi ae bsono immutabili, non ane bterrebbe un riferimento R. Perché ciò funzioni, il riferimento dovrebbe essere archiviato altrove (come una classe statica). Comprendo correttamente le tue intenzioni?
smartcaveman,

9
È possibile memorizzare una relazione bidirezionale per dati immutabili come sottolinea Chthulhu tramite la pigrizia. Ecco un modo per farlo: haskell.org/haskellwiki/Tying_the_Knot
Thomas Eding

36

Ho letto "strutture di dati puramente funzionali" e mi ha fatto capire che ci sono alcune strutture di dati che sono molto più facili da implementare usando oggetti mutabili.

Per implementare un albero di ricerca binario, devi sempre restituire un nuovo albero: il tuo nuovo albero dovrà fare una copia di ogni nodo che è stato modificato (i rami non modificati sono condivisi). Per la tua funzione di inserimento questo non è poi così male, ma per me le cose sono diventate abbastanza inefficienti rapidamente quando ho iniziato a lavorare sull'eliminazione e il riequilibrio.

L'altra cosa da capire è che puoi passare anni a scrivere codice orientato agli oggetti e non puoi mai davvero capire quanto terribile può essere lo stato mutabile condiviso, se il tuo codice non viene eseguito in modo da esporre problemi di concorrenza.


3
È questo il libro di Okasaki?
Lumaca meccanica

Sì. un po 'secco ma un sacco di buone informazioni ...
Paul Sanwald

4
Divertente, ho sempre pensato che l'albero rosso / nero di Okasakis fosse molto più semplice. 10 righe o giù di lì. Immagino che il più grande vantaggio sia quando vuoi davvero mantenere anche la vecchia versione.
Thomas Ahle,

Mentre l'ultima frase è stata probabilmente vera in passato, non è chiaro che rimarrà vera in futuro, date le attuali tendenze hardware ecc.
jk.

29

Dal mio punto di vista, questa è una mancanza di consapevolezza. Se osservi altri linguaggi JVM noti (Scala, Clojure), gli oggetti mutabili vengono visti raramente nel codice ed è per questo che le persone iniziano a usarli in scenari in cui il threading singolo non è sufficiente.

Attualmente sto imparando Clojure e ho una piccola esperienza in Scala (4 anni + anche in Java) e il tuo stile di codifica cambia a causa della consapevolezza dello stato.


Forse "noto" piuttosto che "popolare" sarebbe una scelta migliore della parola.
Den

Sì, è giusto.
Florian Salihovic,

5
+1: Sono d'accordo: dopo aver appreso un po 'di Scala e Haskell, tendo ad usare final in Java e const in C ++ ovunque. Uso anche oggetti immutabili, se possibile, e mentre gli oggetti mutabili sono ancora necessari molto spesso, è sorprendente quanto spesso puoi usare quelli immutabili.
Giorgio,

5
Ho letto questo mio commento dopo due anni e mezzo e la mia opinione è cambiata a favore dell'immutabilità. Nel mio progetto attuale (che è in Python) stiamo usando oggetti molto raramente mutabili. Anche i nostri dati persistenti sono immutabili: creiamo nuovi record come risultato di alcune operazioni ed eliminiamo i vecchi record quando non sono più necessari, ma non aggiorniamo mai alcun record sul disco. Inutile dire che questo ha reso la nostra applicazione multiutente concorrente molto più semplice da implementare e mantenere fino ad ora.
Giorgio,

13

Penso che uno dei principali fattori che hanno contribuito sia stato ignorato: i fagioli Java si basano pesantemente su uno stile specifico di oggetti mutanti e (specialmente considerando la fonte) parecchie persone sembrano considerarlo come (o addirittura il ) esempio canonico di come tutto Java dovrebbe essere scritto.


4
+1, il modello getter / setter viene utilizzato troppo spesso come una sorta di implementazione predefinita dopo la prima analisi dei dati.
Jaap,

Questo è probabilmente un punto importante ... "perché è così che lo fanno tutti gli altri" ... quindi deve essere giusto. Per un programma "Hello World" è probabilmente il più semplice. Gestire i cambiamenti di stato degli oggetti attraverso una serie di proprietà mutabili ... è un po 'più complicato della profondità della comprensione di "Hello World". 20 anni dopo sono molto sorpreso che l'apice della programmazione del 20 ° secolo sia scrivere metodi getX e setX (che noiosi) su ogni attributo di un oggetto senza alcuna struttura. È solo a un passo dall'accesso diretto alle proprietà pubbliche con una mutabilità del 100%.
Darrell Teague,

12

Ogni sistema Java aziendale su cui ho lavorato nella mia carriera utilizza Hibernate o Java Persistence API (JPA). Hibernate e JPA impongono essenzialmente che il sistema utilizzi oggetti mutabili, poiché la loro premessa è che rilevano e salvano le modifiche agli oggetti dati. Per molti progetti la facilità di sviluppo offerta da Hibernate è più convincente dei vantaggi di oggetti immutabili.

Ovviamente gli oggetti mutabili sono in circolazione da molto più tempo di Hibernate, quindi Hibernate non è probabilmente la "causa" originale della popolarità degli oggetti mutabili. Forse la popolarità di oggetti mutevoli ha permesso a Hibernate di prosperare.

Ma oggi se molti programmatori junior si tagliano i denti sui sistemi aziendali usando Hibernate o un altro ORM, allora presumibilmente prenderanno l'abitudine di usare oggetti mutabili. Strutture come Hibernate potrebbero rafforzare la popolarità di oggetti mutevoli.


Punto eccellente. Per essere tutto per tutti, tali framework hanno poca scelta se non quella di passare al minimo comune denominatore, utilizzare i proxy basati sulla riflessione e ottenere / impostare la propria strada verso la flessibilità. Naturalmente questo crea sistemi con regole di transizione statale scarse o assenti o un mezzo comune con cui purtroppo implementarle. Dopo molti progetti, non sono del tutto sicuro di cosa sia meglio per opportunità, estensibilità e correttezza. Tendo a pensare a un ibrido. Bontà ORM dinamica ma con alcune definizioni per quali campi sono richiesti e quali cambiamenti di stato dovrebbero essere possibili.
Darrell Teague,

9

Un punto importante non ancora menzionato è che avere lo stato di un oggetto essere mutabile rende possibile che l' identità dell'oggetto che incapsula quello stato sia immutabile.

Molti programmi sono progettati per modellare cose del mondo reale che sono intrinsecamente mutabili. Supponiamo che alle 12:51, alcune variabili AllTruckscontengano un riferimento all'oggetto # 451, che è la radice di una struttura di dati che indica quale carico è contenuto in tutti i camion di una flotta in quel momento (12:51) e alcune variabili BobsTruckpuò essere usato per ottenere un riferimento all'oggetto # 24601 punti a un oggetto che indica quale carico è contenuto nel camion di Bob in quel momento (00:51). Alle 12:52, alcuni camion (incluso quello di Bob) vengono caricati e scaricati e le strutture dei dati vengono aggiornate in modo tale da AllTruckscontenere un riferimento a una struttura di dati che indica che il carico è in tutti i camion a partire dalle 12:52.

Cosa dovrebbe succedere BobsTruck?

Se la proprietà 'cargo' di ogni oggetto camion è immutabile, l'oggetto # 24601 rappresenterà per sempre lo stato che il camion di Bob aveva alle 12:51. Se BobsTruckcontiene un riferimento diretto all'oggetto # 24601, a meno che AllTrucksanche il codice che aggiorna non venga aggiornato BobsTruck, cesserà di rappresentare lo stato corrente del camion di Bob. Si noti inoltre che, a meno che non BobsTrucksia memorizzato in una qualche forma di oggetto mutabile, l'unico modo in cui il codice che AllTruckspotrebbe essere aggiornato potrebbe essere se il codice fosse esplicitamente programmato per farlo.

Se si vuole essere in grado di BobsTruckosservare lo stato del camion di Bob mantenendo immutabili tutti gli oggetti, si potrebbe avere BobsTruckuna funzione immutabile che, dato il valore che AllTrucksha o ha avuto in un determinato momento, produrrà lo stato del camion di Bob a quella volta. Uno potrebbe anche avere un paio di funzioni immutabili - una delle quali sarebbe come sopra, e l'altra accetterebbe un riferimento a uno stato di flotta e un nuovo stato di camion e restituire un riferimento a un nuovo stato di flotta che abbinato al vecchio, tranne per il fatto che il camion di Bob avrebbe il nuovo stato.

Sfortunatamente, dover usare una tale funzione ogni volta che si vuole accedere allo stato del camion di Bob potrebbe diventare piuttosto fastidioso e ingombrante. Un approccio alternativo sarebbe quello di dire che l'oggetto # 24601 rappresenterà sempre e per sempre (purché qualcuno ne abbia un riferimento) rappresenterà lo stato attuale del camion di Bob. Il codice che vorrà accedere ripetutamente allo stato attuale del camion di Bob non dovrebbe eseguire ogni volta una funzione che richiede molto tempo: potrebbe semplicemente fare una funzione di ricerca una volta per scoprire che l'oggetto # 24601 è il camion di Bob, e quindi semplicemente accedi a quell'oggetto ogni volta che vuole vedere lo stato attuale del camion di Bob.

Si noti che l'approccio funzionale non è privo di vantaggi in un ambiente a thread singolo o in un ambiente a thread multipli in cui i thread osserveranno principalmente i dati anziché modificarli. Qualsiasi thread osservatore che copia il riferimento all'oggetto contenuto inAllTruckse quindi esamina gli stati dei camion rappresentati, quindi vedrà lo stato di tutti i camion dal momento in cui ha afferrato il riferimento. Ogni volta che un thread di osservatori desidera visualizzare dati più recenti, può semplicemente riottenere il riferimento. D'altro canto, avere l'intero stato della flotta rappresentato da un singolo oggetto immutabile impedirebbe la possibilità che due thread aggiornino contemporaneamente diversi camion, poiché ogni thread se lasciato ai propri dispositivi produrrebbe un nuovo oggetto "stato flotta" che includeva il nuovo stato del suo camion e i vecchi stati di ogni altro. La correttezza può essere garantita se ogni thread utilizza CompareExchangeper aggiornare AllTruckssolo se non è cambiato e risponde a un erroreCompareExchangerigenerando il suo oggetto stato e ritentando l'operazione, ma se più di un thread tenta un'operazione di scrittura simultanea, le prestazioni saranno generalmente peggiori rispetto a se tutte le scritture fossero eseguite su un singolo thread; più thread tentano tali operazioni simultanee, peggiore sarà la prestazione.

Se i singoli oggetti del camion sono mutabili ma hanno identità immutabili , lo scenario multi-thread diventa più pulito. È possibile consentire a un solo thread di funzionare alla volta su un determinato camion, ma i thread che operano su camion diversi potrebbero farlo senza interferenze. Mentre ci sono modi in cui si potrebbe emulare tale comportamento anche quando si utilizzano oggetti immutabili (ad esempio si potrebbero definire gli oggetti "AllTrucks" in modo che l'impostazione dello stato del camion appartenente a XXX su SSS richiederebbe semplicemente la generazione di un oggetto che dicesse "A partire da [Time], lo stato del camion appartenente a [XXX] è ora [SSS]; lo stato di tutto il resto è [Vecchio valore di AllTrucks] ". La generazione di un tale oggetto sarebbe abbastanza veloce che anche in presenza di contesa, unCompareExchangeil ciclo non richiederebbe molto tempo. D'altra parte, l'utilizzo di una tale struttura di dati aumenterebbe sostanzialmente il tempo necessario per trovare il camion di una persona in particolare. L'uso di oggetti mutabili con identità immutabili evita questo problema.


8

Non c'è giusto o sbagliato, dipende solo da cosa preferisci. C'è un motivo per cui alcune persone preferiscono le lingue che favoriscono un paradigma rispetto a un altro e un modello di dati rispetto a un altro. Dipende solo dalle tue preferenze e da cosa vuoi raggiungere (ed essere in grado di usare facilmente entrambi gli approcci senza alienare i fan sfegatati di una parte o dell'altra è un santo graal che alcune lingue stanno cercando).

Penso che il modo migliore e più veloce per rispondere alla tua domanda sia per te a capo di pro e contro di immutabilità vs mutabilità .


7

Controlla questo post sul blog: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . Riassume perché gli oggetti immutabili sono meglio che mutabili. Ecco un breve elenco di argomenti:

  • 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

Le persone usano oggetti mutabili, per quanto ho capito, perché stanno ancora mescolando OOP con una programmazione procedurale imperativa.


5

In Java gli oggetti immutabili richiedono un costruttore che prenderà tutte le proprietà dell'oggetto (o che il costruttore le crei da altri argomenti o valori predefiniti). Tali proprietà dovrebbero essere contrassegnate come finali .

Ci sono quattro problemi con questo principalmente a che fare con l'associazione dei dati :

  1. I metadati di riflessione dei costruttori Java non mantengono i nomi degli argomenti.
  2. I costruttori Java (e i metodi) non hanno parametri nominati (chiamati anche etichette), quindi diventa confuso con molti parametri.
  3. Quando si eredita un altro oggetto immutabile, è necessario chiamare l'ordine corretto dei costruttori. Questo può essere piuttosto complicato al punto di arrendersi e lasciare uno dei campi non definitivo.
  4. La maggior parte delle tecnologie di rilegatura (come Spring MVC Data Binding, Hibernate, ecc ...) funzionerà solo con costruttori predefiniti no-arg (questo perché le annotazioni non sempre esistevano).

Puoi mitigare # 1 e # 2 usando annotazioni simili @ConstructorPropertiese creando un altro oggetto generatore mutabile (solitamente fluente) per creare l'oggetto immutabile.


5

Sono sorpreso che nessuno abbia menzionato i vantaggi dell'ottimizzazione delle prestazioni. A seconda della lingua un compilatore può apportare una serie di ottimizzazioni quando si tratta di dati immutabili perché sa che i dati non cambieranno mai. Vengono ignorati tutti i tipi di cose, il che offre enormi vantaggi in termini di prestazioni.

Anche gli oggetti immutabili eliminano quasi l'intera classe di bug di stato.

Non è grande come dovrebbe essere perché è più difficile, non si applica a tutte le lingue e alla maggior parte delle persone è stato insegnato il codice imperativo.

Inoltre ho scoperto che la maggior parte dei programmatori sono felici nella loro scatola e spesso resistono a nuove idee che non comprendono completamente. In generale alla gente non piace il cambiamento.

Ricorda anche che lo stato della maggior parte dei programmatori è cattivo. La maggior parte della programmazione in natura è terribile ed è causata da una mancanza di comprensione e politica.


4

Perché le persone usano delle potenti funzionalità? Perché le persone usano la meta-programmazione, la pigrizia o la digitazione dinamica? La risposta è convenienza. Lo stato mutevole è così facile. È così facile aggiornare sul posto e la soglia della dimensione del progetto in cui lo stato immutabile è più produttivo con cui lavorare rispetto allo stato mutabile è piuttosto alta, quindi la scelta non ti morderà per un po '.


4

I linguaggi di programmazione sono progettati per l'esecuzione da parte dei computer. Tutti i blocchi costitutivi importanti dei computer - CPU, RAM, cache, dischi - sono mutabili. Quando non lo sono (BIOS), sono davvero immutabili e non è nemmeno possibile creare nuovi oggetti immutabili.

Quindi, qualsiasi linguaggio di programmazione costruito su oggetti immutabili soffre di un gap rappresentativo nella sua implementazione. E per le prime lingue come la C, questo è stato un grosso ostacolo.


1

Senza oggetti mutabili non hai stato. Certo, questa è una buona cosa se riesci a gestirlo e se c'è qualche possibilità che un oggetto possa essere referenziato da più di un thread. Ma il programma sarà piuttosto noioso. Molti software, in particolare i server Web, evitano di assumersi la responsabilità di oggetti mutabili spingendo la mutabilità su database, sistemi operativi, librerie di sistema, ecc. In pratica, ciò libera il programmatore da problemi di mutabilità e rende il web (e altri) sviluppo economico. Ma la mutabilità è ancora lì.

In generale, hai tre tipi di classi: classi normali, non thread-safe, che devono essere attentamente custodite e protette; classi immutabili, che possono essere utilizzate liberamente; e classi mutabili, thread-safe che possono essere usate liberamente ma che devono essere scritte con estrema cura. Il primo tipo è quello fastidioso, con i peggiori quelli che si pensa siano del terzo tipo. Ovviamente, il primo tipo sono quelli facili da scrivere.

Di solito finisco con molte classi normali e mutevoli che devo guardare con molta attenzione. In una situazione multi-thread, la sincronizzazione necessaria rallenta tutto anche quando posso evitare un abbraccio mortale. Quindi sto facendo di solito copie immutabili della classe mutevole e consegnando quelli a chiunque possa usarla. Una nuova copia immutabile è necessaria ogni volta che l'originale muta, quindi immagino che a volte potrei avere un centinaio di copie dell'originale là fuori. Sono totalmente dipendente dalla Garbage Collection.

In breve, gli oggetti mutabili non thread-safe vanno bene se non si utilizzano più thread. (Ma il multithreading si sta gonfiando dappertutto - state attenti!) Possono essere usati in sicurezza altrimenti se li limitate alle variabili locali o le sincronizzate rigorosamente. Se riesci ad evitarli utilizzando il codice comprovato di altre persone (DB, chiamate di sistema, ecc.), Fallo. Se puoi usare una classe immutabile, fallo. E penso , in generale , le persone non sono consapevoli dei problemi del multithreading o sono (sensibilmente) terrorizzate da loro e usano tutti i tipi di trucchi per evitare il multithreading (o meglio, spingendo la responsabilità per esso altrove).

Come PS, sento che i getter e i setter Java stanno sfuggendo di mano. Controllare questo fuori.


7
Lo stato immutabile è ancora stato.
Jeremy Heiler,

3
@JeremyHeiler: Vero, ma è lo stato di qualcosa che è mutevole. Se nulla muta, esiste un solo stato, che è la stessa cosa di nessuno stato.
RalphChapin,

1

Molte persone hanno avuto buone risposte, quindi voglio sottolineare qualcosa che hai toccato che era molto attento ed estremamente vero e non è stato menzionato altrove qui.

La creazione automatica di setter e getter è un'idea orribile e orribile, ma è il primo modo in cui le persone con una mentalità procedurale cercano di forzare OO nella loro mentalità. I setter e i getter, insieme alle proprietà, dovrebbero essere creati solo quando ti accorgi di averne bisogno e non tutti i valori predefiniti

In effetti, anche se hai bisogno di getter abbastanza regolarmente, l'unico modo in cui setter o proprietà scrivibili dovrebbero mai esistere nel tuo codice è attraverso un modello di builder in cui sono bloccati dopo che l'oggetto è stato completamente istanziato.

Molte classi sono mutabili dopo la creazione, il che va bene, semplicemente non dovrebbe avere le sue proprietà manipolate direttamente - invece dovrebbe essere chiesto di manipolare le sue proprietà attraverso chiamate di metodo con la logica aziendale reale in esse (Sì, un setter è praticamente lo stesso cosa come manipolare direttamente la proprietà)

Ora, questo non si applica nemmeno al codice / alle lingue di stile "Scripting", ma al codice creato per qualcun altro e si aspetta che gli altri leggano ripetutamente nel corso degli anni. Ho dovuto iniziare a fare questa distinzione ultimamente perché mi diverto così tanto a scherzare con Groovy e c'è una grande differenza negli obiettivi.


1

Gli oggetti mutabili vengono utilizzati quando è necessario impostare valori multipli dopo aver creato un'istanza dell'oggetto.

Non dovresti avere un costruttore con, diciamo, sei parametri. Invece si modifica l'oggetto con i metodi setter.

Un esempio di questo è un oggetto Report, con setter per font, orientamento ecc.

In breve: i mutabili sono utili quando si ha molto stato da impostare su un oggetto e non sarebbe pratico avere una firma del costruttore molto lunga.

EDIT: il modello Builder può essere usato per costruire l'intero stato dell'oggetto.


3
questo viene presentato come se la mutabilità e i cambiamenti dopo l'istanza fossero l'unico modo per impostare più valori. Per completezza, nota che il modello Builder offre capacità uguali o persino più potenti e non richiede di sacrificare l'immutabilità. new ReportBuilder().font("Arial").orientation("landscape").build()
moscerino del

1
"Non sarebbe pratico avere una firma del costruttore molto lunga.": Puoi sempre raggruppare i parametri in oggetti più piccoli e passare questi oggetti come parametri al costruttore.
Giorgio,

1
@cHao Cosa succede se si desidera impostare 10 o 15 attributi? Inoltre, non sembra buono che un metodo chiamato withFontrestituisca a Report.
Tulains Córdova,

1
Per quanto riguarda l'impostazione di 10 o 15 attributi, il codice per farlo non sarebbe imbarazzante se (1) Java sapesse come eludere la costruzione di tutti quegli oggetti intermedi e se (2) di nuovo, i nomi fossero standardizzati. Non è un problema con l'immutabilità; è un problema con Java che non sa come farlo bene.
cHao,

1
@cHao Fare una catena di chiamate per 20 attributi è brutto. Il codice brutto tende ad essere un codice di scarsa qualità.
Tulains Córdova,

1

Penso che l'uso di oggetti mutabili derivi dal pensiero imperativo: si calcola un risultato cambiando passo dopo passo il contenuto delle variabili mutabili (calcolo per effetto collaterale).

Se pensi funzionalmente, vuoi avere uno stato immutabile e rappresentare gli stati successivi di un sistema applicando funzioni e creando nuovi valori da quelli vecchi.

L'approccio funzionale può essere più pulito e più robusto, ma può essere molto inefficiente a causa della copia, quindi è necessario ricorrere a una struttura di dati condivisa che si modifica in modo incrementale.

Il compromesso che trovo più ragionevole è: Inizia con oggetti immutabili e poi passa a oggetti mutabili se la tua implementazione non è abbastanza veloce. Da questo punto di vista, l'uso sistematico di oggetti mutabili sin dall'inizio può essere considerato una sorta di ottimizzazione prematura : si sceglie l'implementazione più efficiente (ma anche più difficile da capire e da debug) fin dall'inizio.

Quindi, perché molti programmatori usano oggetti mutabili? IMHO per due motivi:

  1. Molti programmatori hanno imparato a programmare usando un paradigma imperativo (procedurale o orientato agli oggetti), quindi la mutabilità è il loro approccio di base alla definizione del calcolo, cioè non sanno quando e come usare l'immutabilità perché non ne hanno familiarità.
  2. Molti programmatori si preoccupano troppo presto delle prestazioni, mentre spesso è più efficace concentrarsi innanzitutto sulla scrittura di un programma funzionalmente corretto, quindi cercare di trovare colli di bottiglia e ottimizzarlo.

1
Il problema non è solo la velocità. Esistono molti tipi di costrutti utili che semplicemente non possono essere implementati senza l'utilizzo di tipi mutabili. Tra le altre cose, la comunicazione tra due thread richiede che, al minimo assoluto, entrambi debbano avere un riferimento a un oggetto condiviso in cui uno può inserire i dati e l'altro può leggerlo. Inoltre, è spesso semanticamente molto più chiaro dire "Cambia proprietà P e Q di questo oggetto" piuttosto che dire "Prendi questo oggetto, costruisci un nuovo oggetto che è proprio così tranne il valore di P, e quindi un nuovo oggetto che è proprio così tranne Q ".
supercat

1
Non sono un esperto di FP ma la comunicazione di thread AFAIK (1) può essere raggiunta creando un valore immutabile in un thread e leggendolo in un altro (di nuovo, AFAIK, questo è l'approccio Erlang) (2) AFAIK la notazione per impostare una proprietà non cambia molto (ad es. nel valore record Haskell setProperty corrisponde a Java record.setProperty (valore)), cambia solo la semantica perché il risultato dell'impostazione di una proprietà è un nuovo record immutabile.
Giorgio,

1
"entrambi devono avere un riferimento a un oggetto condiviso in cui uno può mettere i dati e l'altro può leggerlo": più precisamente, puoi rendere immutabile un oggetto (tutti i membri const in C ++ o final in Java) e impostare tutto il contenuto in costruttore. Quindi questo oggetto immutabile viene consegnato dal thread del produttore al thread del consumatore.
Giorgio,

1
(2) Ho già elencato le prestazioni come motivo per non utilizzare lo stato immutabile. Per quanto riguarda (1), ovviamente il meccanismo che implementa la comunicazione tra thread ha bisogno di uno stato mutabile, ma puoi nasconderlo dal tuo modello di programmazione, vedi ad esempio il modello dell'attore ( en.wikipedia.org/wiki/Actor_model ). Quindi, anche se a un livello inferiore hai bisogno di mutabilità per implementare la comunicazione, puoi avere un livello di astrazione superiore in cui comunichi tra i thread inviando avanti e indietro oggetti immutabili.
Giorgio,

1
Non sono sicuro di averti compreso appieno, ma anche un linguaggio funzionale puro in cui ogni valore è immutabile, c'è una cosa mutevole: lo stato del programma, ovvero lo stack di programma corrente e i collegamenti variabili. Ma questo è l'ambiente di esecuzione. Ad ogni modo, suggerisco di discuterne in chat un po 'di tempo e quindi ripulire i messaggi da questa domanda.
Giorgio,

1

So che mi stai chiedendo di Java, ma uso sempre mutable vs immutable in goal-c. Esiste un array immutabile NSArray e un array mutabile NSMutableArray. Queste sono due diverse classi scritte specificamente in un modo ottimizzato per gestire l'esatto utilizzo. Se ho bisogno di creare un array e non modificarne mai il contenuto, utilizzerei NSArray, che è un oggetto più piccolo ed è molto più veloce in ciò che fa, rispetto a un array mutabile.

Quindi, se crei un oggetto Person che è immutabile, allora hai solo bisogno di un costruttore e di un getter, quindi l'oggetto sarà più piccolo e utilizzerà meno memoria, il che a sua volta renderà il tuo programma effettivamente più veloce. Se è necessario modificare l'oggetto dopo la creazione, un oggetto Person modificabile sarà migliore in modo che possa modificare i valori invece di creare un nuovo oggetto.

Quindi: a seconda di cosa pensi di fare con l'oggetto, scegliere mutevole o immutabile può fare una grande differenza, quando si tratta di prestazioni.


1

Oltre a molte altre ragioni fornite qui, un problema è che le lingue tradizionali non supportano bene l'immutabilità. Almeno, sei penalizzato per l'immutabilità in quanto devi aggiungere parole chiave aggiuntive come const o final e devi usare costruttori illeggibili con molti, molti argomenti o schemi di codici lunghi.

È molto più facile nelle lingue create pensando all'immutabilità. Considera questo frammento di Scala per definire una classe Person, facoltativamente con argomenti denominati e creare una copia con un attributo modificato:

case class Person(id: String, firstName: String, lastName: String)

val joe = Person("123", "Joe", "Doe")
val spouse = Person(id = "124", firstName = "Mary", lastName = "Moe")
val joeMarried = joe.copy(lastName = "Doe-Moe")

Quindi, se vuoi che gli sviluppatori adottino l'immutabilità, questo è uno dei motivi per cui potresti considerare di passare a un linguaggio di programmazione più moderno.


ciò non sembra aggiungere nulla di sostanziale rispetto ai punti formulati e spiegati nelle precedenti 24 risposte
moscerino del

@gnat Quale delle risposte precedenti sottolinea che la maggior parte delle lingue tradizionali non supporta decentemente l'immutabilità? Penso che questo punto semplicemente non sia stato sollevato (ho verificato), ma questo è un ostacolo piuttosto importante per l'IMHO.
Hans-Peter Störr,

questo ad esempio spiega in dettaglio questo problema in Java. E almeno altre 3 risposte si riferiscono indirettamente ad essa
moscerino del

0

Immutabile significa che non puoi cambiare il valore, e mutevole significa che puoi cambiare il valore se pensi in termini di primitivi e oggetti. Gli oggetti sono diversi dai primitivi in ​​Java in quanto sono costruiti in tipi. Le primitive sono costruite in tipi come int, booleano e vuoto.

Molte persone pensano che le variabili primitive e di oggetti che hanno un modificatore finale davanti a loro siano immutabili, tuttavia, questo non è esattamente vero. Quindi finale non significa quasi immutabile per le variabili. Dai un'occhiata a questo link per un esempio di codice:

public abstract class FinalBase {

    private final int variable; // Unset

    /* if final really means immutable than
     * I shouldn't be able to set the variable
     * but I can.
     */
    public FinalBase(int variable) { 
        this.variable = variable;
    }

    public int getVariable() {
        return variable;
    }

    public abstract void method();
}

// This is not fully necessary for this example
// but helps you see how to set the final value 
// in a sub class.
public class FinalSubclass extends FinalBase {

    public FinalSubclass(int variable) {
        super(variable);
    }

    @Override
    public void method() {
        System.out.println( getVariable() );
    }

    @Override
    public int getVariable() {

        return super.getVariable();
    }

    public static void main(String[] args) {
        FinalSubclass subclass = new FinalSubclass(10);
        subclass.method();
    }
}

-1

Penso che una buona ragione sarebbe quella di modellare "oggetti" mutabili "nella vita reale", come le finestre dell'interfaccia. Ricordo vagamente di aver letto che l'OOP è stato inventato quando qualcuno ha cercato di scrivere software per controllare alcune operazioni portuali.


1
Non riesco ancora a trovare dove ho letto questo sulle origini di OOP, ma secondo Wikipedia , alcuni sistemi di informazione regionali integrati di una grande compagnia di spedizioni marittime OOCL sono scritti in Smalltalk.
Alexey,

-2

Java, in molti casi impone oggetti mutabili, ad esempio quando si desidera contare o restituire qualcosa in un visitatore o eseguibile, sono necessarie le variabili finali, anche senza il multithreading.


5
finalè in realtà circa 1/4 di un passo verso l' immutabilità. Non vedo perché lo dici.
cHao,

hai letto davvero il mio post?
Ralf H,

Hai davvero letto la domanda? :) Non aveva assolutamente nulla a che fare con final. Allevarlo in questo contesto evoca una strana confusione tra final"mutevole", quando il vero scopo finalè prevenire determinati tipi di mutazione. (A proposito, non il mio downvote.)
cHao il

Non sto dicendo che non hai un punto valido qui da qualche parte. Sto dicendo che non lo stai spiegando molto bene. In qualche modo vedo dove probabilmente ci andrai, ma devi andare oltre. Come è, sembra solo confuso.
cHao,

1
In realtà ci sono pochissimi casi in cui è richiesto un oggetto mutabile . Ci sono casi in cui il codice sarebbe più chiaro, o in alcuni casi più veloce, usando oggetti mutabili. Ma considera che la stragrande maggioranza dei modelli di progettazione sono fondamentalmente solo una rappresentazione a metà della programmazione funzionale per linguaggi che non lo supportano in modo nativo. La parte divertente di ciò è che FP richiede mutabilità solo in luoghi molto selezionati (vale a dire, dove devono verificarsi effetti collaterali), e quei luoghi sono generalmente strettamente limitati in numero, dimensioni e portata.
cHao,
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.