Objets mutables vs immuables

173

J'essaie de comprendre les objets mutables vs immuables. L'utilisation d'objets mutables reçoit beaucoup de mauvaise presse (par exemple, le retour d'un tableau de chaînes à partir d'une méthode) mais j'ai du mal à comprendre quels sont les impacts négatifs de cela. Quelles sont les meilleures pratiques en matière d’utilisation d’objets mutables? Devriez-vous les éviter autant que possible?

Alex Angas
la source
stringest immuable, du moins en .NET, et je pense également dans de nombreux autres langages modernes.
Domenic
4
cela dépend de ce qu'est réellement une chaîne dans le langage - les «chaînes» d'erlang ne sont qu'un tableau d'entiers, et les «chaînes» de haskell sont des tableaux de caractères.
Chii
4
Les chaînes Ruby sont un tableau d'octets modifiables . Cela me rend absolument fou que vous puissiez les changer sur place.
Daniel Spiewak
6
Daniel - pourquoi? Le faites-vous par accident?
5
@DanielSpiewak La solution pour être enfoncé par des écrous que vous pouvez changer les cordes sur place est simple: ne le faites pas. La solution pour être enfoncé par des écrous parce que vous ne pouvez pas changer les cordes en place n'est pas si simple.
Kaz

Réponses:

162

Eh bien, il y a quelques aspects à cela.

  1. Les objets mutables sans identité de référence peuvent provoquer des bogues à des moments impairs. Par exemple, considérons un Personbean avec une equalsméthode basée sur la valeur :

    Map<Person, String> map = ...
    Person p = new Person();
    map.put(p, "Hey, there!");
    
    p.setName("Daniel");
    map.get(p);       // => null
    

    L' Personinstance est «perdue» dans la carte lorsqu'elle est utilisée comme clé car son hashCodeégalité et son égalité sont basées sur des valeurs modifiables. Ces valeurs ont changé en dehors de la carte et tout le hachage est devenu obsolète. Les théoriciens aiment insister sur ce point, mais en pratique, je n'ai pas trouvé que c'était trop un problème.

  2. Un autre aspect est la "raison" logique de votre code. C'est un terme difficile à définir, englobant tout, de la lisibilité au flux. De manière générale, vous devriez être capable de regarder un morceau de code et de comprendre facilement ce qu'il fait. Mais plus important que cela, vous devriez être en mesure de vous convaincre qu'il fait ce qu'il fait correctement . Lorsque les objets peuvent changer indépendamment dans différents «domaines» de code, il devient parfois difficile de garder une trace de ce qui est où et pourquoi (« action effrayante à distance »). C'est un concept plus difficile à illustrer, mais c'est quelque chose que l'on rencontre souvent dans des architectures plus grandes et plus complexes.

  3. Enfin, les objets mutables sont tueurs dans des situations simultanées. Chaque fois que vous accédez à un objet mutable à partir de threads séparés, vous devez gérer le verrouillage. Cela réduit le débit et rend votre code beaucoup plus difficile à maintenir. Un système suffisamment compliqué rend ce problème tellement disproportionné qu'il devient presque impossible à maintenir (même pour les experts de la concurrence).

Les objets immuables (et plus particulièrement les collections immuables) évitent tous ces problèmes. Une fois que vous aurez compris comment ils fonctionnent, votre code deviendra quelque chose de plus facile à lire, plus facile à maintenir et moins susceptible d'échouer de manière étrange et imprévisible. Les objets immuables sont encore plus faciles à tester, non seulement en raison de leur facilité de simulation, mais également des modèles de code qu'ils ont tendance à appliquer. Bref, ce sont de bonnes pratiques partout!

Cela dit, je ne suis guère un fanatique dans ce domaine. Certains problèmes ne sont tout simplement pas bien modélisés lorsque tout est immuable. Mais je pense que vous devriez essayer de pousser autant de code que possible dans cette direction, en supposant bien sûr que vous utilisez un langage qui en fait une opinion défendable (C / C ++ rend cela très difficile, tout comme Java) . Bref: les avantages dépendent un peu de votre problème, mais j'aurais tendance à préférer l'immuabilité.

Daniel Spiewak
la source
11
Excellente réponse. Une petite question cependant: le C ++ n'a-t-il pas un bon support pour l'immuabilité? La fonction const-correctness n'est-elle pas suffisante?
Dimitri C.
1
@DimitriC .: Le C ++ a en fait des fonctionnalités plus fondamentales, notamment une meilleure distinction entre les emplacements de stockage censés encapsuler l'état non partagé de ceux qui encapsulent l'identité d'objet.
supercat du
2
Dans le cas de la programmation Java Joshua Bloch apporte de bonnes explications sur ce sujet dans son célèbre livre Effective Java (Item 15)
dMathieuD
27

Objets immuables et collections immuables

L'un des points les plus fins du débat sur les objets mutables et immuables est la possibilité d'étendre le concept d'immuabilité aux collections. Un objet immuable est un objet qui représente souvent une seule structure logique de données (par exemple une chaîne immuable). Lorsque vous avez une référence à un objet immuable, le contenu de l'objet ne changera pas.

Une collection immuable est une collection qui ne change jamais.

Lorsque j'effectue une opération sur une collection mutable, je change la collection en place et toutes les entités qui ont des références à la collection verront le changement.

Lorsque j'effectue une opération sur une collection immuable, une référence est renvoyée à une nouvelle collection reflétant la modification. Toutes les entités qui ont des références à des versions précédentes de la collection ne verront pas la modification.

Les implémentations intelligentes n'ont pas nécessairement besoin de copier (cloner) la collection entière pour fournir cette immuabilité. L'exemple le plus simple est la pile implémentée sous forme de liste chaînée unique et les opérations push / pop. Vous pouvez réutiliser tous les nœuds de la collection précédente dans la nouvelle collection, en ajoutant un seul nœud pour le push et en ne clonant aucun nœud pour le pop. L'opération push_tail sur une seule liste chaînée, par contre, n'est pas si simple ou efficace.

Variables / références immuables vs mutables

Certains langages fonctionnels prennent le concept d'immuabilité aux références d'objets elles-mêmes, n'autorisant qu'une seule affectation de référence.

  • Dans Erlang, cela est vrai pour toutes les "variables". Je ne peux attribuer des objets à une référence qu'une seule fois. Si je devais opérer sur une collection, je ne pourrais pas réaffecter la nouvelle collection à l'ancienne référence (nom de variable).
  • Scala intègre également cela dans le langage avec toutes les références déclarées avec var ou val , vals n'étant qu'une affectation unique et promouvant un style fonctionnel, mais vars permettant une structure de programme plus semblable à C ou à Java.
  • La déclaration var / val est requise, alors que de nombreux langages traditionnels utilisent des modificateurs optionnels tels que final en java et const en C.

Facilité de développement vs performances

Presque toujours, la raison d'utiliser un objet immuable est de promouvoir une programmation sans effets secondaires et un raisonnement simple sur le code (en particulier dans un environnement hautement concurrent / parallèle). Vous n'avez pas à vous soucier de la modification des données sous-jacentes par une autre entité si l'objet est immuable.

Le principal inconvénient est la performance. Voici un article sur un test simple que j'ai effectué en Java comparant des objets immuables et mutables dans un problème de jouet.

Les problèmes de performances sont sans objet dans de nombreuses applications, mais pas dans toutes, c'est pourquoi de nombreux packages numériques volumineux, tels que la classe Numpy Array en Python, permettent des mises à jour sur place de grands tableaux. Cela serait important pour les domaines d'application qui utilisent de grandes opérations matricielles et vectorielles. Ces gros problèmes de parallélisme de données et de calcul intensif atteignent une grande accélération en fonctionnant sur place.

Ben Jackson
la source
12

Consultez ce billet de blog: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . Cela explique pourquoi les objets immuables sont meilleurs que mutables. En bref:

  • 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é d'identité est évité
  • ils ont toujours une atomicité d'échec
  • ils sont beaucoup plus faciles à mettre en cache
yegor256
la source
10

Les objets immuables sont un concept très puissant. Ils enlèvent une grande partie du fardeau d'essayer de garder les objets / variables cohérents pour tous les clients.

Vous pouvez les utiliser pour des objets non polymorphes de bas niveau - comme une classe CPoint - qui sont principalement utilisés avec la sémantique des valeurs.

Ou vous pouvez les utiliser pour des interfaces polymorphes de haut niveau - comme une IFonction représentant une fonction mathématique - qui est utilisée exclusivement avec la sémantique des objets.

Plus grand avantage: l'immuabilité + la sémantique des objets + les pointeurs intelligents font que la propriété de l'objet n'est pas un problème, tous les clients de l'objet ont leur propre copie privée par défaut. Implicitement, cela signifie également un comportement déterministe en présence de concurrence.

Inconvénient: lorsqu'il est utilisé avec des objets contenant beaucoup de données, la consommation de mémoire peut devenir un problème. Une solution à cela pourrait être de garder les opérations sur un objet symboliques et de faire une évaluation paresseuse. Cependant, cela peut alors conduire à des chaînes de calculs symboliques, qui peuvent avoir une influence négative sur les performances si l'interface n'est pas conçue pour accueillir des opérations symboliques. Dans ce cas, il faut absolument éviter de renvoyer d'énormes morceaux de mémoire à partir d'une méthode. En combinaison avec des opérations symboliques chaînées, cela pourrait entraîner une consommation de mémoire massive et une dégradation des performances.

Les objets immuables sont donc sans aucun doute ma principale façon de penser la conception orientée objet, mais ils ne sont pas un dogme. Ils résolvent beaucoup de problèmes pour les clients d'objets, mais en créent aussi beaucoup, surtout pour les implémenteurs.

QBziZ
la source
Je pense que j'ai mal compris la section 4 pour le plus grand avantage: l'immuabilité + la sémantique de l'objet + les pointeurs intelligents rendent la propriété de l'objet un point "discutable". C'est donc discutable? Je pense que vous utilisez "sans objet" de manière incorrecte ... Vu que la phrase suivante est que l'objet a impliqué un "comportement déterministe" de son comportement "sans objet" (discutable).
Benjamin
Vous avez raison, j'ai mal utilisé «moot». Considérez-le changé :)
QBziZ
6

Vous devez spécifier de quelle langue vous parlez. Pour les langages de bas niveau comme C ou C ++, je préfère utiliser des objets mutables pour économiser de l'espace et réduire la perte de mémoire. Dans les langages de niveau supérieur, les objets immuables facilitent le raisonnement sur le comportement du code (en particulier le code multi-thread) car il n'y a pas "d'action effrayante à distance".

John Millikin
la source
Vous suggérez que les fils sont enchevêtrés quantiquement? C'est un peu exagéré :) Les fils sont en fait, si vous y réfléchissez, assez près d'être enchevêtrés. Un changement effectué par un thread affecte l'autre. +1
ndrewxie
4

Un objet mutable est simplement un objet qui peut être modifié après sa création / instanciation, par rapport à un objet immuable qui ne peut pas être modifié (voir la page Wikipédia sur le sujet). Un exemple de cela dans un langage de programmation est les listes et les tuples Pythons. Les listes peuvent être modifiées (par exemple, de nouveaux éléments peuvent être ajoutés après leur création), contrairement aux tuples.

Je ne pense pas vraiment qu'il y ait une réponse claire quant à laquelle est la meilleure pour toutes les situations. Ils ont tous les deux leur place.

Willurd
la source
1

Si un type de classe est mutable, une variable de ce type de classe peut avoir un certain nombre de significations différentes. Par exemple, supposons qu'un objet foopossède un champ int[] arret qu'il contienne une référence à un int[3]contenant les nombres {5, 7, 9}. Même si le type de champ est connu, il peut représenter au moins quatre choses différentes:

  • Une référence potentiellement partagée, dont tous les détenteurs se soucient uniquement qu'elle encapsule les valeurs 5, 7 et 9. Si vous foosouhaitez arrencapsuler des valeurs différentes, elle doit la remplacer par un tableau différent contenant les valeurs souhaitées. Si l'on veut faire une copie de foo, on peut donner à la copie une référence arrou un nouveau tableau contenant les valeurs {1,2,3}, selon ce qui est plus pratique.

  • La seule référence, n'importe où dans l'univers, à un tableau qui encapsule les valeurs 5, 7 et 9. Ensemble de trois emplacements de stockage qui actuellement contiennent les valeurs 5, 7 et 9; s'il fooveut qu'il encapsule les valeurs 5, 8 et 9, il peut soit changer le deuxième élément de ce tableau, soit créer un nouveau tableau contenant les valeurs 5, 8 et 9 et abandonner l'ancien. Notez que si l'on veut faire une copie de foo, il faut dans la copie remplacer arrpar une référence à un nouveau tableau afin foo.arrde rester la seule référence à ce tableau n'importe où dans l'univers.

  • Une référence à un tableau qui appartient à un autre objet qui l'a exposé foopour une raison quelconque (par exemple, il veut peut-être fooy stocker des données). Dans ce scénario, arrn'encapsule pas le contenu du tableau, mais plutôt son identité . Comme le remplacement arrpar une référence à un nouveau tableau changerait totalement sa signification, une copie de foodevrait contenir une référence au même tableau.

  • Une référence à un tableau dont fooest le seul propriétaire, mais dont les références sont détenues par un autre objet pour une raison quelconque (par exemple, il veut avoir l'autre objet pour y stocker des données - le revers du cas précédent). Dans ce scénario, arrencapsule à la fois l'identité du tableau et son contenu. Le remplacement arrpar une référence à un nouveau tableau changerait totalement sa signification, mais avoir une arrréférence à un clone foo.arrviolerait l'hypothèse qui fooest le seul propriétaire. Il n'y a donc aucun moyen de copier foo.

En théorie, int[]devrait être un type simple et bien défini, mais il a quatre significations très différentes. En revanche, une référence à un objet immuable (par exemple String) n'a généralement qu'une seule signification. Une grande partie du «pouvoir» des objets immuables provient de ce fait.

supercat
la source
1

Les instances mutables sont passées par référence.

Les instances immuables sont passées par valeur.

Exemple abstrait. Supposons qu'il existe un fichier nommé txtfile sur mon disque dur. Maintenant, lorsque vous me demandez txtfile , je peux le retourner dans deux modes:

  1. Créez un raccourci vers txtfile et pas de raccourci vers vous, ou
  2. Prenez une copie pour txtfile et envoyez une copie à vous.

Dans le premier mode, le fichier txt renvoyé est un fichier modifiable, car lorsque vous effectuez des modifications dans le fichier de raccourci, vous effectuez également des modifications dans le fichier d'origine. L'avantage de ce mode est que chaque raccourci renvoyé nécessite moins de mémoire (sur la RAM ou sur le disque dur) et l'inconvénient est que tout le monde (pas seulement moi, propriétaire) a les autorisations pour modifier le contenu du fichier.

Dans le second mode, le fichier txt renvoyé est un fichier immuable, car toutes les modifications du fichier reçu ne font pas référence au fichier d'origine. L'avantage de ce mode est que seul moi (propriétaire) peut modifier le fichier d'origine et l'inconvénient est que chaque copie retournée nécessite de la mémoire (en RAM ou en disque dur).

Teodor
la source
Ce n'est pas strictement vrai. Les instances immuables peuvent en effet être passées par référence, et le sont souvent. La copie est principalement effectuée lorsque vous devez mettre à jour l'objet ou la structure de données.
Jamon Holmgren
0

Si vous renvoyez les références d'un tableau ou d'une chaîne, le monde extérieur peut modifier le contenu de cet objet, et donc le rendre comme objet mutable (modifiable).

user1923551
la source
0

Les moyens immuables ne peuvent pas être modifiés et modifiables signifie que vous pouvez changer.

Les objets sont différents des primitives en Java. Les primitives sont des types intégrés (booléen, int, etc.) et les objets (classes) sont des types créés par l'utilisateur.

Les primitives et les objets peuvent être mutables ou immuables lorsqu'ils sont définis comme des variables membres dans l'implémentation d'une classe.

Beaucoup de gens pensent que les primitives et les variables d'objet ayant un modificateur final devant elles sont immuables, cependant, ce n'est pas tout à fait vrai. Donc, final ne signifie presque pas immuable pour les variables. Voir l'exemple ici
http://www.siteconsortium.com/h/D0000F.php .

JTHouseCat
la source
0

Immutable object- est un état d'objet dont l'état ne peut pas être modifié après sa création. L'objet est immuable lorsque tous ses champs sont immuables

Fil sûr

Le principal avantage de l'objet immuable est qu'il s'agit naturellement d'un environnement concurrent. Le plus gros problème de la concurrence est de savoir shared resourcequi peut être changé n'importe quel thread. Mais si un objet est immuable, c'est read-onlyqui est une opération thread-safe. Toute modification d'un objet immuable d'origine renvoie une copie

sans effets secondaires

En tant que développeur, vous êtes tout à fait sûr que l'état de l'objet immuable ne peut pas être modifié de n'importe quel endroit (volontairement ou non)

optimisation de la compilation

Améliorer les performances

Désavantage:

La copie d'un objet est une opération plus lourde que la modification d'un objet mutable, c'est pourquoi elle a une certaine empreinte de performances

Pour créer un immutableobjet, vous devez utiliser:

  1. Niveau de langue. Chaque langue contient des outils pour vous y aider. Par exemple, Java a finalet primitives, Swift a letet struct[About] . Le langage définit un type de variable. Par exemple Java a primitiveet referencetapez, Swift a valueet referencetapez [About] . Pour les objets immuables, plus pratique est primitiveset valuetapez qui fait une copie par défaut. Quant au referencetype, il est plus difficile (car vous pouvez en changer l'état de l'objet) mais possible. Par exemple, vous pouvez utiliser un clonemodèle au niveau du développeur

  2. Niveau développeur. En tant que développeur, vous ne devez pas fournir d'interface pour changer d'état

yoAlex5
la source