J'essaie de comprendre les objets mutables et immuables. L'utilisation d'objets mutables suscite beaucoup d'attention (par exemple, le retour d'un tableau de chaînes à partir d'une méthode), mais j'ai du mal à comprendre les effets négatifs de cette situation. Quelles sont les meilleures pratiques concernant l'utilisation d'objets mutables? Devez-vous les éviter autant que possible?
Eh bien, il y a quelques aspects à cela. Numéro un, les objets modifiables sans identité de référence peuvent causer des bugs à des moments impairs. Par exemple, considérons un bean Person
avec une méthode equals
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'instance Person
est "perdue" dans la carte lorsqu'elle est utilisée comme clé car il s'agit de hashCode
et son égalité est basée sur des valeurs mutables. Ces valeurs ont changé en dehors de la carte et tout le hachage est devenu obsolète. Les théoriciens aiment bien crier sur ce point, mais dans la pratique, je ne l'ai pas trouvé trop problématique.
Un autre aspect est la "raisonnabilité" logique de votre code. C'est un terme difficile à définir, englobant tout, de la lisibilité au flux. Généralement, 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 capable de vous convaincre qu'il fait ce qu'il fait correctement. Lorsque des objets peuvent changer de manière indépendante entre différents "domaines" de code, il devient parfois difficile de garder une trace de ce qui est où et pourquoi ("action fantasmagorique à distance"). C'est un concept plus difficile à illustrer, mais c'est quelque chose qui est souvent confronté dans des architectures plus grandes et plus complexes.
Enfin, les objets mutables sont Killer dans des situations concurrentes. Chaque fois que vous accédez à un objet mutable à partir de threads distincts, vous devez gérer le verrouillage. Cela réduit le débit et rend votre code considérablement plus difficile à gérer. Un système suffisamment compliqué élimine tellement ce problème qu'il devient presque impossible à maintenir (même pour les experts en concurrence).
Les objets immuables (et plus particulièrement les collections immuables) évitent tous ces problèmes. Une fois que vous aurez compris leur fonctionnement, votre code deviendra plus facile à lire, plus facile à gérer et moins susceptible d’échouer de façon aussi imprévisible que imprévisible. Les objets immuables sont encore plus faciles à tester, non seulement en raison de leur facilité de calcul, mais également des modèles de code qu'ils ont tendance à appliquer. En bref, ce sont de bonnes pratiques tout autour!
Cela dit, je ne suis guère zélé en la matière. Certains problèmes ne sont tout simplement pas représentatifs lorsque tout est immuable. Mais je pense que vous devriez essayer de pousser autant de votre code que possible dans cette direction, en supposant bien sûr que vous utilisez un langage qui en fait un avis valable (le C/C++ le rend très difficile, tout comme Java) . En bref: les avantages dépendent un peu de votre problème, mais j'aurais tendance à préférer l'immuabilité.
L'un des arguments les plus fins du débat sur les objets mutables et immuables est la possibilité d'étendre le concept d'immutabilité aux collections. Un objet immuable est un objet qui représente souvent une structure logique unique 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 change pas.
Une collection immuable est une collection qui ne change jamais.
Lorsque j'effectue une opération sur une collection mutable, je modifie la collection en place et toutes les entités référencées dans la collection verront la modification.
Lorsque j'effectue une opération sur une collection immuable, une référence est renvoyée vers une nouvelle collection reflétant le changement. Toutes les entités faisant référence à des versions précédentes de la collection ne verront pas la modification.
Les implémentations intelligentes ne nécessitent pas nécessairement de copier (cloner) toute la collection pour assurer cette immuabilité. L'exemple le plus simple est la pile mise en œuvre sous forme de liste à lien unique et les opérations Push/pop. Vous pouvez réutiliser tous les noeuds de la collection précédente de la nouvelle collection en ajoutant un seul noeud pour le Push et en ne clonant aucun noeud pour la pop. En revanche, l’opération Push_tail sur une liste à liens simples n’est pas aussi simple ni aussi efficace.
Certains langages fonctionnels utilisent le concept d'immutabilité pour objecter les références elles-mêmes, ne permettant qu'une seule affectation de référence.
Presque toujours, la raison d'utiliser un objet immuable est de promouvoir une programmation libre d'effets secondaires et un raisonnement simple sur le code (en particulier dans un environnement hautement concurrent/parallèle). Vous n'avez pas à vous soucier des données sous-jacentes modifiées par une autre entité si l'objet est immuable.
Le principal inconvénient est la performance. Voici un article sur n test simple que j'ai fait en Java comparant certains objets immuables par rapport à objets mutables dans un problème de jouet.
Les problèmes de performances sont sans objet dans de nombreuses applications, mais pas dans la totalité. C'est pourquoi de nombreux packages numériques volumineux, tels que la classe Numpy Array en Python, autorisent les 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 grands problèmes de parallélisation des données et de calcul intensif permettent une grande accélération en opérant sur place.
Consultez cet article de blog: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . Cela explique pourquoi les objets immuables sont meilleurs que les objets mutables. En bref:
Les objets immuables sont un concept très puissant. Ils simplifient grandement la tâche pour maintenir la cohérence des objets/variables pour tous les clients.
Vous pouvez les utiliser pour des objets de bas niveau, non polymorphes - comme une classe CPoint - qui sont principalement utilisés avec la sémantique de valeur.
Vous pouvez également les utiliser pour des interfaces polymorphes de haut niveau, comme une IFunction représentant une fonction mathématique, utilisées exclusivement avec la sémantique de l'objet.
Le plus gros avantage: immuabilité + sémantique d'objet + pointeurs intelligents évite la question de la propriété d'objet. 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 à ce problème pourrait consister à garder les opérations sur un objet symbolique et à effectuer une évaluation paresseuse. Toutefois, cela peut ensuite conduire à des chaînes de calculs symboliques susceptibles d'avoir une incidence négative sur les performances si l'interface n'est pas conçue pour prendre en charge les opérations symboliques. Il faut absolument éviter dans ce cas de restituer d’énormes morceaux de mémoire 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 si immuables sont donc ma principale façon de penser au design orienté 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, en particulier pour les développeurs.
Vous devriez spécifier de quelle langue vous parlez. Pour les langages de bas niveau tels que C ou C++, je préfère utiliser des objets mutables pour économiser de l'espace et réduire les pertes de mémoire. Dans les langages de niveau supérieur, les objets immuables permettent de raisonner plus facilement sur le comportement du code (en particulier du code multithread) car il n'y a pas d '"action fantasmagorique à distance".
Un objet mutable est simplement un objet qui peut être modifié après avoir été créé/instancié, par opposition à un objet immuable qui ne peut pas être modifié (voir la page Wikipedia sur le sujet). Les listes et les nuplets Pythons en sont un exemple dans un langage de programmation. Les listes peuvent être modifiées (par exemple, de nouveaux éléments peuvent être ajoutés après sa création), contrairement aux n-uplets.
Je ne pense pas vraiment qu'il existe une réponse claire quant à savoir lequel est le meilleur pour toutes les situations. Ils ont tous les deux leur place.
Si un type de classe est modifiable, une variable de ce type de classe peut avoir différentes significations. Par exemple, supposons qu'un objet foo
ait un champ int[] arr
Et 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 d'encapsuler les valeurs 5, 7 et 9. Si foo
veut que arr
encapsule des valeurs différentes, il doit le remplacer par un autre tableau qui contient les valeurs souhaitées. Si on veut faire une copie de foo
, on peut lui donner soit une référence à arr
, soit un nouveau tableau contenant les valeurs {1,2,3}, selon ce qui convient le mieux.
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 contenant actuellement les valeurs 5, 7 et 9; si foo
souhaite encapsuler les valeurs 5, 8 et 9, il peut modifier le deuxième élément de ce tableau ou créer un nouveau tableau contenant les valeurs 5, 8 et 9 et abandonner l'ancien. Notez que si on veut faire une copie de foo
, il faut remplacer dans la copie arr
par une référence à un nouveau tableau pour que foo.arr
Reste le seul. référence à ce tableau n'importe où dans l'univers.
Une référence à un tableau appartenant à un objet other qui l'a exposé à foo
pour une raison quelconque (par exemple, il souhaite peut-être que foo
y stocke des données) . Dans ce scénario, arr
n'encapsule pas le contenu du tableau, mais plutôt identité. Parce que remplacer arr
par une référence à un nouveau tableau changerait totalement sa signification, une copie de foo
devrait contenir une référence au même tableau.
Une référence à un tableau dont foo
est l'unique propriétaire, mais auquel un autre objet détient des références pour une raison quelconque (par exemple, il souhaite que l'autre objet stocke des données à cet endroit - le verso de l'objet précédent. Cas). Dans ce scénario, arr
encapsule à la fois l'identité du tableau et son contenu. Remplacer arr
par une référence à un nouveau tableau changerait totalement sa signification, mais avoir un clone arr
faire référence à foo.arr
Violerait l'hypothèse selon laquelle foo
est l'unique 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 de Nice, 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 découle de ce fait.
Les moyens immuables ne peuvent pas être changés et mutables signifie que vous pouvez changer.
Les objets sont différents des primitives en Java. Les primitives sont des types intégrés (boolean, int, etc.) et les objets (classes) sont des types créés par l'utilisateur.
Les primitives et les objets peuvent être modifiables ou immuables lorsqu'ils sont définis en tant que 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 en face d'eux sont immuables, mais ce n'est pas tout à fait vrai. Donc, final ne signifie presque pas immuable pour les variables. Voir exemple ici
http://www.siteconsortium.com/h/D0000F.php .
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 le rendre ainsi en tant qu'objet mutable (modifiable).
Mutable les instances sont passées par référence.
immuable les instances sont passées par valeur.
Exemple abstrait. Supposons qu'il existe un fichier nommé txtfile dans mon disque dur. Maintenant, quand vous demandez à txtfile de moi, je peux le retourner en deux modes:
En premier mode, txtfile est un fichier mutable, car lorsque vous modifiez le fichier de raccourci, vous modifiez également le fichier d'origine. L'avantage de ce mode est que chaque raccourci renvoyé nécessite moins de mémoire (sur RAM ou sur disque dur)) et l'inconvénient est que tout le monde (pas seulement moi, propriétaire) est autorisé à modifier le contenu du fichier.
En deuxième mode, txtfile est un fichier immuable, car toutes les modifications apportées au 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 renvoyée nécessite de la mémoire (en RAM ou en disque dur).