Aller au contenu

Les classes

Définition

Une classe est une représentation abstraite d’objets qui peuvent exister ou non dans le monde réel. D’un point de vue technique, une classe est un type qui définit la structure et le comportement des objets créés à partir de celle-ci. La création d’un objet à partir d’une classe est appelée instanciation. L’objet obtenu est alors une instance de cette classe. D’un point de vue conceptuel, une classe peut être considérée comme un modèle, un gabarit ou encore un moule, décrivant les caractéristiques communes aux objets d’un même type. Ces deux définitions sont complémentaires : la première décrit le mécanisme proposé par le langage C#, et la seconde permet de comprendre le rôle d’une classe dans la modélisation d’une application.

Dans l’exemple ci-dessous, un moule à donut représente une classe, à partir duquel nous obtenons des donuts qui représentent les objets :

Classe et instances

Les objets d'une même classe ont des caractéristiques communes mais dont les valeurs seront propres à chaque objet (excepté pour les propriétés statiques). Ils proposent les mêmes services, aussi implémentés dans la classe ayant permis de les créer, ou hérités d’une classe de base (voir l'héritage).

Une classe contient un ensemble de membres qui définissent l’état et le comportement des objets créés à partir de celle-ci. Pour simplifier leur présentation, je mets volontairement de côté, dans un premier temps, les membres statiques.

Voici un exemple d'implémentation d'une classe :

public class Gateau
{
    /*******************************************************
     * Etat
     * ****************************************************/
    public double DiametreExterne { get; set; }
    public Pate Pate { get; set; }

    /*******************************************************
     * Constructeur
     * ****************************************************/
    public Gateau(double diametreExterne, Pate pate)
    {
        DiametreExterne = diametreExterne;
        Pate = pate;
    }

    /*******************************************************
     * Comportement
     * ****************************************************/
    public virtual void Frire()
    {
        // Cuisson du gâteau dans l'huile
    }
}

L’état

L’état correspond aux données qui caractérisent un objet. En C#, celles-ci sont principalement représentées par des champs (fields) et des propriétés (properties). Les termes attributs et accesseurs sont employés dans la littérature consacrée à la programmation orientée objet, pour désigner respectivement une donnée et un service associés à un objet. Le champ est un membre qui contient une donnée alors que la propriété permet d’y accéder en lecture et/ou en écriture.

L'état de la classe présentée précédemment contient uniquement des propriétés dites "auto-implémentées". Seules les propriétés sont implémentées et leurs champs sont générés par le compilateur. Pour implémenter la propriété DiametreExterne, nous pourrions également utiliser cette implémentation, plus verbeuse mais équivalente :

// Champ.
private double _diametreExterne;

// Propriété.
public double DiametreExterne
{
    get
    {
        return _diametreExterne;
    }

    set
    {
        _diametreExterne = value;
    }
}

Il est possible d'écrire cette implémentation qui reste équivalente :

// Champ.
private double _diametreExterne;

// Propriété.
public double DiametreExterne
{
    get => _diametreExterne;
    set => _diametreExterne = value;
}

Ou encore cette autre implémentation toujours équivalente (à partir de C# 14), où le mot-clé field représente le champ de stockage généré automatiquement par le compilateur :

public double DiametreExterne
{
    get;
    set => field = value;
}

Dans une propriété, il est possible de ne pas implémenter le getter (get) ou le setter (set), afin de définir respectivement une propriété en écriture seule ou en lecture seule.

Le comportement

Le comportement correspond aux services qu’un objet peut exécuter. Il est constitué de méthodes qui se distinguent entre les procédures et les fonctions. Une procédure exécute un traitement sans retourner de résultat, tandis qu’une fonction exécute un traitement et retourne un résultat. Des évènements complètent le comportement et représentent des services de communication. Des objets abonnés à un évènement seront automatiquement notifiés lorsqu’un évènement sera levé, par exemple parce que la valeur d’un champ a été modifiée ou qu’un service a été exécuté.

Les constructeurs

Une classe peut également posséder un ou plusieurs constructeurs, principalement chargés d’initialiser l’état des instances lors de leur création, et d'exécuter toute autre action nécessaire. Les paramètres des constructeurs représentent les informations minimales qu’il faut fournir à la classe pour l’instancier.

Dans notre classe Gateau, les paramètres de ce constructeur :

public Gateau(double diametreExterne, Pate pate)
{
    DiametreExterne = diametreExterne;
    Pate = pate;
}

indiquent qu'il est nécessaire de fournir un diamètre externe et une pâte afin de pouvoir l'instancier.

Un constructeur peut être surchargé, à condition que chaque surcharge possède une signature différente, notamment par le nombre, le type ou l’ordre de ses paramètres. Par exemple, nous pouvons ajouter un constructeur qui nécessite uniquement un diamètre externe :

public Gateau(double diametreExterne)
{
    DiametreExterne = diametreExterne;
}

Avec ce constructeur, la pâte n'est pas définie. Une bonne pratique consiste à appeler l'autre constructeur, en indiquant de manière explicite : - Soit null afin de spécifier qu'aucune pâte n'est nécessaire pour créer un gâteau. - Soit une nouvelle instance de la classe Pate, utilisée comme pâte par défaut.

public Gateau(double diametreExterne)
    : this(diametreExterne, new Pate())
{

}

Le finaliseur

Une classe peut posséder au maximum un finaliseur, utilisé dans certains cas particuliers pour libérer des ressources non managées détenues par ses objets :

~Gateau()
{
    // Libération des ressources.
}

Comme son nom l'indique, il permet de finaliser la destruction d'un objet. Il est appelé par le Garbage Collector (mécanisme qui gère automatiquement la mémoire en libérant celle occupée par les objets qui ne sont plus utilisés dans l’application) juste avant la destruction effective de l'objet.

L'instanciation

L’instanciation consiste à créer un objet à partir d’une classe. En C#, elle est généralement réalisée à l’aide de l’opérateur new, qui appelle un constructeur afin d’initialiser l’état de l'objet créé. Chaque objet créé possède son propre état et propose des services. Voici un exemple :

Pate pate = new Pate(new Ingredient[] { 
    new Ingredient("Farine"), 
    new Ingredient("Lait"), 
    new Ingredient("Sucre"), 
    new Ingredient("Oeuf") 
});

Donut donut = new Donut(10, 6, pate);

donut.Frire();

Les membres statiques

Jusqu'à présent, nous avons uniquement abordé les membres d'instance, autrement dit des membres applicables aux instances des classes. En complément, une classe peut proposer des membres statiques. Toutes les catégories de membres présentées ci-dessus peuvent être statiques. Dans ce cas, le membre ne s'applique plus à une instance de la classe, mais à la classe elle-même.

Par exemple, si nous définissions la propriété DiametreExterne comme étant statique, cela signifie que le diamètre externe n'est plus propre à chaque gâteau, mais commun à tous les gâteaux. Autrement dit, les gâteaux ne peuvent être que d'un seul diamètre externe :

public static double DiametreExterne { get; set; }

Pour utiliser cette propriété, il faut utiliser le nom de la classe :

Gateau.DiametreExterne = 25;

Une méthode peut aussi être statique. Par exemple, voici une méthode qui retourne un ensemble de gâteaux, dont le diamètre externe est égal à celui passé en paramètre :

public static IEnumerable<Gateau> GetInstances(double diametreExterne)
{
    // Implémentation.
}

Pour appeler cette méthode, il faut également utiliser le nom de la classe :

var gateaux = Gateau.GetInstances(18);

Un constructeur peut aussi être statique (aucun niveau de visibilité n'est à définir) :

static Gateau()
{
    // Implémentation.
}

Ce constructeur n’est jamais appelé explicitement dans le code. Il est exécuté automatiquement par le runtime avant la première utilisation du type. Son rôle principal est d’initialiser les membres statiques, notamment lorsque leur initialisation nécessite des traitements trop complexes, pour être implémentés en dans leur déclaration.