web-dev-qa-db-fra.com

Si les objets immuables sont bons, pourquoi les gens continuent-ils à créer des objets mutables?

Si les objets immuables¹ sont bons, simples et offrent des avantages dans la programmation simultanée, pourquoi les programmeurs continuent-ils à créer des objets mutables²?

J'ai quatre ans d'expérience en programmation Java et comme je le vois, la première chose que les gens font après avoir créé une classe est de générer des getters et des setters dans le IDE (ce qui le rend mutable.) Y a-t-il un manque de sensibilisation ou pouvons-nous nous en sortir avec l'utilisation d'objets mutables dans la plupart des scénarios?


¹ Objet immuable est un objet dont l'état ne peut pas être modifié après sa création.
² objet Mutable est un objet qui peut être modifié après sa création.

250
Vinoth Kumar C M

Les objets mutables et immuables ont leurs propres utilisations, avantages et inconvénients.

Les objets immuables simplifient en effet la vie dans de nombreux cas. Ils sont particulièrement applicables aux types de valeur, où les objets n'ont pas d'identité afin qu'ils puissent être facilement remplacés. Et ils peuvent rendre la programmation simultanée plus sûre et plus propre (la plupart des bogues de concurrence notoirement difficiles à trouver sont finalement causés par un état mutable partagé entre les threads). Cependant, pour les objets volumineux et/ou complexes, la création d'une nouvelle copie de l'objet pour chaque modification peut être très coûteuse et/ou fastidieuse. Et pour les objets ayant une identité distincte, changer un objet existant est beaucoup plus simple et intuitif que d'en créer une nouvelle copie modifiée.

Pensez à un personnage de jeu. Dans les jeux, la vitesse est une priorité absolue, donc représenter vos personnages de jeu avec des objets mutables rendra votre jeu beaucoup plus rapide qu'une implémentation alternative où une nouvelle copie du personnage de jeu est générée pour chaque petit changement.

De plus, notre perception du monde réel est inévitablement basée sur des objets mutables. Lorsque vous remplissez votre voiture de carburant à la station-service, vous la percevez comme le même objet tout le long (c'est-à-dire que son identité est maintenu tandis que son état change) - pas comme si la vieille voiture avec un réservoir vide était remplacée par de nouvelles instances de voitures consécutives ayant leur réservoir de plus en plus plein. Ainsi, chaque fois que nous modélisons un domaine du monde réel dans un programme, il est généralement plus simple et plus facile d'implémenter le modèle de domaine à l'aide d'objets mutables pour représenter des entités du monde réel.

En dehors de toutes ces raisons légitimes, hélas, la cause la plus probable pour laquelle les gens continuent à créer des objets mutables est l'inertie de l'esprit, c'est-à-dire la résistance au changement. Notez que la plupart des développeurs d'aujourd'hui ont été formés bien avant que l'immuabilité (et le paradigme qui le contient, la programmation fonctionnelle) ne deviennent "à la mode" dans leur sphère d'influence, et ne gardent pas leurs connaissances à jour sur les nouveaux outils et méthodes de notre métier - en fait, beaucoup d'entre nous, les humains positivement résister de nouvelles idées et de nouveaux processus. "Je programme comme ça depuis nn ans et je me fiche des dernières manies stupides!"

326
Péter Török

Je pense que vous avez tous manqué la réponse la plus évidente. La plupart des développeurs créent des objets mutables car la mutabilité est la valeur par défaut dans les langages impératifs. La plupart d'entre nous ont mieux à faire avec notre temps que de constamment modifier le code loin des valeurs par défaut - plus correct ou non. Et l'immuabilité n'est pas plus une panacée que toute autre approche. Cela rend certaines choses plus faciles, mais rend les autres beaucoup plus difficiles, comme certaines réponses l'ont déjà souligné.

130
Onorio Catenacci
  1. Il y a une place pour la mutabilité. Conception pilotée par domaine les principes permettent de bien comprendre ce qui doit être mutable et ce qui doit être immuable. Si vous y réfléchissez, vous vous rendrez compte qu'il est impossible de concevoir un système dans lequel tout changement d'état vers un objet nécessite sa destruction et sa recomposition, et pour chaque objet qui l'a référencé. Avec des systèmes complexes, cela pourrait facilement entraîner un essuyage complet et une reconstruction complète du graphique d'objet du système entier

  2. La plupart des développeurs ne font rien où les exigences de performances sont suffisamment importantes pour qu'elles doivent se concentrer sur la concurrence (ou sur de nombreux autres problèmes qui sont universellement considérés comme de bonnes pratiques par les personnes informées).

  3. Il y a certaines choses que vous ne pouvez tout simplement pas faire avec des objets immuables, comme avoir des relations bidirectionnelles. Une fois que vous avez défini une valeur d'association sur un objet, ce sont les changements d'identité. Ainsi, vous définissez la nouvelle valeur sur l'autre objet et elle change également. Le problème est que la référence du premier objet n'est plus valide, car une nouvelle instance a été créée pour représenter l'objet avec la référence. Poursuivre cela entraînerait simplement des régressions infinies. J'ai fait une petite étude de cas après avoir lu votre question, voici à quoi elle ressemble. Avez-vous une approche alternative qui permet une telle fonctionnalité tout en maintenant l'immuabilité?

        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;
          } 
        }
    
49
smartcaveman

J'ai lu "des structures de données purement fonctionnelles", et cela m'a fait réaliser qu'il existe un certain nombre de structures de données qui sont beaucoup plus faciles à implémenter à l'aide d'objets mutables.

Pour implémenter une arborescence de recherche binaire, vous devez renvoyer une nouvelle arborescence à chaque fois: Votre nouvelle arborescence aura dû faire une copie de chaque nœud qui a été modifié (les branches non modifiées sont partagées). Pour votre fonction d'insertion, ce n'est pas trop mal, mais pour moi, les choses sont devenues assez inefficaces rapidement quand j'ai commencé à travailler sur la suppression et le rééquilibrage.

L'autre chose à réaliser est que vous pouvez passer des années à écrire du code orienté objet et ne jamais vraiment réaliser à quel point l'état mutable partagé peut être terrible, si votre code n'est pas exécuté d'une manière qui exposera les problèmes de concurrence.

36
Paul Sanwald

De mon point de vue, c'est un manque de sensibilisation. Si vous regardez d'autres langages JVM connus (Scala, Clojure), les objets mutables sont rarement vus dans le code et c'est pourquoi les gens commencent à les utiliser dans des scénarios où le thread unique n'est pas suffisant.

J'apprends actuellement Clojure et j'ai une petite expérience en Scala (4 ans + en Java également) et votre style de codage change à cause de la conscience de Etat.

29

Je pense qu'un facteur contributif majeur a été ignoré: Java Les haricots reposent fortement sur un style spécifique d'objets en mutation, et (en particulier compte tenu de la source), beaucoup de gens semblent considérer cela comme un (ou même le) exemple canonique de la façon dont tous Java doit être écrit.

13
Jerry Coffin

Chaque entreprise Java système sur lequel j'ai travaillé au cours de ma carrière utilise Hibernate ou Java Persistence API (JPA). Hibernate et JPA dictent essentiellement que votre Le système utilise des objets mutables, car leur principe est qu'ils détectent et enregistrent les modifications de vos objets de données. Pour de nombreux projets, la facilité de développement qu'Hibernate apporte est plus convaincante que les avantages des objets immuables.

De toute évidence, les objets mutables existent depuis bien plus longtemps que Hibernate, donc Hibernate n'est probablement pas la "cause" d'origine de la popularité des objets mutables. Peut-être que la popularité des objets mutables a permis à Hibernate de prospérer.

Mais aujourd'hui, si de nombreux programmeurs débutants se font les dents sur les systèmes d'entreprise utilisant Hibernate ou un autre ORM, ils prendront probablement l'habitude d'utiliser des objets mutables. Des cadres comme Hibernate peuvent ancrer la popularité des objets mutables.

12
gutch

Un point majeur non encore mentionné est que le fait d'avoir l'état d'un objet mutable permet d'avoir l'immuable identité de l'objet qui encapsule cet état.

De nombreux programmes sont conçus pour modéliser des choses du monde réel qui sont intrinsèquement mutables. Supposons qu'à 12 h 51, une variable AllTrucks contienne une référence à l'objet # 451, qui est la racine d'une structure de données qui indique la cargaison contenue dans tous les camions d'une flotte à ce moment (12: 51am), et une variable BobsTruck peut être utilisée pour obtenir une référence à l'objet # 24601 pointe vers un objet qui indique quelle cargaison est contenue dans le camion de Bob à ce moment (00h51). À 12 h 52, certains camions (y compris ceux de Bob) sont chargés et déchargés, et les structures de données sont mises à jour afin que AllTrucks contienne désormais une référence à une structure de données qui indique que la cargaison se trouve dans tous les camions à partir du 12 : 52h.

Que doit-il arriver à BobsTruck?

Si la propriété "cargo" de chaque objet camion est immuable, alors l'objet # 24601 représentera à jamais l'état du camion de Bob à 12 h 51. Si BobsTruck contient une référence directe à l'objet # 24601, à moins que le code qui met à jour AllTrucks arrive également à mettre à jour BobsTruck, il cessera de représenter l'état actuel du camion de Bob . Notez en outre qu'à moins que BobsTruck soit stocké dans une certaine forme d'objet mutable, la seule façon dont le code qui met à jour AllTrucks pourrait le mettre à jour serait si le code était explicitement programmé pour le faire.

Si l'on veut pouvoir utiliser BobsTruck pour observer l'état du camion de Bob tout en gardant tous les objets immuables, on pourrait avoir BobsTruck une fonction immuable qui, étant donné la valeur que AllTrucks a ou a eu à un moment donné, donnera l'état du camion de Bob à ce moment-là. On pourrait même lui faire assumer une paire de fonctions immuables - dont l'une serait comme ci-dessus, et l'autre accepterait une référence à un état de flotte et à un nouvel état de camion, et retournerait une référence à un nouvel état de flotte qui correspondait à l'ancien, sauf que le camion de Bob aurait le nouvel état.

Malheureusement, devoir utiliser une telle fonction chaque fois que l'on veut accéder à l'état du camion de Bob pourrait devenir assez ennuyeux et encombrant. Une approche alternative serait de dire que l'objet # 24601 représentera toujours et à jamais (tant que quelqu'un en aura une référence) l'état actuel du camion de Bob. Le code qui voudra accéder à plusieurs reprises à l'état actuel du camion de Bob n'aurait pas à exécuter une fonction consommatrice de temps à chaque fois - il pourrait simplement faire une fonction de recherche une fois pour découvrir que l'objet # 24601 est le camion de Bob, puis simplement accéder à cet objet chaque fois qu'il veut voir l'état actuel du camion de Bob.

Notez que l'approche fonctionnelle n'est pas sans avantages dans un environnement monothread ou dans un environnement multithread où les threads observeront principalement les données plutôt que de les modifier. Tout thread observateur qui copie la référence d'objet contenue dans AllTrucks et examine ensuite les états de camion ainsi représentés verra l'état de tous les camions au moment où il a saisi la référence. Chaque fois qu'un thread d'observation souhaite voir des données plus récentes, il peut simplement récupérer la référence. D'un autre côté, le fait que la totalité de l'état de la flotte soit représenté par un seul objet immuable exclurait la possibilité que deux threads mettent à jour différents camions simultanément, car chaque thread, s'il était laissé à lui-même, produirait un nouvel objet "état de la flotte" qui inclurait le nouvel état de son camion et les anciens états de tous les autres. La correction peut être assurée si chaque thread utilise CompareExchange pour mettre à jour AllTrucks uniquement s'il n'a pas changé et répond à un CompareExchange échoué en régénérant son objet d'état et en réessayant le mais si plusieurs threads tentent une opération d'écriture simultanée, les performances seront généralement moins bonnes que si toutes les écritures étaient effectuées sur un seul thread; plus les threads tentent de telles opérations simultanées, plus les performances seront mauvaises.

Si des objets de camion individuels sont modifiables mais ont identités immuables, le scénario multithread devient plus propre. Un seul fil peut être autorisé à fonctionner à la fois sur un camion donné, mais les fils opérant sur différents camions peuvent le faire sans interférence. Bien qu'il existe des moyens d'imiter un tel comportement même lorsque vous utilisez des objets immuables (par exemple, vous pouvez définir les objets "AllTrucks" de sorte que définir l'état du camion appartenant à XXX à SSS nécessiterait simplement de générer un objet qui dit "à partir de [Time], l'état du camion appartenant à [XXX] est maintenant [SSS]; l'état de tout le reste est [Ancienne valeur de AllTrucks] ". La génération d'un tel objet serait suffisamment rapide pour que même en présence de conflits, un CompareExchange loop ne prendrait pas longtemps. D'un autre côté, l'utilisation d'une telle structure de données augmenterait considérablement le temps nécessaire pour trouver le camion d'une personne particulière. L'utilisation d'objets mutables avec identités immuables évite que problème.

9
supercat

Il n'y a pas de bien ou de mal, cela dépend juste de ce que vous préférez. Il y a une raison pour laquelle certaines personnes préfèrent les langues qui favorisent un paradigme sur un autre et un modèle de données sur un autre. Cela dépend simplement de vos préférences et de ce que vous voulez réaliser (et être capable d'utiliser facilement les deux approches sans aliéner les fans purs et durs d'un côté ou de l'autre est un Saint Graal que certaines langues recherchent).

Je pense que la meilleure façon et la plus rapide de répondre à votre question est de vous diriger Avantages et inconvénients de l'immuabilité vs mutabilité .

8
haylem

Consultez cet article de blog: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . Il résume pourquoi les objets immuables sont meilleurs que mutables. Voici une courte liste d'arguments:

  • les objets immuables sont plus simples à construire, à tester et à utiliser
  • les objets vraiment immuables sont toujours thread-safe
  • ils aident à éviter le couplage temporel
  • leur utilisation est sans effet secondaire (pas de copies défensives)
  • le problème de mutabilité de l'identité est évité
  • ils ont toujours une atomicité d'échec
  • ils sont beaucoup plus faciles à mettre en cache

Les gens utilisent des objets mutables, si je comprends bien, car ils mélangent toujours OOP avec une programmation procédurale impérative.

7
yegor256

Dans Java les objets immuables nécessitent un constructeur qui prendra toutes les propriétés de l'objet (ou le constructeur les crée à partir de autres arguments ou valeurs par défaut.) Ces propriétés devraient être marquées final .

Il y a quatre problèmes avec cela principalement liés à la liaison de données :

  1. Les métadonnées de réflexion des constructeurs Java ne conservent pas les noms des arguments.
  2. Les constructeurs Java (et les méthodes) n'ont pas de paramètres nommés (également appelés étiquettes), ce qui crée de la confusion avec de nombreux paramètres.
  3. Lors de l'héritage d'un autre objet immuable, l'ordre des constructeurs doit être appelé. Cela peut être assez délicat au point d'abandonner et de laisser l'un des champs non final.
  4. La plupart des technologies de liaison (comme Spring MVC Data Binding, Hibernate, etc ...) ne fonctionneront qu'avec des constructeurs par défaut sans argument (car les annotations n'existaient pas toujours).

Vous pouvez atténuer # 1 et # 2 en utilisant des annotations comme @ConstructorProperties et en créant un autre objet générateur mutable (généralement fluide) pour créer l'objet immuable.

5
Adam Gent

Je suis surpris que personne n'ait mentionné les avantages de l'optimisation des performances. Selon la langue, un compilateur peut faire un tas d'optimisations lorsqu'il traite des données immuables car il sait que les données ne changeront jamais. Toutes sortes de choses sont ignorées, ce qui vous offre d'énormes avantages en termes de performances.

Les objets immuables éliminent également presque toute la classe des bogues d'état.

Ce n'est pas aussi grand qu'il devrait l'être car son plus difficile, ne s'applique pas à toutes les langues et la plupart des gens ont appris le codage impératif.

J'ai également constaté que la plupart des programmeurs sont satisfaits de leur boîte et sont souvent résistants aux nouvelles idées qu'ils ne comprennent pas complètement. En général, les gens n'aiment pas le changement.

N'oubliez pas non plus que l'état de la plupart des programmeurs est mauvais. La plupart des programmes qui se font à l'état sauvage sont terribles et sont causés par un manque de compréhension et de politique.

5
Jay

Pourquoi les gens utilisent-ils une fonctionnalité puissante? Pourquoi les gens utilisent-ils la méta-programmation, la paresse ou la frappe dynamique? La réponse est la commodité. L'état mutable est si facile. Il est si facile de mettre à jour sur place et le seuil de taille du projet où l'état immuable est plus productif que l'état mutable est assez élevé, donc le choix ne vous mordra pas pendant un certain temps.

4
dan_waterworth

Les langages de programmation sont conçus pour être exécutés par des ordinateurs. Tous les éléments constitutifs importants des ordinateurs - CPU, RAM, cache, disques - sont mutables. Quand ils ne le sont pas (BIOS), ils sont vraiment immuables et vous ne pouvez pas non plus créer de nouveaux objets immuables.

Par conséquent, tout langage de programmation construit sur des objets immuables souffre d'un manque de représentation dans sa mise en œuvre. Et pour les premiers langages tels que C, c'était une grosse pierre d'achoppement.

4
MSalters

Les objets mutables sont utilisés lorsque vous devez définir plusieurs valeurs après l'instanciation de l'objet.

Vous ne devriez pas avoir de constructeur avec, disons, six paramètres. Au lieu de cela, vous modifiez l'objet avec des méthodes de définition.

Un exemple de ceci est un objet Report, avec des paramètres pour la police, l'orientation, etc.

Pour faire court: les mutables sont utiles lorsque vous avez beaucoup d'état à définir sur un objet et qu'il ne serait pas pratique d'avoir une signature constructeur très longue.

EDIT: Le modèle Builder peut être utilisé pour construire l'état entier de l'objet.

1
Tulains Córdova

Beaucoup de gens ont eu de bonnes réponses, donc je tiens à souligner quelque chose que vous avez abordé qui était très attentif et extrêmement vrai et n'a pas été mentionné ailleurs ici.

La création automatique de setters et de getters est une idée horrible et horrible, mais c'est la première façon pour les gens soucieux de la procédure de forcer OO dans leur état d'esprit. Les setters et les getters, ainsi que les propriétés, ne doivent être créés que lorsque vous trouvez que vous en avez besoin et pas par défaut

En fait, bien que vous ayez besoin de getters assez régulièrement, la seule façon dont les setters ou les propriétés inscriptibles doivent jamais exister dans votre code est via un modèle de générateur où ils sont verrouillés après que l'objet a été complètement instancié.

De nombreuses classes sont mutables après la création, ce qui est bien, il ne devrait tout simplement pas avoir ses propriétés directement manipulées - à la place, il devrait être invité à manipuler ses propriétés via des appels de méthode avec une logique métier réelle (Oui, un setter est à peu près le même chose comme manipulant directement la propriété)

Maintenant, cela ne s'applique pas non plus au code/langages de style "Scripting", mais au code que vous créez pour quelqu'un d'autre et attendez que d'autres lisent à plusieurs reprises au fil des ans. J'ai dû commencer à faire cette distinction récemment parce que j'aime tellement jouer avec Groovy et qu'il y a une énorme différence de cibles.

1
Bill K

Je pense que l'utilisation d'objets mutables découle d'une pensée impérative: vous calculez un résultat en modifiant pas à pas le contenu des variables mutables (calcul par effet secondaire).

Si vous pensez fonctionnellement, vous voulez avoir un état immuable et représenter les états ultérieurs d'un système en appliquant des fonctions et en créant de nouvelles valeurs à partir des anciennes.

L'approche fonctionnelle peut être plus propre et plus robuste, mais elle peut être très inefficace en raison de la copie, de sorte que vous souhaitez revenir à une structure de données partagée que vous modifiez de manière incrémentielle.

Le compromis que je trouve le plus raisonnable est le suivant: commencez avec des objets immuables, puis passez à des objets mutables si votre implémentation n'est pas assez rapide. De ce point de vue, l'utilisation systématique d'objets modifiables dès le début peut être considérée comme une sorte de optimisation prématurée: vous choisissez l'implémentation la plus efficace (mais aussi la plus difficile à comprendre et à déboguer) dès le début.

Alors, pourquoi de nombreux programmeurs utilisent-ils des objets mutables? À mon humble avis pour deux raisons:

  1. De nombreux programmeurs ont appris à programmer en utilisant un paradigme impératif (procédural ou orienté objet), donc la mutabilité est leur approche de base pour définir le calcul, c'est-à-dire qu'ils ne savent pas quand et comment utiliser l'immuabilité parce qu'ils ne le connaissent pas.
  2. De nombreux programmeurs s'inquiètent trop tôt des performances, alors qu'il est souvent plus efficace de se concentrer d'abord sur l'écriture d'un programme fonctionnellement correct, puis d'essayer de trouver des goulots d'étranglement et de l'optimiser.
1
Giorgio

Je sais que vous posez des questions sur Java, mais j'utilise constamment mutable vs immuable dans objective-c. Il existe un tableau immuable NSArray et un tableau mutable NSMutableArray. Ce sont deux classes différentes écrites spécifiquement de manière optimisée pour gérer l'utilisation exacte. Si j'ai besoin de créer un tableau et de ne jamais altérer son contenu, j'utiliserais NSArray, qui est un objet plus petit et beaucoup plus rapide dans ce qu'il fait, par rapport à un tableau mutable.

Donc, si vous créez un objet Person qui est immuable, vous n'avez besoin que d'un constructeur et de getters, donc l'objet sera plus petit et utilisera moins de mémoire, ce qui rendra votre programme plus rapide. Si vous devez modifier l'objet après sa création, un objet Personne modifiable sera préférable pour pouvoir modifier les valeurs au lieu de créer un nouvel objet.

Donc: en fonction de ce que vous prévoyez de faire avec l'objet, choisir mutable vs immuable peut faire une énorme différence en termes de performances.

1
Michael Ozeryansky

Sans objets mutables, vous n'avez aucun état. Certes, c'est une bonne chose si vous pouvez le gérer et s'il y a une chance qu'un objet puisse être référencé à partir de plusieurs threads. Mais le programme va être plutôt ennuyeux. De nombreux logiciels, en particulier les serveurs Web, évitent de prendre la responsabilité des objets mutables en repoussant la mutabilité sur les bases de données, les systèmes d'exploitation, les bibliothèques système, etc. En pratique, cela libère le programmeur des problèmes de mutabilité et rend le Web (et autres) développement abordable. Mais la mutabilité est toujours là.

En général, vous avez trois types de classes: les classes normales, non thread-safe, qui doivent être soigneusement gardées et protégées; des classes immuables, qui peuvent être utilisées librement; et des classes mutables et thread-safe qui peuvent être utilisées librement mais qui doivent être écrites avec un soin extrême. Le premier type est le plus gênant, les pires étant ceux qui sont considérés comme étant du troisième type. Bien sûr, le premier type est le plus facile à écrire.

Je me retrouve généralement avec beaucoup de classes normales et mutables que je dois regarder très attentivement. Dans une situation multi-thread, la synchronisation nécessaire ralentit tout, même lorsque je peux éviter une étreinte mortelle. Donc, je fais généralement des copies immuables de la classe mutable et je les remets à quiconque peut l'utiliser. Une nouvelle copie immuable est nécessaire à chaque fois que l'orignal mute, alors j'imagine parfois que j'ai peut-être une centaine d'exemplaires de l'original. Je suis totalement dépendant de Garbage Collection.

En résumé, les objets mutables non thread-safe sont corrects si vous n'utilisez pas plusieurs threads. (Mais le multithreading inflitrate partout - faites attention!) Ils peuvent être utilisés en toute sécurité sinon si vous les limitez aux variables locales ou les synchronisez rigoureusement. Si vous pouvez les éviter en utilisant du code éprouvé d'autres personnes (bases de données, appels système, etc.), faites-le. Si vous pouvez utiliser une classe immuable, faites-le. Et je pense , en général , les gens ignorent les problèmes de multithreading ou en sont (sensiblement) terrifiés et utilisent toutes sortes de trucs pour éviter le multithreading (ou plutôt, en pousser la responsabilité ailleurs).

En tant que P.S., je sens que Java getters et setters deviennent incontrôlables. Vérifiez this out.

1
RalphChapin

Outre de nombreuses autres raisons données ici, un problème est que les langages traditionnels ne prennent pas bien en charge l'immuabilité. Au moins, vous êtes pénalisé pour l'immuabilité dans la mesure où vous devez ajouter des mots clés supplémentaires comme const ou final, et devez utiliser des constructeurs illisibles avec de très nombreux arguments ou de longs modèles de générateur de code.

C'est beaucoup plus facile dans les langues conçues avec l'immuabilité à l'esprit. Considérez ceci Scala extrait de code pour définir une personne de classe, éventuellement avec des arguments nommés, et créer une copie avec un attribut modifié:

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")

Donc, si vous voulez que les développeurs adoptent l'immuabilité, c'est l'une des raisons pour lesquelles vous pourriez envisager de passer à un langage de programmation plus moderne.

1
Hans-Peter Störr

Immuable signifie que vous ne pouvez pas modifier la valeur et mutable signifie que vous pouvez modifier la valeur si vous pensez en termes de primitives et d'objets. Les objets sont différents des primitives dans Java dans la mesure où ils sont intégrés dans des types. Les primitives sont intégrées dans des types comme int, booléen et void.

Beaucoup de gens pensent que les primitives et les variables d'objets qui ont un modificateur final devant elles sont immuables, cependant, ce n'est pas exactement vrai. Donc, final ne signifie presque pas immuable pour les variables. Consultez ce lien pour un exemple de code:

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();
    }
}
0
Jon