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


250

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

J'ai quatre ans d'expérience dans la programmation Java et, à mon avis, après avoir créé une classe, les personnes créent des getters et des setters dans l'EDI (ce qui la rend mutable). Y a-t-il un manque de conscience ou pouvons-nous nous en tirer avec l'utilisation d'objets mutables dans la plupart des scénarios?


¹ Un objet immuable est un objet dont l’état ne peut plus être modifié après sa création.
² L' objet mutable est un objet qui peut être modifié après sa création.


42
Je pense que, en dehors des raisons légitimes (comme mentionné par Péter ci - dessous), « développeur paresseux » est une raison plus fréquente que « stupide » développeur ». Et avant « développeur stupide » il y a aussi « développeur mal informé ».
Joachim Sauer

201
Chaque programmeur / blogueur évangélique compte 1000 lecteurs de blogs passionnés qui se réinventent immédiatement et adoptent les dernières techniques. Pour chacun d'entre eux, il y a 10 000 programmeurs qui travaillent d'arrache-pied et qui travaillent un jour ou l'autre et distribuent le produit. Ces gars-là utilisent des techniques éprouvées et fiables qui fonctionnent pour eux depuis des années. Ils attendent que de nouvelles techniques soient largement adoptées et montrent les avantages réels avant de les utiliser. Ne les appelez pas stupides, et ils sont tout sauf paresseux, appelez-les plutôt "occupés".
Binary Worrier

22
@BinaryWorrier: les objets immuables ne sont guère une "chose nouvelle". Ils ne sont peut-être pas très utilisés pour les objets de domaine, mais Java et C # les utilisent depuis le début. Aussi: "paresseux" n'est pas toujours un mauvais mot, certains types de "paresseux" sont un avantage absolu pour un développeur.
Joachim Sauer

11
@ Joachim: Je pense qu'il est assez évident que "paresseux" ait été utilisé dans son sens péjoratif ci-dessus :) devenir tout à coup la saveur du mois . Je ne dis pas qu'ils sont une mauvaise chose (ils ne le sont pas), ou qu'ils n'ont pas leur place (ils le font évidemment), allez-y doucement, parce qu'ils n'ont pas entendu la dernière bonne Parole et été converti avec ferveur (sans vous blâmer pour le commentaire "paresseux", je sais que vous avez tenté de l'atténuer).
Binary Worrier

116
-1, les objets immuables ne sont pas "bons". Juste plus ou moins approprié pour des situations spécifiques. Quiconque vous dit une technique ou une autre est objectivement «bon» ou «mauvais» pour une autre dans toutes les situations, c'est vous vendre la religion.
GrandmasterB

Réponses:


326

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

Les objets immuables rendent effectivement la vie plus simple dans de nombreux cas. Ils sont particulièrement applicables aux types de valeur, où les objets n'ont pas d'identité et peuvent donc être facilement remplacés. Et ils peuvent rendre la programmation concurrente plus sûre et plus propre (la plupart des bogues de concomitance notoirement difficiles à trouver sont en fin de compte causés par l’état mutable partagé entre les threads). Cependant, pour les objets volumineux et / ou complexes, créer une nouvelle copie de l'objet à chaque modification peut s'avérer très coûteux et / ou fastidieux. Et pour les objets ayant une identité distincte, modifier un objet existant est beaucoup plus simple et intuitif que de créer une nouvelle copie modifiée de celui-ci.

Pensez à un personnage de jeu. Dans les jeux, la vitesse est la priorité absolue. Par conséquent, si vous représentez vos personnages avec des objets mutables, votre jeu fonctionnera beaucoup plus rapidement qu'une implémentation alternative dans laquelle une nouvelle copie du personnage du jeu est générée à 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 d’essence à la station-service, vous la percevez comme le même objet (c’est-à-dire que son identité est conservée tant que son état change) - ce n’est pas comme si l’ancienne voiture avec un réservoir vide était remplacée par une nouvelle instances de voiture ayant leur réservoir progressivement de plus en plus plein. Ainsi, chaque fois que nous modélisons un domaine réel dans un programme, il est généralement plus simple et plus simple d'implémenter le modèle de domaine en utilisant des objets mutables pour représenter des entités réelles.

En dehors de toutes ces raisons légitimes, hélas, la raison la plus probable pour laquelle les gens continuent à créer des objets mutables est l'inertie de l'esprit, autrement dit la résistance au changement. Notez que la plupart des développeurs d'aujourd'hui ont été formés bien avant que l'immutabilité (et le paradigme de contenu, la programmation fonctionnelle) ne soit devenue "tendance" 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’êtres humains résistent positivement aux nouvelles idées et processus. «J'ai été la programmation comme ça depuis nn ans et je ne me soucie pas des dernières manies stupides! »


27
C'est vrai. Surtout dans la programmation graphique, les objets mutables sont très pratiques.
Florian Salihovic

23
Ce n'est pas seulement une résistance, je suis sûr que beaucoup de développeurs aimeraient essayer les derniers et les plus performants, mais combien de fois les nouveaux projets tournent-ils dans l'environnement du développeur moyen où ils peuvent appliquer ces nouvelles pratiques? Tout le monde ne peut ni ne veut écrire un projet de loisir dans le seul but d'essayer un état immuable.
Steven Evers

29
Deux petites mises en garde: (1) Prenez le personnage de jeu en mouvement. La Pointclasse dans .NET par exemple est immuable, mais la création de nouveaux points à la suite de modifications est simple et donc abordable. L'animation d'un personnage immuable peut être rendue très économique en découplant les «pièces mobiles» (mais oui, certains aspects sont alors mutables). (2) Les «objets volumineux et / ou complexes» peuvent très bien être immuables. Les cordes sont souvent volumineuses et bénéficient généralement de l'immuabilité. Une fois, j'ai réécrit une classe de graphes complexe pour qu'elle soit immuable, ce qui rend le code plus simple et plus efficace. Dans de tels cas, la clé est d'avoir un constructeur mutable.
Konrad Rudolph

4
@ KonradRudolph, bons points, merci. Je ne voulais pas exclure l'utilisation de l'immuabilité dans des objets complexes, mais implémenter correctement et efficacement une telle classe est loin d'être une tâche triviale, et l'effort supplémentaire requis peut ne pas être toujours justifié.
Péter Török

6
Vous faites un bon point sur l'état vs identité. C'est pourquoi Rich Hickey (auteur de Clojure) a divisé les deux à Clojure. On pourrait dire que la voiture que vous avez avec un demi-réservoir d’essence n’est pas la même que celle avec un quart de réservoir. Ils ont la même identité, mais ils ne sont pas les mêmes. Chaque "tic" du temps de notre réalité crée un clone de chaque objet de notre monde. Notre cerveau les associe simplement à une identité commune. Clojure a des références, des atomes, des agents, etc. pour représenter le temps. Et des cartes, des vecteurs et des listes pour le temps réel.
Timothy Baldridge Le

130

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 modifier constamment le code pour l’éloigner des valeurs par défaut - plus correct ou non. Et l'immuabilité n'est pas une panacée, pas plus que toute autre approche. Cela facilite certaines choses mais rend beaucoup d’autres beaucoup plus difficile, comme certaines réponses l’ont déjà indiqué.


9
Dans la plupart des IDE modernes que je connaisse, il faut pratiquement le même effort pour générer uniquement des accesseurs, que pour générer à la fois des accesseurs et des setters. Bien qu'il soit vrai que l'ajout final, constetc. demande un peu d'effort supplémentaire ... sauf si vous configurez un modèle de code :-)
Péter Török

10
@ PéterTörök, ce n'est pas seulement un effort supplémentaire, c'est le fait que vos collègues codeurs voudront vous pendre à l'effigie, car ils trouvent votre style de codage si étranger à leur expérience. Cela décourage également ce genre de chose.
Onorio Catenacci

5
Cela pourrait être surmonté avec plus de communication et d'éducation, par exemple, il est conseillé de parler ou de présenter à ses coéquipiers un nouveau style de codage avant de l'introduire dans une base de code existante. Il est vrai qu’un bon projet a un style de codage commun qui n’est pas simplement un amalgame des styles de codage privilégiés par tous les membres du projet (anciens et actuels). Donc, introduire ou changer des idiomes de codage devrait être une décision d'équipe.
Péter Török

3
@ PéterTörök Dans un langage impératif, les objets mutables sont le comportement par défaut. Si vous ne voulez que des objets immuables, il est préférable de passer à un langage fonctionnel.
emory

3
@ PéterTörök Il est un peu naïf de supposer que l'intégration de l'immuabilité dans un programme n'implique rien de plus que de supprimer tous les paramètres. Vous avez toujours besoin d'un moyen pour que l'état du programme change progressivement, et pour cela, vous avez besoin de constructeurs, d'immuabilité de popsicle, ou de substituts mutables, qui nécessitent tous environ 1 milliard de fois l'effort nécessaire pour que les décrocheurs prennent le temps de le laisser tomber.
Asad Saeeduddin

49
  1. Il y a une place pour la mutabilité. Les principes de conception pilotés par domaine fournissent une solide compréhension de ce qui devrait être modifiable et de ce qui devrait être immuable. Si vous y réfléchissez, vous réaliserez qu'il n'est pas pratique de concevoir un système dans lequel tout changement d'état d'un objet nécessite sa destruction et sa recomposition, ainsi que tout objet qui l'a référencé. Avec des systèmes complexes, cela pourrait facilement conduire à un effacement complet et à une reconstruction du graphe d'objets du système entier.

  2. La plupart des développeurs ne font pas quoi que les exigences de performances soient suffisamment importantes pour qu'ils aient besoin de se concentrer sur la concurrence (ou sur de nombreux autres problèmes considérés universellement comme une bonne pratique par ceux qui sont informés).

  3. Il y a des 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, son identité change. Donc, vous définissez la nouvelle valeur sur l'autre objet et cela 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. Si cela continuait, il en résulterait des régressions infinies. Après avoir lu votre question, j’ai fait une petite étude de cas. Voici à quoi cela ressemble. Avez-vous une approche alternative qui permet une telle fonctionnalité tout en maintenant 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., Si vous avez une approche différente de la gestion des graphes complexes avec des relations bidirectionnelles utilisant uniquement des objets immuables, j'aimerais beaucoup l'entendre. (Je suppose que nous pouvons être d'accord sur le fait qu'une collection / un tableau est un objet)
smartcaveman

2
@AndresF., (1) Ma première déclaration n'était pas universelle et n'était donc pas fausse. En fait, j'ai fourni un exemple de code pour expliquer comment c'était nécessairement vrai dans certains cas très fréquents dans le développement d'applications. (2) Les relations bidirectionnelles requièrent généralement la mutabilité. Je ne crois pas que Java soit le coupable. Comme je l'ai dit, je serais heureux d'évaluer toutes les solutions constructives que vous suggéreriez, mais à ce stade, vos commentaires ressemblent beaucoup à "Vous avez tort parce que je l'ai dit".
smartcaveman

14
@smartcaveman À propos de (2), je suis également en désaccord: en général, une "relation bidirectionnelle" est un concept mathématique orthogonal à la mutabilité. Comme d'habitude implémenté en Java, il nécessite une mutabilité (je suis d'accord avec vous sur ce point). Cependant, je peux penser à une autre implémentation: une classe de relations entre les deux objets, avec un constructeur Relationship(a, b); Au moment de créer la relation, les deux entités a& bexistent déjà, et la relation elle-même est également immuable. Je ne dis pas que cette approche est pratique en Java; juste que c'est possible.
Andres F.

4
@AndresF., Donc, en fonction de ce que vous dites, si Rest le Relationship(a,b)et les deux aet bsont immuables, ani bne détiendrait une référence R. Pour que cela fonctionne, la référence devrait être stockée ailleurs (comme une classe statique). Est-ce que je comprends bien votre intention?
smartcaveman

9
Il est possible de stocker une relation bidirectionnelle pour des données immuables, comme le fait remarquer Chthulhu via la paresse. Voici un moyen de le faire: haskell.org/haskellwiki/Tying_the_Knot
Thomas Eding

36

J'ai lu "des structures de données purement fonctionnelles", et cela m'a fait comprendre qu'il existe de nombreuses 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 retourner une nouvelle arborescence à chaque fois: votre nouvelle arborescence devra faire une copie de chaque nœud modifié (les branches non modifiées sont partagées). Ce n'est pas si grave pour votre fonction d'insertion, mais pour moi, les choses sont devenues assez inefficaces rapidement lorsque 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 réalisez jamais à quel point un état mutable partagé peut être terrible, si votre code n'est pas exécuté de manière à exposer les problèmes de simultanéité.


3
Est-ce le livre Okasaki?
Escargot mécanique

oui. un peu sec mais une tonne de bonnes informations ...
Paul Sanwald

4
Drôle, j'ai toujours pensé que l'arbre rouge / noir d'Okasakis était tellement plus simple. 10 lignes ou plus. J'imagine que le principal avantage réside dans le fait que vous souhaitez conserver l'ancienne version également.
Thomas Ahle

Bien que la dernière phrase ait probablement été vraie dans le passé, il n’est pas clair qu’elle restera vraie à l’avenir, au vu des tendances actuelles en matière de matériel, etc.
jk.

29

De mon point de vue, c'est un manque de conscience. Si vous examinez d'autres langages JVM connus (Scala, Clojure), les objets mutables sont rarement vus dans le code. C'est pourquoi les gens commencent à les utiliser dans des scénarios où le threading unique ne suffit pas.

J'apprends actuellement Clojure et j'ai une petite expérience de Scala (plus de 4 ans en Java) et votre style de codage change en raison de la prise de conscience de l'état.


Peut-être que "connu" plutôt que "populaire" serait un meilleur choix de mot.
Den

Oui c'est vrai.
Florian Salihovic

5
+1: je suis d'accord: après avoir appris quelques notions de Scala et de Haskell, j'ai tendance à utiliser final en Java et const en C ++ partout. J'utilise également des objets immuables si possible et, même si des objets mutables sont encore nécessaires très souvent, il est étonnant de constater à quelle fréquence vous pouvez utiliser des objets immuables.
Giorgio

5
J'ai lu ce commentaire après deux ans et demi et mon opinion a changé en faveur de l'immutabilité. Dans mon projet actuel (qui est en Python), nous utilisons très rarement des objets mutables. Même nos données persistantes sont immuables: nous créons de nouveaux enregistrements à la suite d'opérations et supprimons d'anciens enregistrements lorsqu'ils ne sont plus nécessaires, mais nous ne mettons jamais à jour aucun enregistrement sur disque. Inutile de dire que cela a rendu notre application simultanée et multi-utilisateur beaucoup plus facile à mettre en œuvre et à maintenir jusqu'à présent.
Giorgio

13

Je pense que l' un des principaux facteurs contribuant a été ignoré: Java Beans comptent beaucoup sur un style spécifique d'objets muter, et (surtout compte tenu de la source) tout à fait un peu de gens semblent prendre cela comme un (ou même le ) exemple canonique de la façon dont tous Java devrait être écrit.


4
+1, le motif getter / setter est utilisé trop souvent comme une sorte d'implémentation par défaut après la première analyse des données.
Jaap

C'est probablement un gros problème ... "parce que c'est comme ça que tout le monde le fait" ... donc ça doit être juste. Pour un programme "Hello World", c'est probablement le plus simple. La gestion des changements d'état d'objet via une série de propriétés mutables ... un peu plus délicate que la profondeur de la compréhension de "Hello World". 20 ans plus tard, je suis très surpris que le summum de la programmation au XXe siècle soit d'écrire les méthodes getX et setX (fastidieuses) sur chaque attribut d'un objet dépourvu de structure. Il n’est qu’à deux pas d’accéder directement aux propriétés publiques avec une mutabilité totale.
Darrell Teague

12

Tous les systèmes Java d'entreprise sur lesquels j'ai travaillé au cours de ma carrière utilisent Hibernate ou l'API JPA (Java Persistence API). Hibernate et JPA indiquent essentiellement que votre système utilise des objets mutables, car leur principe de base est qu'ils détectent et enregistrent les modifications apportées à vos objets de données. Pour de nombreux projets, la facilité de développement apportée par Hibernate est plus convaincante que les avantages d’objets immuables.

Il est évident que les objets mutables existent depuis beaucoup plus longtemps qu'Hibernate. Hibernate n'est donc probablement pas la "cause" initiale de la popularité des objets mutables. Peut-être que la popularité des objets mutables a permis à Hibernate de s’épanouir.

Mais aujourd’hui, si de nombreux jeunes programmeurs se moquent des systèmes d’entreprise utilisant Hibernate ou un autre ORM, ils prendront vraisemblablement l’habitude d’utiliser des objets mutables. Des frameworks comme Hibernate peuvent enraciner la popularité des objets mutables.


Excellent point. Pour être tout pour tout le monde, de tels cadres n’ont pas d’autre choix que de passer au plus petit dénominateur commun, d’utiliser des proxies basés sur la réflexion et de se frayer un chemin vers la flexibilité. Bien sûr, cela crée des systèmes avec peu ou pas de règles de transition d'état ou un moyen commun de les mettre en œuvre malheureusement. Après de nombreux projets, je ne suis pas tout à fait sûr de ce qui est préférable pour ce qui est de la rapidité, de l’extensibilité et de l’exactitude. J'ai tendance à penser à un hybride. Dynamique ORM, mais avec quelques définitions pour lesquelles des champs sont requis et quels changements d'état devraient être possibles.
Darrell Teague

9

Un point important qui n’a pas encore été mentionné est que le fait que l’état d’un objet soit mutable permette de rendre immuable l’ 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’à 00h51, une variable AllTruckscontienne une référence à l’objet n ° 451, qui est la racine d’une structure de données qui indique quelle cargaison est contenue dans tous les camions d’une flotte à ce moment-là (12h51), et certaines variables. BobsTruckpeut être utilisé pour obtenir une référence à l'objet # 24601, à un objet indiquant quelle cargaison est contenue dans le camion de Bob à ce moment-là (12h51). À 12h52, certains camions (y compris ceux de Bob) sont chargés et déchargés, et les structures de données sont mises à jour de sorte à AllTruckscontenir une référence à une structure de données indiquant que la cargaison est dans tous les camions à compter de 12h52.

Que devrait-il arriver BobsTruck?

Si la propriété 'cargo' de chaque objet camion est immuable, l'objet # 24601 représentera à tout jamais l'état du camion de Bob à 00h51. Si BobsTruckcontient une référence directe à l'objet n ° 24601, à moins que le code mis à jour ne soit AllTruckségalement mis à jour BobsTruck, il ne représentera plus l'état actuel du camion de Bob. Notez en outre que, sauf si BobsTruckest stocké dans une forme d'objet mutable, le code AllTruckspouvant être mis à jour pourrait être mis à jour uniquement si le code était explicitement programmé pour le faire.

Si on veut pouvoir BobsTruckobserver l'état du camion de Bob tout en maintenant tous les objets immuables, on pourrait avoir BobsTruckune fonction immuable qui, étant donné la valeur qui AllTrucksa eu ou a eu à un moment donné, donnera l'état du camion de Bob à ce temps. On pourrait même lui demander d’assumer deux fonctions immuables - l’une comme celle ci-dessus, et l’autre acceptant une référence à un état de flotte et un nouvel état de camion, et renvoyant une référence à un nouvel état de flotte correspondait à l'ancien, sauf que le camion de Bob aurait le nouvel état.

Malheureusement, avoir à 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 autre approche serait de dire que l’objet n ° 24601 restera toujours et à jamais (tant que quelqu'un en fera référence) représente l’ état actuel du camion de Bob. Le code qui voudra accéder de manière répétée à l'état actuel du camion de Bob n'aura pas à exécuter une fonction fastidieuse à 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 à tout moment, il veut voir l'état actuel du camion de Bob.

Notez que l'approche fonctionnelle n'est pas sans avantages dans un environnement mono-thread, ou dans un environnement multi-thread où les threads ne feront qu'observer les données plutôt que de les modifier. Tout fil de l'observateur qui copie la référence de l'objet contenue dansAllTruckset examine ensuite les états des camions représentés ainsi verront l’état de tous les camions à partir du moment où ils ont saisi la référence. Chaque fois qu'un thread observateur veut voir de nouvelles données, il peut simplement saisir à nouveau la référence. D'autre part, avoir l'ensemble de l'état de la flotte représenté par un seul objet immuable exclurait la possibilité que deux threads mettent à jour simultanément des camions différents, car chaque thread, laissé à lui-même, produirait un nouvel objet "state de la flotte", qui comprend: le nouvel état de son camion et les anciens états de tous les autres. L’exactitude peut être assurée si chaque thread utilise CompareExchangepour se mettre à jour AllTrucksque s’il n’a pas changé et répond à un échec.CompareExchangeen régénérant son objet d'état et en renouvelant l'opération, 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 une identité immuable , le scénario multithread devient plus clair. Un seul fil peut être autorisé à fonctionner à la fois sur un camion donné, mais les fils fonctionnant sur des camions différents peuvent le faire sans interférence. Bien qu’il existe des moyens d’émuler un tel comportement, même lorsqu’on utilise des objets immuables (par exemple, on pourrait définir les objets "AllTrucks" de manière à ce que le réglage de l’état d’un camion appartenant à XXX à SSS nécessite simplement de générer un objet qui dit "À partir de [Heure], 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 assez rapide pour que, même en cas de conflit, unCompareExchangela boucle ne prendrait pas longtemps. D'autre part, l'utilisation d'une telle structure de données augmenterait considérablement le temps requis pour trouver le camion d'une personne donnée. L'utilisation d'objets mutables d' identités immuables évite ce problème.


8

Il n'y a pas de bien ou de mal, cela dépend de ce que vous préférez. Il y a une raison pour laquelle certaines personnes préfèrent des langues qui privilégient un paradigme plutôt qu'un autre, et un modèle de données plutôt qu'un autre. Cela dépend de vos préférences et de ce que vous voulez réaliser (et être en mesure d'utiliser facilement les deux approches sans aliéner les fans inconditionnels d'un côté ou de l'autre est un saint graal que certaines langues recherchent).

Je pense que la meilleure et la plus rapide des réponses à votre question est de vous diriger vers les avantages et les inconvénients de l’immutabilité et de la mutabilité .


7

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)
  • problème de mutabilité d'identité évité
  • ils ont toujours l'atomicité d'échec
  • ils sont beaucoup plus faciles à cacher

Les gens utilisent des objets mutables, autant que je sache, car ils mélangent toujours la POO avec une programmation procédurale impérative.


5

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

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

  1. Les métadonnées de réflexion des constructeurs Java ne conservent pas les noms d'argument.
  2. Les constructeurs Java (et les méthodes) n'ont pas de paramètres nommés (aussi appelés étiquettes), de sorte que de nombreux paramètres peuvent être déroutants.
  3. Lorsque vous héritez d'un autre objet immuable, vous devez appeler l'ordre approprié des constructeurs. 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 (telles que la liaison de données Spring MVC, Hibernate, etc.) ne fonctionneront qu'avec des constructeurs par défaut sans argument (en raison du fait que les annotations n'existaient pas toujours).

Vous pouvez limiter les points 1 et 2 en utilisant des annotations telles @ConstructorPropertiesque la création d'un autre objet générateur modifiable (généralement couramment) pour créer l'objet immuable.


5

Je suis surpris que personne n'ait mentionné les avantages de l'optimisation des performances. Selon le langage utilisé, un compilateur peut procéder à de nombreuses optimisations lorsqu'il s'agit de données immuables, car il sait que les données ne changeront jamais. Toutes sortes de choses sont ignorées, ce qui vous donne d’énormes avantages en termes de performances.

De plus, les objets immuables éliminent presque toute la classe des bugs d'état.

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

De plus, j'ai constaté que la plupart des programmeurs sont heureux dans leur boîte et résistent souvent à de nouvelles idées qu'ils ne comprennent pas complètement. En général, les gens n'aiment pas le changement.

Rappelez-vous également que l'état de la plupart des programmeurs est mauvais. La plupart des programmes qui sont faits dans la nature sont terribles et sont causés par un manque de compréhension et de politique.


4

Pourquoi les gens utilisent-ils une fonctionnalité puissante? Pourquoi les gens utilisent-ils la méta-programmation, la paresse ou le typage 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 la taille du projet où l'état immuable est plus productif que l'état modable est assez élevé, de sorte que le choix ne vous fera pas souffrir pendant un certain temps.


4

Les langages de programmation sont conçus pour être exécutés par des ordinateurs. Tous les blocs de construction importants des ordinateurs - CPU, RAM, cache, disques - sont mutables. Lorsqu'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 au-dessus d’objets immuables souffre d’un déficit de représentation dans son implémentation. Et pour les langues anciennes telles que le C, c'était une grosse pierre d'achoppement.


1

Sans objets mutables, vous n'avez pas d'é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é d'objets mutables en poussant la mutabilité sur des bases de données, des systèmes d'exploitation, des bibliothèques système, etc. En pratique, cela libère le programmeur des problèmes de mutabilité et rend 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 fil-safe, qui doivent être soigneusement gardées et protégées; classes immuables, qui peuvent être utilisées librement; et des classes mutables, thread-safe qui peuvent être utilisées librement mais qui doivent être écrites avec un soin extrême. Le premier type est celui qui pose problème, les pires étant ceux que l’on pense être du troisième type. Bien sûr, les premiers types sont les plus faciles à écrire.

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

En résumé, les objets modifiables, non thread-safe, conviennent si vous n'utilisez pas plusieurs threads. (Mais le multithreading est une injustice partout - soyez prudent!) Ils peuvent être utilisés en toute sécurité si vous les limitez à des variables locales ou si vous les synchronisez de manière rigoureuse. Si vous pouvez les éviter en utilisant le 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 qu'en général , les gens ne sont pas au courant des problèmes de multithreading ou sont (sensiblement) terrifiés par eux et utilisent toutes sortes de trucs pour éviter le multithreading (ou plutôt, en poussent la responsabilité ailleurs).

En tant que PS, je sens que les jetters et les setters Java deviennent incontrôlables. Regarde ça .


7
L'état immuable est toujours l'état.
Jeremy Heiler

3
@ JeremyHeiler: C'est vrai, mais c'est l'état de quelque chose qui est mutable. Si rien ne change, il n'y a qu'un seul état, ce qui est la même chose que pas d'état du tout.
RalphChapin

1

Beaucoup de gens ont eu de bonnes réponses, je tiens donc à souligner un aspect très observateur et extrêmement vrai que vous avez évoqué et qui n'a pas été mentionné ailleurs.

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

En fait, bien que vous ayez besoin de getters assez régulièrement, le seul moyen de créer des propriétés d’inscription ou d’écriture dans votre code consiste à utiliser un modèle de générateur qui les verrouille après l’instanciation complète de l’objet.

De nombreuses classes sont modifiables après la création, ce qui est correct. Les propriétés ne doivent pas être manipulées directement. Vous devez plutôt lui demander de manipuler ses propriétés via des appels de méthodes contenant une logique métier réelle (oui, un opérateur est à peu près le même chose comme manipulant directement la propriété)

Maintenant, cela ne s'applique pas vraiment non plus aux codes / langages de style "Scripting", mais au code que vous créez pour quelqu'un d'autre et attendez des autres qu'ils lisent de manière répétée au fil des ans. J'ai dû faire cette distinction récemment parce que j'aime beaucoup jouer avec Groovy et qu'il y a une énorme différence entre les objectifs.


1

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

Vous ne devriez pas avoir de constructeur avec, par exemple, six paramètres. Au lieu de cela, vous modifiez l'objet avec les méthodes setter.

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

En bref: les mutables sont utiles lorsque vous avez beaucoup d’états à définir sur un objet et qu’il ne serait pas pratique d’avoir une signature de constructeur très longue.

EDIT: un modèle de générateur peut être utilisé pour construire l’état complet de l’objet.


3
ceci est présenté comme si la mutabilité et les changements après l'instanciation étaient le seul moyen de définir plusieurs valeurs. Par souci d'exhaustivité, notez que le motif Builder offre des capacités égales, voire plus puissantes, et qu'il n'est pas nécessaire de sacrifier l'immuabilité. new ReportBuilder().font("Arial").orientation("landscape").build()
moucher

1
"Il ne serait pas pratique d'avoir une très longue signature de constructeur.": Vous pouvez toujours grouper des paramètres dans des objets plus petits et les transmettre en tant que paramètres au constructeur.
Giorgio

1
@ cHao Et si vous voulez définir 10 ou 15 attributs? De plus, il ne semble pas bon qu'une méthode nommée withFontretourne un Report.
Tulains Córdova

1
En ce qui concerne la définition de 10 ou 15 attributs, le code pour le faire ne serait pas gênant si (1) Java savait comment éviter la construction de tous ces objets intermédiaires et si (2) encore, les noms étaient normalisés. Ce n'est pas un problème d'immutabilité; c'est un problème avec Java qui ne sait pas bien le faire.
cHao

1
@cHao Faire une chaîne d'appels pour 20 attributs, c'est moche. Le code laid a tendance à être un code de mauvaise qualité.
Tulains Córdova

1

Je pense que l'utilisation d'objets mutables découle de la 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 suivants d'un système en appliquant des fonctions et en créant de nouvelles valeurs à partir d'anciennes.

L'approche fonctionnelle peut être plus propre et plus robuste, mais elle peut s'avérer 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 par les objets immuables puis passez aux objets mutables si votre mise en œuvre n'est pas assez rapide. De ce point de vue, utiliser systématiquement des objets mutables dès le début peut être considéré comme une optimisation prématurée : vous choisissez dès le départ la mise en œuvre la plus efficace (mais aussi plus difficile à comprendre et à mettre au point).

Alors, pourquoi beaucoup de programmeurs utilisent-ils des objets mutables? IMHO pour deux raisons:

  1. De nombreux programmeurs ont appris à programmer en utilisant un paradigme impératif (procédural ou orienté objet). La mutabilité est donc leur approche de base pour définir le calcul: ils ne savent pas quand et comment utiliser l'immutabilité car ils ne le connaissent pas.
  2. De nombreux programmeurs s'inquiètent trop tôt pour les 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
L'enjeu n'est pas que la vitesse. Il existe de nombreux types de constructions utiles qui ne peuvent tout simplement pas être mises en œuvre sans utiliser des types mutables. Entre autres choses, la communication entre deux threads nécessite que, au minimum absolu, les deux doivent avoir une référence à un objet partagé sur lequel on peut mettre des données et l'autre pouvoir le lire. De plus, il est souvent beaucoup plus clair de dire "Modifiez les propriétés P et Q de cet objet" que de "Prenez cet objet, construisez un nouvel objet qui lui ressemble, à l'exception de la valeur de P, puis d'un nouvel objet est juste comme ça sauf pour Q ".
Supercat

1
Je ne suis pas un expert en PF, mais la communication de thread AFAIK (1) peut être obtenue en créant une valeur immuable dans un thread et en la lisant dans un autre (là encore, autant que je sache, c'est l'approche d'Erlang). change peu (par exemple, dans Haskell, setProperty record correspond à Java record.setProperty (valeur)), seule la sémantique change car le résultat de la définition d'une propriété est un nouvel enregistrement immuable.
Giorgio

1
"les deux doivent avoir une référence à un objet partagé sur lequel on peut mettre des données et l'autre peut le lire": plus précisément, vous pouvez rendre un objet immuable (tous les membres const en C ++ ou final en Java) et définir tout le contenu du constructeur. Ensuite, cet objet immuable est transféré du fil producteur au fil consommateur.
Giorgio

1
(2) J'ai déjà cité les performances comme une raison de ne pas utiliser l'état immuable. En ce qui concerne (1), le mécanisme qui implémente la communication entre les threads nécessite bien sûr un état modifiable, mais vous pouvez le masquer dans votre modèle de programmation, voir par exemple le modèle d'acteur ( en.wikipedia.org/wiki/Actor_model ). Ainsi, même si à un niveau inférieur, vous avez besoin de mutabilité pour implémenter la communication, vous pouvez avoir un niveau d'abstraction supérieur dans lequel vous communiquez entre les threads qui envoient des objets immuables dans les deux sens.
Giorgio

1
Je ne suis pas sûr de bien vous comprendre, mais même un langage purement fonctionnel où chaque valeur est immuable, il existe une chose qui peut être modifiée: l'état du programme, c'est-à-dire la pile de programmes actuelle et les liaisons de variables. Mais c'est l'environnement d'exécution. Quoi qu'il en soit, je suggère que nous discutions de cela en chat un moment, puis que nous effacions les messages de cette question.
Giorgio

1

Je sais que vous parlez de Java, mais j’utilise tout le temps 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 en modifier le contenu, j'utiliserais NSArray, un objet plus petit et beaucoup plus rapide, comparé à un tableau mutable.

Ainsi, si vous créez un objet Person immuable, vous avez uniquement besoin d'un constructeur et de getters. L'objet sera donc plus petit et utilise moins de mémoire, ce qui accélérera votre programme. Si vous devez modifier l'objet après la création, un objet Personne mutable sera préférable afin de pouvoir modifier les valeurs au lieu de créer un nouvel objet.

Donc, selon ce que vous envisagez de faire avec l'objet, choisir mutable ou immuable peut faire une énorme différence en termes de performances.


1

Outre de nombreuses autres raisons données ici, un problème est que les langues grand public ne supportent pas bien l'immutabilité. Au moins, vous êtes pénalisé pour immuabilité dans la mesure où vous devez ajouter des mots-clés supplémentaires, tels que const ou final, et devez utiliser des constructeurs illisibles avec de très nombreux arguments ou des modèles de constructeur trop longs.

C'est beaucoup plus facile dans les langues créées avec immuabilité. Considérez cet extrait de code Scala pour définir une classe Person, é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")

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.


cela ne semble rien ajouter de substantiel aux points soulevés et expliqués dans les 24 réponses précédentes
gnat

@gnat Laquelle des réponses précédentes montre que la plupart des langues traditionnelles ne supportent pas décemment l'immutabilité? Je pense que ce point n'a tout simplement pas été fait (j'ai vérifié), mais c'est à mon humble avis un obstacle assez important.
Hans-Peter Störr

celui-ci, par exemple, explique en profondeur ce problème en Java. Et au moins 3 autres réponses le concernent indirectement
Gnat

0

Immuable signifie que vous ne pouvez pas changer la valeur et mutable signifie que vous pouvez changer la valeur si vous pensez en termes de primitives et d'objets. Les objets diffèrent des primitives en Java en ce qu'ils sont de types intégrés. Les primitives sont des types intégrés tels que int, boolean et void.

Beaucoup de gens pensent que les variables primitives et objets qui ont un modificateur final en face d'eux sont immuables, cependant, ce n'est pas tout à fait 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();
    }
}

-1

Je pense qu'une bonne raison serait de modéliser des "objets" mutables "réels", comme des fenêtres d'interface. Je me souviens vaguement d'avoir lu que la POO avait été inventée lorsque quelqu'un avait essayé d'écrire un logiciel pour contrôler certaines opérations portuaires.


1
Je ne trouve pas encore où j'ai lu ceci sur les origines de la POO, mais selon Wikipedia , un système d'information régional intégré d'une grande société de transport de conteneurs OOCL est écrit en Smalltalk.
Alexey

-2

Java, dans de nombreux cas, requiert des objets mutables, par exemple lorsque vous voulez compter ou renvoyer quelque chose dans un visiteur ou un exécutable, vous avez besoin de variables finales, même sans multithreading.


5
finalest en fait environ un quart d'un pas vers l' immutabilité. Ne pas voir pourquoi vous en parlez.
cHao

avez-vous réellement lu mon post?
Ralf H

Avez-vous lu la question? :) Cela n'avait absolument rien à voir avec final. Le situer dans ce contexte évoque une confusion vraiment étrange finalentre «mutable», alors que le but même finalest de prévenir certains types de mutation. (BTW, pas mon downvote.)
cHao

Je ne dis pas que vous n'avez pas de point valable quelque part. Je dis que vous ne l'expliquez pas très bien. Je vois en quelque sorte où vous allez probablement, mais vous devez aller plus loin. Tel quel, cela semble juste confus.
cHao

1
En réalité, il existe très peu de cas dans lesquels un objet mutable est requis . Il existe des cas où le code serait plus clair ou, dans certains cas, plus rapide, en utilisant des objets mutables. Mais considérons que la grande majorité des modèles de conception ne sont fondamentalement qu'une interprétation à moitié assortie de programmation fonctionnelle pour des langages qui ne la prennent pas en charge de manière native. Une partie amusante de cela est que la PF ne nécessite une mutabilité que dans des endroits très choisis (à savoir, où les effets secondaires doivent se produire), et ces endroits sont généralement très limités en nombre, taille et étendue.
cHao
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.