59 votes

Qu'est-ce que l'immutabilité et pourquoi devrais-je m'en soucier ?

J'ai lu quelques articles sur l'immutabilité mais je ne comprends toujours pas très bien le concept.

J'ai récemment créé un fil de discussion ici qui mentionnait l'immutabilité, mais comme c'est un sujet en soi, je crée maintenant un fil dédié.

J'ai mentionné dans le fil précédent que je pensais que l'immutabilité consiste à rendre un objet en lecture seule et à lui donner une faible visibilité. Un autre membre a dit que cela n'avait pas vraiment de rapport avec ça. Cette page (partie d'une série) utilise un exemple de classe/struct immuable et elle utilise readonly et d'autres concepts pour le verrouiller.

Quelle est exactement la définition de l'état dans le cas de cet exemple? L'état est un concept que je n'ai pas vraiment saisi.

D'un point de vue des lignes directrices de conception, une classe immuable doit être une classe qui n'accepte pas de saisie de l'utilisateur et qui se contente de renvoyer des valeurs, n'est-ce pas?

Je comprends que tout objet qui ne fait que renvoyer des informations devrait être immuable et "verrouillé", n'est-ce pas? Donc si je veux renvoyer l'heure actuelle dans une classe dédiée avec cette seule méthode, je devrais utiliser un type de référence car cela fonctionnera avec une référence du type et ainsi je bénéficierai de l'immutabilité.

58voto

PCheese Points 2777

Qu'est-ce que l'Immutabilité?

  • L'immutabilité s'applique principalement aux objets (chaînes de caractères, tableaux, une classe Animal personnalisée)
  • Typiquement, s'il existe une version immuable d'une classe, une version mutable est également disponible. Par exemple, Objective-C et Cocoa définissent à la fois une classe NSString (immuable) et une classe NSMutableString.
  • Si un objet est immuable, il ne peut pas être modifié après sa création (essentiellement en lecture seule). Vous pourriez considérer cela comme "seul le constructeur peut changer l'objet".

Cela n'a pas directement de rapport avec une entrée utilisateur; même votre code ne peut pas changer la valeur d'un objet immuable. Cependant, vous pouvez toujours créer un nouvel objet immuable pour le remplacer. Voici un exemple en pseudocode; notez que dans de nombreux langages, vous pouvez simplement faire myString = "hello"; au lieu d'utiliser un constructeur comme je l'ai fait ci-dessous, mais je l'ai inclus pour plus de clarté:

String myString = new ImmutableString("hello");
myString.appendString(" world"); // Impossible
myString.setValue("hello world"); // Impossible
myString = new ImmutableString("hello world"); // OK

Vous mentionnez "un objet qui renvoie simplement des informations"; cela ne fait pas automatiquement de lui un bon candidat pour l'immutabilité. Les objets immuables ont tendance à toujours renvoyer la même valeur avec laquelle ils ont été construits, donc je suis porté à dire que l'heure actuelle ne serait pas idéale puisqu'elle change souvent. Cependant, vous pourriez avoir une classe MomentDeTemps qui est créée avec un horodatage spécifique et renvoie toujours cet horodatage dans le futur.

Avantages de l'Immutabilité

  • Si vous passez un objet à une autre fonction/méthode, vous ne devriez pas vous soucier de savoir si cet objet aura la même valeur après que la fonction se termine. Par exemple:

    String myString = "HeLLo WoRLd";
    String lowercasedString = lowercase(myString);
    print myString + " a été converti en " + lowercasedString;

    Que se passerait-il si l'implémentation de lowercase() changeait myString en créant une version en minuscules? La troisième ligne ne vous donnerait pas le résultat souhaité. Bien sûr, une bonne fonction lowercase() ne ferait pas cela, mais vous êtes assuré de ce fait si myString est immuable. Ainsi, les objets immuables peuvent aider à renforcer de bonnes pratiques de programmation orientée objet.

  • C'est plus facile de rendre un objet immuable résistant aux threads

  • Il simplifie potentiellement l'implémentation de la classe (ce qui est pratique si vous êtes celui qui écrit la classe)

État

Si vous deviez prendre toutes les variables d'instance d'un objet et écrire leurs valeurs sur papier, ce serait l'état de cet objet à ce moment donné. L'état du programme est l'état de tous ses objets à un moment donné. L'état change rapidement au fil du temps; un programme doit changer d'état pour continuer à s'exécuter.

Cependant, les objets immuables ont un état fixe au fil du temps. Une fois créé, l'état d'un objet immuable ne change pas bien que l'état du programme dans son ensemble puisse. Cela facilite le suivi de ce qui se passe (et permet de voir les autres avantages mentionnés ci-dessus).

0 votes

Je voterais pour cela si tu n'agissais pas comme si cela ne s'appliquait qu'à la POO.

0 votes

@Rayne - Pourriez-vous donner un exemple d'immutabilité dans quelque chose d'autre que la POO? Le mot-clé const peut fonctionner de manière similaire; cependant, je considère const comme étant appliqué par le compilateur et l'immutabilité comme étant appliquée par le contrat (c'est-à-dire, c'est au développeur de s'assurer qu'il est mis en œuvre correctement).

0 votes

@PCheese Voir mon post ci-dessous, j'ai fait un exemple dans un langage de programmation fonctionnelle appelé Clojure.

22voto

Drew Noakes Points 69288

Immuable

En termes simples, la mémoire est immuable lorsqu'elle n'est pas modifiée après son initialisation.

Les programmes écrits dans des langages impératifs tels que C, Java et C# peuvent manipuler les données en mémoire à leur guise. Une zone de mémoire physique, une fois réservée, peut être modifiée en tout ou en partie par un thread d'exécution à tout moment pendant l'exécution du programme. En fait, les langages impératifs encouragent cette façon de programmer.

Écrire des programmes de cette manière a été incroyablement réussi pour les applications à thread unique. Cependant, à mesure que le développement d'applications modernes se dirige vers plusieurs threads d'opération concurrents au sein d'un même processus, un monde de problèmes potentiels et de complexité est introduit.

Lorsqu'il n'y a qu'un seul thread d'exécution, vous pouvez imaginer que ce thread unique "possède" toutes les données en mémoire et peut donc les manipuler à sa guise. Cependant, il n'y a pas de concept implicite de propriété lorsque plusieurs threads d'exécution sont impliqués.

À la place, ce fardeau incombe au programmeur qui doit faire de grands efforts pour garantir que les structures en mémoire sont dans un état cohérent pour tous les lecteurs. Les structures de verrouillage doivent être utilisées avec précaution pour empêcher qu'un thread ne voie des données pendant qu'elles sont mises à jour par un autre thread. Sans cette coordination, un thread consommerait inévitablement des données qui étaient seulement à moitié en train d'être mises à jour. Le résultat d'une telle situation est imprévisible et souvent catastrophique. De plus, faire fonctionner correctement les verrous dans le code est notoirement difficile et, quand c'est mal fait, peut paralyser les performances ou, dans le pire des cas, entraîner des impasses qui interrompent irrémédiablement l'exécution.

L'utilisation de structures de données immuables allège le besoin d'introduire des verrouillages complexes dans le code. Lorsqu'une section de la mémoire est garantie de ne pas changer pendant la durée de vie d'un programme, plusieurs lecteurs peuvent accéder simultanément à la mémoire. Il leur est impossible d'observer ces données particulières dans un état incohérent.

De nombreux langages de programmation fonctionnelle, tels que Lisp, Haskell, Erlang, F# et Clojure, encouragent les structures de données immuables par leur nature même. C'est pour cette raison qu'ils suscitent un regain d'intérêt alors que nous nous dirigeons vers un développement d'applications de plus en plus complexes à plusieurs threads et des architectures informatiques de plus en plus nombreuses.

État

L'état d'une application peut simplement être considéré comme le contenu de toute la mémoire et des registres de CPU à un moment donné.

Logiquement, l'état d'un programme peut être divisé en deux :

  1. L'état du tas
  2. L'état de la pile de chaque thread en cours d'exécution

Dans les environnements gérés tels que C# et Java, un thread ne peut pas accéder à la mémoire d'un autre. Par conséquent, chaque thread "possède" l'état de sa pile. La pile peut être considérée comme contenant des variables locales et des paramètres de type valeur (struct), et les références aux objets. Ces valeurs sont isolées des threads externes.

Cependant, les données sur le tas peuvent être partagées entre tous les threads, donc il faut faire attention pour contrôler l'accès concurrent. Toutes les instances d'objets de type référence (class) sont stockées sur le tas.

En POO, l'état d'une instance d'une classe est déterminé par ses champs. Ces champs sont stockés sur le tas et sont donc accessibles par tous les threads. Si une classe définit des méthodes permettant de modifier des champs après l'achèvement du constructeur, alors la classe est mutable (non immuable). Si les champs ne peuvent pas être modifiés de quelque manière que ce soit, alors le type est immuable. Il est important de noter qu'une classe avec tous les champs C# readonly/Java final n'est pas nécessairement immuable. Ces constructions garantissent que la référence ne peut pas changer, mais pas l'objet référencé. Par exemple, un champ peut avoir une référence immuable à une liste d'objets, mais le contenu réel de la liste peut être modifié à tout moment.

En définissant un type comme étant vraiment immuable, son état peut être considéré comme figé et donc le type est sûr d'être accédé par plusieurs threads.

En pratique, il peut être inconfortable de définir tous vos types comme immuables. Modifier une valeur sur un type immuable peut impliquer une bonne quantité de copie mémoire. Certains langages rendent ce processus plus facile que d'autres, mais de toute façon, le CPU finira par faire un travail supplémentaire. De nombreux facteurs contribuent à déterminer si le temps passé à copier la mémoire l'emporte sur l'impact des contensions de verrouillage.

Beaucoup de recherches ont été menées sur le développement de structures de données immuables telles que des listes et des arbres. Lors de l'utilisation de telles structures, par exemple une liste, l'opération 'ajout' renverra une référence à une nouvelle liste avec l'élément ajouté. Les références à la liste précédente ne voient aucun changement et ont toujours une vue cohérente des données.

9voto

Jeroen Landheer Points 3346

Pour le dire simplement: Une fois que vous créez un objet immuable, il n'y a aucun moyen de changer le contenu de cet objet. Des exemples d'objets immuables .Net sont String et Uri.

Lorsque vous modifiez une chaîne de caractères, vous obtenez simplement une nouvelle chaîne de caractères. La chaîne d'origine ne changera pas. Un Uri n'a que des propriétés en lecture seule et aucune méthode disponible pour changer le contenu de l'Uri.

Les cas où les objets immuables sont importants sont variés et dans la plupart des cas ont à voir avec la sécurité. L'Uri est un bon exemple ici. (par exemple, Vous ne voulez pas qu'un Uri soit modifié par un code non digne de confiance.) Cela signifie que vous pouvez transmettre une référence à un objet immuable sans vous soucier que le contenu ne changera jamais.

J'espère que cela vous aide.

7voto

Rayne Points 14518

Les choses qui sont immuables ne changent jamais. Les choses mutables peuvent changer. Les choses mutables mutent. Les choses immuables semblent changer mais créent en réalité une nouvelle chose mutable.

Par exemple, voici une carte en Clojure

(def imap {1 "1" 2 "2"})
(conj imap [3 "3"])
(println imap)

La première ligne crée une nouvelle carte Clojure immuable. La deuxième ligne ajoute 3 et "3" à la carte. Cela peut sembler modifier l'ancienne carte mais en réalité elle retourne une nouvelle carte avec 3 "3" ajouté. C'est un excellent exemple d'immuabilité. Si cela avait été une carte mutable, il aurait simplement ajouté 3 "3" directement à la même ancienne carte. La troisième ligne affiche la carte

{3 "3", 1 "1", 2 "2"}

L'immuabilité aide à maintenir un code propre et sécurisé. C'est entre autres pour cela que les langages de programmation fonctionnelle tendent à favoriser l'immuabilité et à réduire l'état.

0 votes

Si conj crée une nouvelle carte, pourquoi cette carte n'a-t-elle pas un nouveau nom? Autrement dit : comment accéder à l'ancienne valeur de imap? Si je ne peux pas, alors il est clair que conj modifie la carte

0 votes

Si vous n'utilisez pas l'ancienne valeur imaps en Clojure, elle sera bien sûr collectée par le garbage collecteur... Vous pouvez conserver l'ancienne valeur imaps en simplement en l'utilisant à nouveau, par exemple en l'assignant à une variable.

0 votes

Je ne comprends pas vraiment ça mais bon, je ne connais pas Clojure donc.. :)

2voto

MandyK Points 971

Un objet immuable est quelque chose que vous pouvez sûrement supposer ne va pas changer ; il a la propriété importante que tout le monde l'utilisant peut supposer qu'ils voient la même valeur.

L'immuabilité signifie généralement aussi que vous pouvez considérer l'objet comme étant une "valeur", et qu'il n'y a pas de différence effective entre des copies identiques de l'objet et l'objet lui-même.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X