Question

J'essaie de comprendre les objets mutables et immuables. L'utilisation d'objets mutables suscite de nombreuses critiques (par exemple, le renvoi d'un tableau de chaînes 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?

Était-ce utile?

La solution

Eh bien, il y a plusieurs 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 égale à basée sur une 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'il est utilisé comme clé car il s'agit de hashCode et l'é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 crier sur ce point, mais dans la pratique, cela ne me posait pas trop de problème.

Un autre aspect est la logique "raisonnabilité". 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 encore, vous devriez être en mesure 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 que l’on rencontre souvent dans des architectures plus vastes et plus complexes.

Enfin, les objets mutables sont killer dans des situations simultanées. 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, c’est une bonne pratique 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 dans cette direction que possible, 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’immutabilité.

Autres conseils

Objets immuables ou 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’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 cet 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 modifie la collection en place et toutes les entités faisant référence à 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) l’ensemble de la collection afin de fournir cette immuabilité. L'exemple le plus simple est la pile implémentée 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 transfert et en ne clonant aucun noeud pour la pop. L’opération push_tail sur une liste à liens simples, en revanche, n’est pas si simple ni aussi efficace.

Variables / références immuables vs mutables

Certains langages fonctionnels utilisent le concept d’immuabilité pour objecter des références elles-mêmes, ne permettant qu’une seule affectation de référence.

  • À Erlang, cela est vrai pour toutes les "variables". Je ne peux affecter 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 l’intègre également dans le langage, toutes les références étant déclarées avec var ou val , vals ne représentant qu’une seule affectation et promouvant un style fonctionnel, mais permettant une structure de programme de type c ou java.
  • La déclaration var / val est obligatoire, alors que de nombreuses langues traditionnelles utilisent des modificateurs facultatifs tels que final en java et const en c.

Facilité de développement et performances

La raison pour utiliser un objet immuable est presque toujours 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 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 un test simple que j'ai fait en Java et qui comparait certains objets immuables ou 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 d’atteindre une grande rapidité en opérant sur place.

Consultez ce billet de blog: http://www.yegor256.com/ 2014/06/09 / objets-devrait-être-immuable.html . Cela explique pourquoi les objets immuables sont meilleurs que les objets 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 l'atomicité d'échec
  • ils sont beaucoup plus faciles à mettre en cache

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 des valeurs.

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 d'un objet.

Plus grand avantage: immuabilité + sémantique d’objet + pointeurs intelligents: la propriété de l’objet ne pose aucun problème, tous les clients de l’objet ont leur propre copie privée par défaut. Cela implique implicitement un comportement déterministe en présence de simultanéité.

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 alors conduire à des chaînes de calculs symboliques, susceptibles d'influer négativement sur les performances, si l'interface n'est pas conçue pour prendre en charge des opérations symboliques. Dans ce cas, il faut absolument éviter de renvoyer 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 immuables sont donc certainement 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 devez spécifier la langue dont 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) alors que les n-uplets ne le peuvent pas.

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

Si un type de classe est modifiable, une variable de ce type de classe peut avoir plusieurs significations. Par exemple, supposons qu'un objet toto ait un champ int [] arr et qu'il contienne une référence à un int [3] contenant les numéros {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 soit encapsulé valeurs différentes, il doit le remplacer par un tableau différent contenant 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 le cas 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 contenant actuellement les valeurs 5, 7 et 9; si toto veut qu'il encapsule les valeurs 5, 8 et 9, il peut changer 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 un. 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 afin de foo.arr pour rester la seule référence à ce tableau, où que ce soit dans l'univers.

  • Référence à un tableau appartenant à un autre objet qui l'a exposé à foo pour une raison quelconque (par exemple, il souhaite peut-être foo pour y stocker des données). Dans ce scénario, arr n'encapsule pas le contenu du tableau, mais plutôt son identité . Étant donné que le remplacement de arr par une référence à un nouveau tableau changerait totalement sa signification, une copie de toto devrait contenir une référence au même tableau.

  • 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 là - le revers de l'affaire précédente). 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 le fait de faire référence à un arr d'un clone avec foo.arr violerait l'hypothèse que toto en est l'unique propriétaire. Il n'y a donc aucun moyen de copier foo .

En théorie, int [] devrait être un type simple, 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 " puissance " des objets immuables découle de ce fait.

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 comme objet modable (modifiable).

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 la mise en oeuvre 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 .

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, quand vous me demandez txtfile , je peux le retourner en deux modes:

  1. Créez un raccourci vers fichier txt et ne le faites pas, ou
  2. Prenez une copie pour fichier txt et ne vous en copie pas.

Dans le premier mode, le fichier txt renvoyé 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, fichier txt renvoyé 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 que chaque copie renvoyée nécessite de la mémoire (en RAM ou sur un disque dur).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top