Développement C# (.NET)
Last updated on Tuesday, January 8, 2013
  • Facile

Ce cours est visible gratuitement en ligne.

Got it!

La programmation orientée objet (2/3)

Vous savez déjà créer et utiliser des objets. Vous pouvez déjà commencer à créer vos classes sans ce chapitre, mais quelques notions importantes de plus ne ferrons pas de mal :p
En effet, dans ce chapitre, je vais vous parler de l'encapsulation, une concept de base de la programmation orientée objet. Nous verrons donc ici comment modifier la visibilité des champs et des méthodes et dans quels cas nous devons rendre un champ ou une méthode plus ou moins accessible.

Théorie sur l'encapsulation

Le problème

Imaginons un objet comme une mécanique très complexes, avec des multitudes d'engrenages, de pistons, de mécanismes... comme dans une voiture. Cette mécanique est tellement complexe qu'on a créé des commandes pour manipuler ces différents éléments de manière simple. Par exemple tourner la clé actionnera le démarreur qui injectera de l'essence dans le moteur et allumera les bougies pour déclencher l'explosion, ... et ainsi de suite jusqu'à que les roues soient entrainées. Ainsi, même si la mécanique est complexe dans le moteur, il nous suffit globalement de savoir tourner une clé, appuyer sur des pédales et tourner un volant.

L'encapsulation

En programmation orientée objet, c'est la même chose, nous allons "cacher" tout les champs pour ne pouvoir les manipuler qu'au sein de notre classe, ces manipulations seront faites par les méthodes qui pourront quant à elles être publiques. Nous allons nommer ce concept "encapsulation", il va apporter un niveau d'abstraction supplémentaire.

Mais rendre nos champs privés, ça ne va pas complexifier l'utilisation de la classe ?

La réponse est non, bien au contraire, cela va simplifier beaucoup de chose, non pas lors de la conception de la classe, mais lors de son utilisation !! Imaginez un instant que dans votre voiture vous ayez accès dans l'habitacle à chaque partie du moteur ! D'une part cela va déstabiliser quelqu'un qui n'a jamais appris à conduire, d'autres part c'est parfaitement inutile et même pire cela pourrait être dangereux si jamais vous essayez de manipuler directement un élément du moteur. Pour une classe (mais avec des enjeux moins vitaux je vous rassure :p ) cela va être la même chose, avoir accès à tout les champs peut nous perdre dans un premier temps (quel champ faut-il récupérer ? Quelle méthode faut-il utiliser?), ensuite c'est inutile puisque nous pouvons utiliser de manière simple une méthode qui manipulera les données à notre place et enfin en manipulant des champs qui n'ont pas lieu d'être modifiés directement pourrait "dérégler" le fonctionnement des méthodes.

Différentes accessibilités

Publique

Une accessibilité publique sur un champ ou une méthode permet d'être accessible en dehors de la classe. Le mot clé pour une accéssibilité publique est public. Observons l'extrait de code suivant :

class Program
{
    static void Main(string[] args)
    {
        ClasseChampPublic champPublic = new ClasseChampPublic();
        ClasseChampPasPublic champNonPublic = new ClasseChampPasPublic();
        Console.WriteLine(champPublic.nombre);
        Console.WriteLine(champNonPublic.nombre);//Impossible, nombre n'est pas public!
    }
}

class ClasseChampPublic
{
    public int nombre;
}

class ClasseChampPasPublic
{
    int nombre;
}

Cela ne compilera pas à cause de la ligne numéro 8, en effet, on ne pourra pas accéder au champ "nombre" ainsi.

Privée

Une accessibilité privée est l'accéssibilité par défaut pour les champs, les classes et les méthodes. Lorsque nous ne mettons accessibilité devant un champ ou une méthode, ce champ ou cette méthode sera privée. Le mot clé pour une accessibilité privée est private. La classe suivante aura tout ses champs et toutes ses méthodes privées.

class ClasseToutePrivee
{
    private int nombre;

    private void DireBonjour()
    {
        Console.WriteLine("Bonjour");
    }
}

Le code donnerait exactement le même résultat sans les mot clé private, à vous de voir si vous préférez un code plus court ou un code plus explicite ;)

Protégée

Une accessibilitée protégée sera presque identique à une accessibilité privée. La seule différente se fera lorsqu'une classe héritera d'une autre. En effet, une classe fille n'aura pas accès aux champs et méthodes privés mais aura accès aux champs et méthodes protégées. Sinon, en dehors de la classe mère et des classes filles, l’accessibilité sera identique à privée. Le mot clé pour une accessibilité protégée est protected. Observons le code suivant :

class Program
{
    static void Main(string[] args)
    {
        ClasseFille fille = new ClasseFille();
        Console.WriteLine(fille.nombrePrive);//Impossible, nombrePrivee n'est pas public
        Console.WriteLine(fille.nombreProtege);//Impossible, nombreProtege n'est pas public
    }
}

class ClasseMere
{
    private int nombrePrive;
    protected int nombreProtege;
}

class ClasseFille : ClasseMere
{
    public ClasseFille()
    {
        nombrePrivee = 4;//Impossible, champ prive
        nombreProtege = 5;//Possible, champ protégé provenant de la classe mere
    }
}

Nous voyons que depuis la classe Program, nous n'avons accès ni à "nombrePrive", ni à "nombreProtege", sur ce point une accessibilité publique ou privée reviens au même. Néanmoins, nous voyons que dans "ClasseFille" nous avons accès à "nombreProtegee" car ce champ provient de la classe mère et est signalé comme protégé.

L'encapsulation en pratique

Les accessibilités recommandées

Généralement en POO pour les champs on applique une règle simple : tout en privé! Et c'est d'ailleurs ce que quasiment tout développeur recommande
(sauf parfois certains champs en protégés quand on n'a pas le choix).

Quant aux méthodes, on les déclare comme publiques, sauf bien sur pour les méthodes ne servant qu'aux traitements internes et n'ayant aucun intérêt d'être accessibles en dehors de la classe.

class Personnage
{
    private string _nom;
    private int _age;

    public Personnage(string nom, int age)
    {
        this._nom = nom;
        this._age = age;
        Console.WriteLine(GetIdentite() + " a été créé");
    }

    private string GetIdentite()
    {
        return (_nom.ToString() + ", " + _age.ToString() + " ans");
    }

    public void SePresenter()
    {
        Console.WriteLine("----------------------------");
        Console.WriteLine("Bonjour, je suis " + GetIdentite());
        Console.WriteLine("----------------------------");
    }
}

La classe précédente est un cas typique d'utilisation des accessibilités publiques et privées : tout les champs sont privés, une méthode est publique, et une méthode privée a été créée pour éviter de dupliquer du code entre le constructeur et l'autre méthode. Ici, la méthode privée "GetIdentite" n'a pas vraiment d'intérêt à être publique, donc nous la laissons privée et nous l'utilisons uniquement pour la logique interne.

Accéder aux méthodes c'est bien. Mais comment faire si on veut accéder à des champs ?

La question est bonne, différentes manières peuvent être appliquées, nous allons les voir dans la partie suivante.

Accéder aux champs : accesseurs, mutateurs et propriétés

Pour accéder aux champs, comme je l'ai dis, plusieurs manières existent, l'une est plus spécifique au C# que les autres, néanmoins je nous allons voir la technique "plus générale" qui fonctionne tout aussi bien et qui sera applicable si vous apprenez un autre langage orienté objet.

Les accesseurs et les mutateurs

La première manière est assez simple, d'ailleurs je l'ai déjà utilisé sans le dire dans le premier chapitre sur la programmation orientée objet et certains auront peut être deviné comment faire tout seuls.

Nous allons tout simplement utiliser des méthodes publiques pour récupérer ou modifier nos champs! Une méthode dite "accesseur" qui ne prendra aucun paramètre et qui aura comme type de retour le type du champ à récupérer (par convention, le nom de cette méthode commencera par "Get" suivi du nom du champ) et une seconde méthode dite "mutateur" sans type de retour prenant en paramètre la nouvelle valeur à affecter (par convention son nom commencera par "Set" suivant du nom du champ). En ajoutant accesseurs et mutateurs à la classe précédente nous aurons le code suivant (pour plus de clarté, j'ai ajouté des "régions" permettant de délimiter les différentes parties du code):

class Personnage
{
    #region Champs
    private string _nom;
    private int _age; 
    #endregion

    #region Accesseurs/Mutateurs
    public string GetNom()
    {
        return _nom;
    }

    public void SetNom(string nouveauNom)
    {
        _nom = nouveauNom;
    }

    public int GetAge()
    {
        return _age;
    }

    public void SetAge(int nouvelAge)
    {
        _age = nouvelAge;
    }
    #endregion

    #region Constructeur
    public Personnage(string nom, int age)
    {
        this._nom = nom;
        this._age = age;
        Console.WriteLine(GetIdentite() + " a été créé");
    } 
    #endregion

    #region Methodes
    private string GetIdentite()
    {
        return (_nom.ToString() + ", " + _age.ToString() + " ans");
    }

    public void SePresenter()
    {
        Console.WriteLine("----------------------------");
        Console.WriteLine("Bonjour, je suis " + GetIdentite());
        Console.WriteLine("----------------------------");
    } 
    #endregion
}

Mais attend, mettre un accesseur et un mutateur, cela revient au même que de mettre le champ en public mais en plus compliqué ?

Oui et non. Au premier abord on pourrait dire que cela revient au même, mais en réalité on contrôle plus l'accès aux champs de cette manière. Si nous voulions fournir un accès en lecture seule à _age et à _nom, nous enlèverions les mutateurs, ainsi la modification ne pourrait se faire qu'à l'intérieur de la classe. De même on pourrait rajouter des traitements à chaque accès ou à chaque modification, comme afficher un message.

Cela fait tout de même beaucoup de code pour pas grand chose, et on s'y perd un peu entre les accesseurs et mutateurs mêlés avec les autres méthodes. Il n'y a pas un moyen de faire plus simple ?

Justement, j'allais y venir. Cette manière passe par ce qu'on appelle les propriétés !!

Les propriétés

Les propriétés sont plus spécifiques au C# (mais existent peut être dans d'autres langages ). Elles vont être quelques chose entre un champ et une méthode. Par exemple :

private int _unChamp;

public int UnePropriete
{
    get { return _unChamp; }
    set { _unChamp = value; }
}

Comme vous pouvez le deviner "_unChamp" est un champ et "UnePropriete" est ... une propriété :D . Au premier abord, elle ressemble à un champ vu qu'elle a une accessibilité, un type et un nom, nous l'utiliserons d'ailleurs comme telle. Ensuite il s'ensuit un "corps de propriété", délimité par des accolades ( " { " et " } ") avec deux parties distinctes, une partie "get" et une partie "set". La partie "get" va simplement être exécutée lorsque que nous voudrons accéder à la propriété pour lire sa valeur, entre les accolades suivant le mot clé "get", nous attendons le retour d'un champ ou d'une valeur du type de la propriété (ici nous retournons "unChamp" qui est du même type que "UnePropriété").
Quant à la partie "set", elle récupère une valeur accessible par le mot clé "value" dans le corps de "set", libre à vous de faire ce que vous voulez de cette valeur, généralement nous l'attribuons à un champ.

Cela peut paraitre un peu flou, même si ce n'est pas très compliqué, je vais vous montrer un petit exemple pas à pas, tout d'abord, une classe de base avec juste un champ, une propriété et un constructeur.

class ClasseTest
{
    private int _unChamp;

    public int UnePropriete
    {
        get { return _unChamp; }
        set { _unChamp = value; }
    }

    public ClasseTest()
    {
        _unChamp = 3;
    }
}

Ensuite, je vais instancier ma classe et récupérer la propriété.

static void Main(string[] args)
{
    ClasseTest instanceTest = new ClasseTest(); //unChamp vaut 3 (voir constructeur de ClasseTest)
    Console.WriteLine("La propriété vaut " + instanceTest.UnePropriete.ToString());//Affichera "La propriété vaut 3
}

Ici cela va être la partie "get" de la propriété qui sera exécutée, la valeur de "_unChamp" sera donc retournée.

A présent, je vais modifier "UnePropriete" pour lui assigner la valeur 4 :

instanceTest.UnePropriete = 4;

Là, cela va être la partie "set" qui sera exécutée et "value" vaudra 4 (la valeur qu'on veut assigner à la propriété), après cette ligne de code, "_unChamp" vaudra 4. Nous vérifions cela en récupérant à nouveau à la valeur de "UnePropriete", ce qui donne le code suivant :

static void Main(string[] args)
{
    ClasseTest instanceTest = new ClasseTest(); //unChamp vaut 3 (voir constructeur de ClasseTest)
    Console.WriteLine("La propriété vaut " + instanceTest.UnePropriete.ToString());//Affichera "La propriété vaut 3
    instanceTest.UnePropriete = 4;
    Console.WriteLine("Nous avons modifié \"UnePropriete\"");//Les \ sont dit caractères d'échappement, ils permettent d'afficher " sans 
                                                             //fermer la chaine de caractère
    Console.WriteLine("La propriété vaut maintenant " + instanceTest.UnePropriete.ToString());//Affichera "La propriété vaut maintenant 4
}

Lorsque nous exécutons nous obtenons :

La propriété vaut 3
Nous avons modifié "UnePropriete"
La propriété vaut maintenant 4

Pour rendre notre propriété en lecture seule depuis l'extérieur de la classe, deux solutions s'offrent à nous :

  • Supprimer la partie "set" de la propriété (c'est radical vous verrez :D )

  • Mettre le mot clé "private" devant la partie "set", ainsi nous pourrons utiliser la propriété à l'intérieur de la classe.

Ajouts du framework 3.5

Depuis le framework 3.5, des raccourcis d'écriture ont été ajoutés, ainsi au lieu d'écrire cela :

private int _unChamp;

public int UnePropriete
{
    get { return _unChamp; }
    set { _unChamp = value; }
}

Nous pouvons simplement écrire cela :

public int UnePropriete { get; set; }

Ces deux extraits de codes reviennent au même, le second fonctionnera comme si nous avions un membre supplémentaire qui est accédé et modifié par cette propriété. Si vous ne voulez pas faire de traitements supplémentaires dans la propriété, ce raccourci est tout fait convenable. A titre personnel, je préfère l'ancienne méthode que je trouve plus explicite avec un champ et une propriété séparée, mais ce n'est que mon humble avis ^^

Des raccourcis Visual Studio

Nous avons une grande chance de travailler sur un bon environnement de développement qui nous simplifie beaucoup de choses !! En effet, Visual Studio inclue trois "code snippets" qui nous permettent de créer automatiquement des propriétés.
Le premier est "prop", tapez ce mot clé dans l'éditeur de code et appuyez deux fois sur la touche tabulation, un extrait de code sera généré pour une propriété "version 3.0", le type de la propriété et son nom sont alors surlignés, vous êtes alors en mode édition de la propriété : en appuyant sur tabulation vous passez successivement sur le type et le nom de la propriété, pour quitter le mode édition de la propriété, appuyez sur la touche Entrée ou tapez du code autre part. Attention, vous ne pouvez avoir le "mode édition de propriété" uniquement lors de la création de celle ci par le raccourci de Visual Studio.

public int MyProperty { get; set; }//Généré par prop

Le second est propg, il générera une propriété "version" 3.0" mais avec un "set" privé, ce qui permettra de modifier la valeur de cette propriété uniquement à l'intérieur de sa classe.

public int MyProperty { get; private set; }//Généré par propg

Et enfin la dernière : propfull. Celle ci générera un champ et la propriété permettant d'y accéder. En mode édition de propriété, la modification du type du champ modifie en même temps le type de la propriété (logique non ? ;) ) et la modification du nom du champ modifie aussi l'intérieur du "get" et du "set" de la propriété, profitez donc de ce "code snippet" lors de l'ajout de propriété, cela permet d'ajouter un grand nombre de propriété de manière assez rapide.

private int myVar;

public int MyProperty
{
    get { return myVar; }
    set { myVar = value; }
}

On en a fini avec un des concepts les plus importants de la POO : l'encapsulation. Peut importe le langage orienté objet que vous manipulez, vous retrouverez ces concepts d'encapsulation permettant de maintenir cohérente la manipulation des champs de nos objets.

Example of certificate of achievement
Example of certificate of achievement