C# – Comment fournir une vue non modifiable d’un objet ?

Voilà ce que ça donne de passer son temps à regarder le code de .NET dans Reflector : on y trouve chaque jour des façons de faire auxquelles nous n’avions pas pensé !

Ici, une façon simple de founir des objets non modifiables. À lire ci-dessous.

Un wrapper, comme son nom l’indique un peu, permet d’emballer un objet derrière une nouvelle interface. Cette nouvelle interface peut ajouter, supprimer ou modifier les propriétés et les méthodes de l’interface de la classe originale.

Prenons un exemple presque réaliste. Vous écrivez une classe nommée Customer qui sera un peu le centre d’une nouvelle application CRM. Cette application possèdera bien entendu une collection de Customer et passera cette collection à différents traitements. Certains traitements mettront à jour le contenu de la collection, mais la plupart n’y accéderont qu’en consultation.

Vous vous demandez : comment m’assurer que ces traitements n’iront pas modifier le contenu de ma collection ?

Une solution simple consiste (vous l’aurez deviné…) à créer un wrapper qui dissimulera les objets Customer derrière une nouvelle interface ne permettant aucune modification. Il existe différentes méthodes pour créer un tel wrapper, mais la meilleure me semble être celle qui est décrite ci-dessous.

Dans cette implémentation, le wrapper est une classe interne privée (ReadOnlyCustomer) de la classe Customer. L’avantage est que l’utilisateur manipule toujours des objets de type Customer, certains étant modifiables et d’autres pas. L’indicateur IsReadOnly() permet de déterminer s’il s’agit d’un objet modifiable ou non.

Voici la définition de la classe Customer. Voir tout de suite après un exemple d’utilisation de cette classe.

private class Customer
{
    private string _name;
    public virtual string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public virtual void SaySomething(string something)
    {
        Console.WriteLine( something );
    }

    // Vrai si l'instance en cours est read-only.
    public virtual bool IsReadOnly
    {
        get { return false; }
    }
   
    // Retourne un wrapper read-only de l'instance indiquée. Il s'agit toujours de
    // la même instance, mais l'interface est modifiée (IsReadOnly retourne true
    // et il n'est pas possible de modifier le contenu de l'objet).
    public static Customer ReadOnly( Customer customer )
    {
        return new ReadOnlyCustomer(customer);
    }

    // Classe wrapper de la classe Customer permettant de la rendre read-only.
    // A noter qu'il s'agit d'une classe privée. Il n'est possible de créer des
    // objets ReadOnlyCustomer qu'en appelant la méthode Customer.ReadOnly.
    private class ReadOnlyCustomer : Customer
    {
        Customer _customer;

        internal ReadOnlyCustomer(Customer customer)
        {
            this._customer = customer;
        }

        public override string Name
        {
            get { return this._customer.Name; }
            set { throw new AccessViolationException("La propriété Name ne peut être modifiée."); }
        }

        public override void SaySomething(string something)
        {
            base.SaySomething(something);
        }

        public override bool IsReadOnly
        {
            get{ return true; }
        }
    }
}

Exemple d’utilisation :

static void Main(string[] args)
{
    // Création d'un objet Customer.
    Customer customer = new Customer();
    customer.Name = "Pioupiou";

    // La ligne suivante affiche : "customer.Name = Pioupiou"
    Console.WriteLine("customer.Name = " + customer.Name);

    // Création d'un wrapper read-only sur l'objet Customer initial :
    Customer readonlyCustomer = Customer.ReadOnly(customer);

    // La ligne suivante affiche : "readonlyCustomer.Name = Pioupiou"
    Console.WriteLine("readonlyCustomer.Name = " + readonlyCustomer.Name);

    // Les modifications apportées à l'objet Customer sont visibles à travers le wrapper :
    customer.Name = "coincoin";

    // La ligne suivante affiche : "readonlyPerson.Name = coincoin"
    Console.WriteLine("readonlyCustomer.Name = " + readonlyCustomer.Name);

    // La ligne suivante génère une exception de type AccessViolationException :
    readonlyCustomer.Name = "Coincoin";
}

On procédera de façon similaire pour définir une méthode ReadOnly() dans la classe CustomersCollection, cette méthode retournant alors un objet de type ReadOnlyCustomersCollection, ce dernier contenant une collection de ReadOnlyCustomer non modifiables.

L’application pourra alors conserver une référence aux deux listes et passer l’une ou l’autre à chaque traitement. Aucune gestion n’est à prévoir puisque toute modification apportée à la collection modifiable est aussitôt visible dans la collection non modifiable.

Elle est pas belle, la vie ?

Ajouter un commentaire

Les retours à la ligne suivantes sont automatiques.
Codes XHTML permis : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>