Aller au contenu

Objet immuable

Un article de Wikipédia, l'encyclopédie libre.

Un objet immuable, en programmation orientée objet et fonctionnelle, est un objet dont l'état ne peut pas être modifié après sa création. Ce concept est à contraster avec celui d'objet variable.

Généralités

[modifier | modifier le code]

Avant l'apparition de la programmation orientée objet, les variables d'exécution dont le contenu n'était pas destiné à changer en cours d'exécution (par exemple, le facteur de conversion permettant de transformer des livres en kilogrammes, ou encore la valeur de pi avec plusieurs décimales) étaient connues sous le nom de constantes, pour les différencier de celles qui pouvaient être modifiées.

Dans la plupart des langages orientés objet, les objets sont manipulés par des références. C'est le cas, par exemple, de Java, des objets de type class de C# et de Swift (contrairement aux objets de type struct), de Python, de JavaScript et de Ruby. Dans ce cas, le fait que l'état d'un objet partagé par référence puisse ou non être modifié est important.

Lorsqu'un objet est réputé immuable, on peut en obtenir une copie en dupliquant simplement sa référence, au lieu de copier l'objet entier. Comme une référence (qui n'occupe typiquement en mémoire que la taille d'un pointeur) est habituellement bien moins volumineuse que l'objet lui-même, cette technique permet à la fois d'économiser de la mémoire et d'améliorer la vitesse d'exécution.

La technique de copie des références est bien plus complexe à utiliser pour les objets variables, parce que si un élément quelconque utilisant la référence à un objet variable modifie ce dernier, la modification deviendra visible pour tous les autres éléments utilisateurs de la référence. Au cas où ce ne serait pas l'effet souhaité, il peut s'avérer difficile de faire répondre correctement les autres utilisateurs. Dans une telle situation, la copie préventive de l'objet dans son ensemble constitue une solution fréquemment utilisée, mais coûteuse. Le patron de conception observateur représente une solution alternative pour la gestion des modifications des objets variables.

Les objets immuables peuvent être utilisés dans des applications multi-threads. Plusieurs threads peuvent utiliser des données implémentées en tant qu'objets immuables, sans se préoccuper d'une éventuelle modification par un thread concurrent. C'est pourquoi les objets immuables sont considérés comme plus sûrs dans un contexte multi-threads que les objets variables.

La technique qui consiste à toujours utiliser des références au lieu de copies d'objets identiques immuables, en ne stockant qu'une seule fois en mémoire leur valeur, porte en anglais le nom d'« interning ». Dans ce cadre, deux objets sont considérés comme égaux si et seulement si leurs références, typiquement représentées par des entiers, sont égales. Certains langages gèrent cette technique automatiquement : par exemple, Python gère automatiquement de cette manière les chaînes courtes. Si l'algorithme qui implémente l'interning garantit de l'effectuer chaque fois que c'est possible, alors la comparaison d'égalité entre deux objets se réduit à la comparaison de leurs pointeurs, ce qui représente un gain en rapidité substantiel dans la plupart des applications. L'interning ne présente généralement d'intérêt que pour les objets immuables.

Il peut arriver que seuls certains champs d'un objet soient immuables : l'objet lui-même est immuable seulement si tous ses champs sont immuables. Ceci peut permettre par exemple de contraindre certaines données d'un objet à rester invariables tout au long de la vie de l'objet. Dans certains langages, ceci se réalise au moyen d'un mot-clé (comme const en C++, ou final en Java). Dans d'autres langages, c'est l'inverse : en OCaml, les champs d'un objet sont immuables par défaut, et doivent être marqués explicitement comme variables le cas échéant.

Dans les langages de programmation

[modifier | modifier le code]

L'immuabilité n'implique pas que l'objet, tel qu'il est stocké en mémoire, ne puisse être réécrit. Il s'agit plutôt d'une directive de compilation indiquant ce qu'un programmeur a le droit de faire via l'interface normale de l'objet, et non ce qu'il est possible de faire dans l'absolu (par exemple en contournant le système de typage ou en violant les règles de gestion des const en C ou en C++).

Voici un exemple d'objet immuable en C# :

class Position2D
{
    private readonly double _x;
    private readonly double _y;
    public double X { get { return _x; }}
    public double Y { get { return _y; }}
    public Position2D(double x, double y)
    {
        _x = x;
        _y = y;
    }
}

Un exemple classique d'objet immuable en Java est l'instanciation de la classe String :

    String s = "ABC";
    s.toLowerCase();

La méthode toLowerCase() ne modifie pas la valeur "ABC" contenue dans la chaîne s. Au lieu de cela, un nouvel objet String est instancié, et la valeur "abc" lui est attribuée lors de sa construction. La méthode toLowerCase() retourne une référence à cet objet String. Si l'on souhaite que la chaîne s contienne la valeur "abc", il faut utiliser une approche différente :

    s = s.toLowerCase();

Dans ce cas, la chaîne s référence un nouvel objet String qui contient "abc". Rien dans la déclaration d'un objet de classe String ne le contraint à être immuable : c'est plutôt qu'aucune des méthodes associées à la classe String n'affecte jamais la valeur d'un tel objet, ce qui le rend de fait immuable.

Voici à présent un exemple d'objet Java, qui en fonction des différentes définitions de l'immuabilité peut être considéré soit comme variable, soit comme immuable. Si l'on considère que l'immuabilité des objets référencés découle de l'immuabilité de l'objet qui porte la référence (une sorte d'« immuabilité profonde »), alors l'objet ci-dessous est variable, car il comporte une référence à un objet List, qui peut être variable. Si, en revanche, on ne considère que la valeur de la référence elle-même, et non l'objet vers lequel elle pointe (une sorte d'« immuabilité de surface »), alors cet objet est immuable, car il ne fournit aucune interface (méthode ou champ non privé) permettant de modifier de l'extérieur la valeur de l'un quelconque de ses champs (y compris la valeur de la référence à l'objet List).

    class Panier {
       private final List articles;

       public Panier(List articles) { this.articles = articles; }

       public List getArticles() { return articles; }
       public int total() { /* retourne le prix total */ }
    }

Si l'on souhaite garantir que l'objet List est également immuable, alors la modification suivante résout partiellement le problème. Dans la classe PanierImmuable, la liste n'est pas variable : il est impossible d'y ajouter ou d'en retirer des articles. Toutefois, il n'est pas garanti que les articles soient eux aussi immuables. Une solution consiste à inclure chaque article de la liste dans un patron de conception décorateur conteneur pour le rendre également immuable. Une autre solution consisterait à ne laisser aucune référence aux articles de l'objet List s'échapper de la classe PanierImmuable en utilisant l'instruction return new ArrayList(articles); dans la méthode getArticles().

    class PanierImmuable {
       private final List articles;

       public PanierImmuable(List articles) {
         this.articles = Collections.unmodifiableList(new ArrayList(articles));
       }

       public List getArticles() {
           return articles;
       }
       public int total() { /* retourne le prix total */ }
    }

Les instances des classes enveloppes (types objets enveloppant les types primitifs) Integer, Long, Short, Double, Float, Character, Byte, Boolean sont également immuables.

Les classes immuables peuvent être implémentées en suivant quelques recommandations simples[1].

En langage Python, les types bool, tuple, str, bytes, frozenset, range sont immuables, ainsi que les types numériques int, float et complex. La liste n'est pas exhaustive[2].

Les objets tuple sont des listes immuables d'objets hétérogènes en Python. On peut voir le type tuple comme le pendant immuable de list, et frozenset comme le pendant immuable de set. Contrairement à ce qu'on observe par exemple en langage Ruby, les chaînes de caractères (str) sont immuables. Les chaînes d'octets (bytes) également, mais il existe un type bytearray variable.

L'objet symbole est immuable en Ruby, il est noté :

:symbole

Par défaut, les variables et les objets sont immuables ou non mutables. Le symbole mut permet de les rendre mutables.

Copy-on-write

[modifier | modifier le code]

Notes et références

[modifier | modifier le code]
  1. (en) « Immutable objects », javapractices.com (consulté le )
  2. Voir Data model dans la documentation du langage Python, et Built-in Types dans la documentation de la bibliothèque standard.