web-dev-qa-db-fra.com

Pourquoi avons-nous besoin d'une classe immuable?

Je suis incapable d’obtenir quels sont les scénarios où nous avons besoin d’une classe immuable.
Avez-vous déjà été confronté à une telle exigence? ou pouvez-vous s'il vous plaît nous donner un exemple réel d'utilisation de ce modèle.

66
Rakesh Juyal

Les autres réponses semblent vouloir expliquer pourquoi l’immutabilité est bonne. C'est très bon et je l'utilise chaque fois que possible. Cependant, ce n'est pas votre question . Je vais répondre à votre question point par point pour essayer de vous donner les réponses et les exemples dont vous avez besoin.

Je suis incapable d’obtenir quels sont les scénarios où nous avons besoin d’une classe immuable.

"Besoin" est un terme relatif ici. Les classes immuables sont un modèle de conception qui, comme tout paradigme/modèle/outil, est là pour faciliter la construction de logiciels. De même, beaucoup de code a été écrit avant le OO paradigme, mais comptez moi parmi les programmeurs qui "ont besoin" OO. Les classes immuables, comme OO, ne sont pas strictement nécessaires , mais je vais agir comme j'en ai besoin.

Avez-vous déjà été confronté à une telle exigence?

Si vous ne regardez pas les objets du domaine problématique avec la bonne perspective, il se peut que vous ne voyiez pas une exigence pour un objet immuable. Il peut être facile de penser qu'un domaine problématique ne nécessite pas de classes immuables si vous ne savez pas quand les utiliser de manière avantageuse.

J'utilise souvent des classes immuables où je pense à un objet donné dans mon domaine problématique comme une valeur ou une instance fixe . Cette notion dépend parfois de la perspective ou du point de vue, mais idéalement, il sera facile de basculer dans la bonne perspective pour identifier les bons objets candidats.

Vous pouvez avoir une meilleure idée de l'endroit où les objets immuables sont vraiment utiles (si ce n'est pas strictement nécessaire) en vous assurant de lire divers ouvrages/articles en ligne pour développer un bon sens de la manière dont penser à des classes immuables. Un bon article pour vous aider à démarrer est Théorie et pratique de Java: Muter ou ne pas muter?

Je vais essayer de donner ci-dessous quelques exemples de la façon dont on peut voir des objets sous différentes perspectives (modifiable ou immuable) pour clarifier ce que je veux dire par perspective.

... pouvez-vous s'il vous plaît nous donner un exemple réel d'utilisation de ce modèle.

Puisque vous avez demandé de vrais exemples, je vais vous en donner quelques uns, mais commençons par quelques exemples classiques.

Objets valeur classiques

Les chaînes et les entiers sont souvent considérés comme des valeurs. Il n’est donc pas surprenant de constater que la classe String et la classe wrapper Integer (ainsi que les autres classes wrapper) sont immuables en Java. Une couleur est généralement considérée comme une valeur, donc la classe de couleurs immuable.

Contre-exemple

En revanche, une voiture n'est généralement pas considérée comme un objet de valeur. Modéliser une voiture signifie généralement créer une classe dont l'état varie (odomètre, vitesse, niveau de carburant, etc.). Cependant, dans certains domaines, il peut s'agir d'un objet de valeur. Par exemple, une voiture (ou plus précisément un modèle de voiture) peut être considérée comme un objet de valeur dans une application permettant de rechercher l'huile moteur appropriée pour un véhicule donné.

Cartes à jouer

Avez-vous déjà écrit un programme de cartes à jouer? J'ai fait. J'aurais pu représenter une carte à jouer comme un objet modifiable avec un costume et un rang mutables. Une main de tirage au poker pourrait être 5 instances fixes où le remplacement de la 5ème carte dans ma main signifierait la mutation de la 5ème instance de carte à jouer en une nouvelle carte en changeant sa couleur et sa couleur.

Cependant, j’ai tendance à penser à une carte à jouer comme à un objet immuable ayant une couleur et une couleur fixes immuables une fois créées. Ma main de poker au tirage serait de 5 instances et le remplacement d'une carte dans ma main impliquerait de jeter l'une de ces instances et d'ajouter une nouvelle instance aléatoire à ma main.

Projection cartographique

Un dernier exemple est quand j'ai travaillé sur un code de carte où la carte pourrait s'afficher elle-même dans divers projections . Le code original faisait en sorte que la carte utilise une instance de projection fixe, mais modifiable (comme la carte à jouer modifiable ci-dessus). Changer la projection cartographique impliquait la mutation des ivars de l'instance de la carte (type de projection, point central, zoom, etc.).

Cependant, j’ai senti que la conception était plus simple si j’imaginais une projection comme une valeur immuable ou une instance fixe. Changer la projection cartographique voulait dire que la carte référencait une instance de projection différente plutôt que de muter l'instance de projection fixe de la carte. Cela simplifiait également la capture de projections nommées telles que MERCATOR_WORLD_VIEW.

71
Bert F

Les classes immuables sont en général beaucoup plus simples à concevoir, à mettre en œuvre et à utiliser correctement . Un exemple est String: l'implémentation de Java.lang.String est nettement plus simple que celle de std::string en C++, principalement en raison de son immuabilité.

La concurrence est un domaine particulier dans lequel l'immutabilité fait une différence particulièrement importante: les objets immuables peuvent être partagés en toute sécurité entre plusieurs threads , alors que les objets mutables doivent être rendus thread-safe. via une conception et une mise en œuvre soigneuses - ceci est généralement loin d’être une tâche triviale.

Mise à jour: Effective Java 2nd Edition traite ce problème en détail - voir Point 15: Réduire au minimum la mutabilité .

Voir aussi ces articles liés:

42
Péter Török

Effective Java de Joshua Bloch expose plusieurs raisons pour écrire des classes immuables:

  • Simplicité - chaque classe est dans un seul état
  • Thread Safe - étant donné que l'état ne peut pas être modifié, aucune synchronisation n'est requise
  • Écrire dans un style immuable peut conduire à un code plus robuste. Imaginez si Strings n'était pas immuable; Toute méthode getter renvoyant une chaîne nécessiterait que l'implémentation crée une copie défensive avant que la chaîne ne soit renvoyée. Dans le cas contraire, un client risque de rompre accidentellement ou de manière malveillante cet état de l'objet.

En règle générale, il est de bonne pratique de rendre un objet immuable sauf en cas de graves problèmes de performances. Dans de telles circonstances, des objets de générateur modifiables peuvent être utilisés pour créer des objets immuables, par ex. StringBuilder

37
Tarski

Les hashmaps sont un exemple classique. Il est impératif que la clé d’une carte soit immuable. Si la clé n'est pas immuable et que vous modifiez une valeur de telle sorte que hashCode () génère une nouvelle valeur, la carte est maintenant rompue (une clé se trouve maintenant au mauvais emplacement dans la table de hachage.).

12
Kirk Woll

Java est pratiquement une et toutes les références. Parfois, une instance est référencée plusieurs fois. Si vous modifiez une telle instance, cela sera reflété dans toutes ses références. Parfois, vous ne voulez tout simplement pas avoir ceci pour améliorer la robustesse et la sécurité des threads. Alors une classe immuable est utile pour obliger à créer une instance new et à la réaffecter à la référence actuelle De cette façon, l'instance d'origine des autres références reste intacte.

Imaginez à quoi ressemblerait Java si String était mutable.

7
BalusC

Nous n'avons pas besoin classes immuables, mais elles peuvent certainement faciliter certaines tâches de programmation, en particulier lorsque plusieurs threads sont impliqués. Vous n'avez pas besoin d'effectuer de verrouillage pour accéder à un objet immuable, et les faits que vous avez déjà établis sur un tel objet continueront à être vrais à l'avenir.

6

Je vais attaquer cela d'une perspective différente. Je trouve que les objets immuables me facilitent la vie lorsque je lis du code.

Si j'ai un objet mutable, je ne suis jamais sûr de sa valeur s'il est utilisé en dehors de ma portée immédiate. Supposons que je crée MyMutableObject dans les variables locales d'une méthode, que je la remplisse de valeurs, puis que je la transmette à cinq autres méthodes. N'IMPORTE QUELLE de ces méthodes peut changer l'état de mon objet, il faut donc que l'une des deux choses se produise

  1. Je dois suivre le corps de cinq méthodes supplémentaires tout en réfléchissant à la logique de mon code.
  2. Je dois faire cinq copies défensives inutiles de mon objet pour que les bonnes valeurs soient transmises à chaque méthode.

Le premier rend difficile le raisonnement sur mon code. La seconde rend mon code insuffisant en performances - je reproduis fondamentalement un objet immuable avec une sémantique de copie sur écriture, mais je le fais tout le temps, que les méthodes appelées modifient ou non réellement l'état de mon objet.

Si j'utilise plutôt MyImmutableObject, je peux être assuré que ce que je fixe est ce que seront les valeurs pour la vie de ma méthode. Il n'y a pas d '"action fantasmagorique à distance" qui puisse me changer les idées et il n'est pas nécessaire que je fasse des copies défensives de mon objet avant d'invoquer les cinq autres méthodes. Si les autres méthodes veulent changer les choses pour leurs buts elles doivent faire la copie - mais elles ne le font que si elles doivent vraiment en faire une copie (par opposition à ce que je le fasse avant chaque appel de méthode externe) . Je m'épargne les ressources mentales pour garder en mémoire des méthodes qui ne figurent peut-être même pas dans mon fichier source actuel, et au système, je ne épargne pas les frais généraux liés à la réalisation sans fin de copies défensives inutiles au cas où.

(Si je sors du monde Java et, disons, du monde C++, entre autres, je deviens encore plus délicat. Je peux faire en sorte que les objets apparaissent comme s'ils étaient mutables, mais dans la coulisse, faites-les cloner de manière transparente type de changement d’état - c’est la copie sur écriture - personne n’est le plus sage.)

5

Prenons un cas extrême: les constantes entières. Si j'écris une déclaration du type "x = x + 1", je veux être sûr à 100% que le nombre "1" ne deviendra en quelque sorte pas 2, peu importe ce qui se passe ailleurs dans le programme.

Bon, d'accord, les constantes entières ne sont pas une classe, mais le concept est le même. Supposons que j'écris:

String customerId=getCustomerId();
String customerName=getCustomerName(customerId);
String customerBalance=getCustomerBalance(customerid);

Cela semble assez simple. Mais si Strings n'était pas immuable, je devrais alors envisager la possibilité que getCustomerName puisse changer customerId. Ainsi, lorsque j'appellerai getCustomerBalance, j'obtiendrai le solde d'un client différent. Maintenant, vous pourriez dire: "Pourquoi quelqu'un écrivant une fonction getCustomerName lui demanderait-il de changer l'identifiant? Cela n'aurait aucun sens." Mais c'est exactement là où vous pourriez avoir des problèmes. La personne qui écrit le code ci-dessus peut comprendre qu'il est évident que les fonctions ne changeront pas le paramètre. Puis vient quelqu'un qui doit modifier une autre utilisation de cette fonction pour gérer le cas où un client a plusieurs comptes sous le même nom. Et il dit: "Oh, voici cette fonction de nom getCustomer qui recherche déjà le nom. Je vais simplement faire en sorte que l'identifiant soit automatiquement changé pour le prochain compte du même nom, et le mettre en boucle ..." alors votre programme commence mystérieusement ne fonctionne pas. Serait-ce un mauvais style de codage? Probablement. Mais c'est précisément un problème dans les cas où l'effet secondaire n'est pas évident.

L’immuabilité signifie simplement qu’une certaine classe d’objets sont des constantes et que nous pouvons les traiter comme des constantes.

(Bien entendu, l'utilisateur peut affecter un "objet constant" différent à une variable. Quelqu'un peut écrire String s = "hello"; Et écrire plus tard S = "au revoir"; À moins que Je fais la variable finale, je ne peux pas être sûr qu'elle ne soit pas modifiée dans mon propre bloc de code, tout comme les constantes entières m'assurent que "1" est toujours le même nombre, mais que "x = 1" ne sera jamais être changé en écrivant "x = 2". Mais je peux être assuré que si j'ai un handle sur un objet immuable, qu'aucune fonction à laquelle je le transmets ne peut le changer sur moi, ou que si j'en fais deux copies, une modification de la variable contenant une copie ne changera pas l'autre.

5
Jay

Il y a plusieurs raisons à l'immutabilité:

  • Sécurité des threads: les objets immuables ne peuvent pas être changés, pas plus que son état interne. Il n'est donc pas nécessaire de le synchroniser.
  • Cela garantit également que tout ce que j'envoie (via un réseau) doit être dans le même état que celui envoyé précédemment. Cela signifie que personne (espion) ne peut venir ajouter des données aléatoires dans mon ensemble immuable.
  • C'est aussi plus simple à développer. Vous garantissez qu’aucune sous-classe n’existera si un objet est immuable. Par exemple. une classe String.

Donc, si vous voulez envoyer des données via un service réseau et que vous voulez vous sentir garanti que vous obtiendrez un résultat identique à celui que vous avez envoyé, définissez-le comme immuable.

5
Buhake Sindi

Les structures de données immuables peuvent également aider lors du codage d'algorithmes récursifs. Par exemple, disons que vous essayez de résoudre un problème 3SAT . Une façon est de faire ce qui suit:

  • Choisissez une variable non affectée.
  • Donnez-lui la valeur de TRUE. Simplifiez l'instance en supprimant les clauses qui sont maintenant satisfaites et répétez pour résoudre l'instance plus simple.
  • Si la récursion sur le cas TRUE a échoué, affectez plutôt cette variable à FALSE. Simplifiez cette nouvelle instance et recommencez pour la résoudre.

Si vous avez une structure modifiable pour représenter le problème, alors lorsque vous simplifiez l'instance dans la branche TRUE, vous devez soit:

  • Gardez une trace de tous les changements que vous apportez et annulez-les une fois que vous réalisez que le problème ne peut pas être résolu. Cela entraîne des coûts importants car votre récursion peut aller assez loin et il est difficile de coder.
  • Faites une copie de l'instance, puis modifiez la copie. Cela sera lent, car si votre récursion a une profondeur de quelques dizaines de niveaux, vous devrez faire beaucoup de copies de l'instance.

Cependant, si vous le codez de manière intelligente, vous pouvez avoir une structure immuable, dans laquelle toute opération renvoie une version mise à jour (mais toujours immuable) du problème (similaire à String.replace - elle ne remplace pas la chaîne, elle vous donne simplement une nouvelle un). La façon naïve de mettre cela en œuvre est de faire en sorte que la structure "immuable" copie et en crée une nouvelle lors de toute modification, la réduisant à la 2ème solution lorsque celle-ci est mutable, avec tout ce surdébit, mais vous pouvez manière efficace.

1
Claudiu

Une des raisons du "besoin" pour les classes immuables est la combinaison de tout passer par référence et de ne pas prendre en charge les vues en lecture seule d'un objet (c'est-à-dire la variable const de C++).

Prenons le cas simple d’une classe prenant en charge le motif observateur:

class Person {
    public string getName() { ... }
    public void registerForNameChange(NameChangedObserver o) { ... }
}

Si string n'était pas immuable, il serait impossible pour la classe Person d'implémenter registerForNameChange() correctement, car quelqu'un pourrait écrire ce qui suit, en modifiant effectivement le nom de la personne sans déclencher de notification.

void foo(Person p) {
    p.getName().prepend("Mr. ");
}

En C++, getName() renvoyant un const std::string& a pour effet de renvoyer par référence et d'empêcher l'accès aux mutateurs, ce qui signifie que les classes immuables ne sont pas nécessaires dans ce contexte. 

1
André Caron

L'utilisation du mot-clé final ne rend pas nécessairement quelque chose d'immuable:

public class Scratchpad {
    public static void main(String[] args) throws Exception {
        SomeData sd = new SomeData("foo");
        System.out.println(sd.data); //prints "foo"
        voodoo(sd, "data", "bar");
        System.out.println(sd.data); //prints "bar"
    }

    private static void voodoo(Object obj, String fieldName, Object value) throws Exception {
        Field f = SomeData.class.getDeclaredField("data");
        f.setAccessible(true);
        Field modifiers = Field.class.getDeclaredField("modifiers");
        modifiers.setAccessible(true);
        modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL);
        f.set(obj, "bar");
    }
}

class SomeData {
    final String data;
    SomeData(String data) {
        this.data = data;
    }
}

Juste un exemple pour démontrer que le mot clé "final" est là pour éviter les erreurs de programmation, et pas beaucoup plus. Alors que la réaffectation d'une valeur sans mot-clé final peut facilement se produire par accident, aller à cette longueur pour modifier une valeur doit être fait intentionnellement. Il est là pour la documentation et pour éviter les erreurs de programmation.

1
Mark Peters

Ils nous donnent aussi une garantie. La garantie d’immuabilité signifie que nous pouvons les développer et créer de nouveaux modèles d’efficacité qui seraient autrement impossibles.

http://en.wikipedia.org/wiki/Singleton_pattern

1
Ryan Hayes

Les objets immuables sont des instances dont les états ne changent pas une fois lancés. L'utilisation de tels objets est spécifique à l'exigence. 

La classe immuable est bonne pour la mise en cache et est thread-safe.

1
Shreya Agarwal

Une caractéristique des classes immuables qui n'a pas encore été appelée: stocker une référence à un objet de classe profondément immuable est un moyen efficace de stocker tout l'état qui y est contenu. Supposons que j’ai un objet mutable qui utilise un objet profondément immuable pour contenir 50 000 informations d’état. Supposons en outre que je souhaite à 25 reprises faire une "copie" de mon objet original (modifiable) (par exemple, pour un tampon "d'annulation"); l'état peut changer entre les opérations de copie, mais généralement pas. Faire une "copie" de l'objet mutable nécessiterait simplement de copier une référence à son état immuable, donc 20 copies équivaudraient simplement à 20 références. En revanche, si l’État disposait d’objets mutables d’une valeur de 50 000 $, chacune des 25 opérations de copie devrait produire sa propre copie de données d’une valeur de 50 000 $; conserver les 25 copies nécessiterait de conserver plus de 12 millions de données dupliquées. Bien que la première opération de copie produise une copie des données qui ne changera jamais, et que les 24 autres opérations pourraient théoriquement simplement y renvoyer, dans la plupart des implémentations, il n’y aurait aucun moyen pour le second objet de demander une copie de la information pour savoir qu'une copie immuable existe déjà (*).

(*) Un modèle qui peut parfois être utile est que les objets mutables aient deux champs pour conserver leur état: un sous forme mutable et un sous forme immuable. Les objets peuvent être copiés de manière mutable ou immuable et commenceraient leur vie avec l’un ou l’autre ensemble de référence. Dès que l'objet veut changer d'état, il copie la référence immuable à l'objet mutable (si cela n'a pas déjà été fait) et invalide celui qui est immuable. Lorsque l'objet est copié comme immuable, si sa référence immuable n'est pas définie, une copie immuable est créée et la référence immuable pointe vers celle-ci. Cette approche nécessitera quelques opérations de copie supplémentaires par rapport à une "copie à part entière en écriture" (par exemple, demander à copier un objet muté depuis la dernière copie nécessiterait une opération de copie, même si l'objet original n'est plus jamais muté. ) mais évite les complexités de threading que FFCOW impliquerait.

1
supercat

Mes 2 centimes pour les futurs visiteurs:


2 scénarios où les objets immuables sont de bons choix sont:

En multi-threading

Les problèmes de simultanéité dans un environnement multithread peuvent très bien être résolus par la synchronisation, mais la synchronisation est une affaire coûteuse (ne pas creuser ici sur "pourquoi"), donc si vous utilisez des objets immuables, il n'y a pas de synchronisation pour résoudre le problème de simultanéité les objets immuables ne peuvent pas être modifiés, et si l'état ne peut pas être modifié, tous les threads peuvent accéder de manière transparente à l'objet. Les objets immuables constituent donc un excellent choix pour les objets partagés dans un environnement multithread.


Comme clé pour les collections basées sur le hachage

Une des choses les plus importantes à noter lorsque vous travaillez avec une collection basée sur le hachage est que la clé doit être telle que sa fonction hashCode() doit toujours renvoyer la même valeur pour la durée de vie de l'objet, car si cette valeur est modifiée, l'ancienne entrée est faite dans la base de hachage. La collection utilisant cet objet ne peut pas être récupérée, ce qui provoquerait une fuite de mémoire. Étant donné que l'état des objets immuables ne peut pas être modifié, ils constituent un excellent choix en tant que clé d'une collection à base de hachage. Donc, si vous utilisez un objet immuable comme clé pour une collection basée sur le hachage, vous pouvez être sûr qu'il n'y aura pas de fuite de mémoire à cause de cela (bien sûr, il peut toujours y avoir une fuite de mémoire lorsque l'objet utilisé comme clé n'est pas référencé de n'importe où ailleurs, mais ce n'est pas le point ici).

1
hagrawal

Pourquoi une classe immuable? 

Une fois qu'un objet est instancié, son état ne peut plus être modifié dans la durée de vie. Ce qui le rend également sécuritaire.

Exemples :

Évidemment, String, Integer et BigDecimal, etc. Une fois que ces valeurs sont créées, il est impossible de modifier leur durée de vie.

Cas d'utilisation : Une fois que l'objet de connexion à la base de données est créé avec ses valeurs de configuration, vous n'avez peut-être pas besoin de changer son état dans lequel vous pouvez utiliser une classe immuable

0
bharanitharan

from Effective Java; Une classe immuable est simplement une classe dont les instances ne peuvent pas être modifiées. Toutes les informations contenues dans chaque instance sont fournies lors de sa création et sont corrigées pour la durée de vie de l'objet. Les bibliothèques de la plate-forme Java contiennent de nombreuses classes immuables , Y compris String, les classes primitives encadrées et BigInte - Ger et BigDecimal. Il existe de nombreuses bonnes raisons à cela: Les classes immuables Sont plus faciles à concevoir, à implémenter et à utiliser que les classes mutables. Ils sont moins enclins à commettre des erreurs et sont plus sûrs.

0
tarikfasun

En vertu de l’immuabilité, vous pouvez être sûr que le comportement/l’état de l’objet immuable sous-jacent ne changera pas, ce qui vous donnera l’avantage supplémentaire d’effectuer des opérations supplémentaires:

  • Vous pouvez utiliser plusieurs cœurs/traitements ( traitement simultané/parallèle ) facilement (car la séquence des opérations n'aura plus d'importance.)

  • Peut faire la mise en cache pour les opérations coûteuses (car vous êtes sûr de la même chose
    résultat). 

  • Peut faire débogage avec facilité (comme l'historique de l'exécution ne sera pas une préoccupation
    plus)

0