Vedo i vantaggi di rendere immutabili gli oggetti nel mio programma. Quando penso profondamente a un buon design per la mia applicazione, spesso arrivo a molti dei miei oggetti immutabili. Spesso arriva al punto in cui mi piacerebbe avere tutti i miei oggetti immutabili.
Questa domanda riguarda la stessa idea ma nessuna risposta suggerisce qual è un buon approccio all'immutabilità e quando effettivamente usarlo. Ci sono dei buoni schemi di design immutabili? L'idea generale sembra essere "rendere immutabili gli oggetti se non si ha assolutamente bisogno che cambino", cosa che in pratica è inutile.
La mia esperienza è che l'immutabilità porta il mio codice sempre più al paradigma funzionale e questa progressione accade sempre:
- Inizio a richiedere strutture dati persistenti (in senso funzionale) come elenchi, mappe ecc.
- È estremamente sconveniente lavorare con riferimenti incrociati (ad esempio il nodo dell'albero che fa riferimento ai suoi figli mentre i bambini fanno riferimento ai loro genitori), il che mi impedisce di utilizzare i riferimenti incrociati, il che rende ancora più funzionali le mie strutture dati e il codice.
- L'ereditarietà si interrompe per avere senso e comincio a usare invece la composizione.
- Le idee di base di OOP come l'incapsulamento cominciano a cadere a pezzi e i miei oggetti cominciano a somigliare a funzioni.
A questo punto praticamente non utilizzo più nulla del paradigma OOP e posso semplicemente passare a un linguaggio puramente funzionale. Quindi la mia domanda: c'è un approccio coerente al buon design OOP immutabile o è sempre il caso che quando si porta l'idea immutabile al suo massimo potenziale, si finisce sempre per programmare in un linguaggio funzionale che non richiede più nulla dal mondo OOP? Ci sono delle buone linee guida per decidere quali classi dovrebbero essere immutabili e quali dovrebbero rimanere mutabili per assicurare che l'OOP non si disgrega?
Solo per comodità, fornirò un esempio. Abbiamo un ChessBoard
come una collezione immutabile di pezzi di scacchi immutabili (estendendo la classe astratta Piece
). Dal punto di vista OOP, un pezzo è responsabile della generazione di mosse valide dalla sua posizione sulla scacchiera. Ma per generare le mosse il pezzo ha bisogno di un riferimento al suo tabellone mentre il tabellone deve fare riferimento ai suoi pezzi. Bene, ci sono alcuni trucchi per creare questi riferimenti incrociati immutabili a seconda del tuo linguaggio OOP ma sono dolorosi da gestire, meglio non avere un pezzo per fare riferimento alla sua scheda. Ma poi il pezzo non può generare le sue mosse poiché non conosce lo stato della scacchiera. Quindi il pezzo diventa solo una struttura dati che tiene il tipo di pezzo e la sua posizione. È quindi possibile utilizzare una funzione polimorfica per generare mosse per tutti i tipi di pezzi. Questo è perfettamente realizzabile nella programmazione funzionale ma quasi impossibile in OOP senza verifiche di tipo runtime e altre cattive pratiche OOP ... Quindi, una mossa è solo una funzione che rende una nuova scheda da una vecchia scheda, di nuovo un'idea funzionale che ha perfettamente senso ma non avendo più nulla a che fare con OOP.