Archives Wild Code School
C#

1. POO - Les constructeurs

Quête 11 sur 46 · 3 images archivées

Après cette quête, tu sauras définir et utiliser des constructeurs dans tes classes

Objectifs

  • Comprendre la notion de construction
  • Comprendre la notion de finalisation
  • Connaître la syntaxe de définition d'un constructeur
  • Utiliser plusieurs constructeurs pour une seule classe

Porte bien ton casque sur le chantier

Tu es invité sur un chantier afin de participer à la conception d'un bâtiment. La conception avait précédemment été bâclée par une entreprise peu scrupuleuse en terme de qualité. Le prestataire en question a donc été éjecté de l'affaire et c'est TOI qui as été choisi pour réaliser le boulot.

Mais tes clients ont un problème majeur : ils n'arrivent pas à définir la taille au regard du nombre d'étages. Ils veulent réduire les frais, mais s'accordent quand même pour dire que des étages d'une hauteur de 1m90, c’est très peu envisageable.

Tu dois les ramener à la raison et profiter de ton savoir-faire pour leur proposer une solution rationnelle et justifier les choix que tu leur proposes.

Un constructeur

Un constructeur est une méthode appartenant à une classe qui a pour tâche de construire une de ses instances. Ainsi, un constructeur permet d'initialiser des attributs dans la classe et/ou de réaliser certaines opérations devant être faites au départ lorsqu'un objet est instancié. Les constructeurs sont vitaux, car ils permettent de définir l'objet de base sur lequel on travaille.

La signature d'un constructeur est différente de celle des autres méthodes. La signature d'un constructeur se passe du type retourné (puisqu'il ne peut en retourner qu'un seul) et le nom de la méthode est le nom de la classe.

Un petit exemple pour clarifier:

csharp
namespace Constructors
{
    public class Building
    {
        private double _height;

        // Constructor is public because it should be
        // accessed outside of the class scope
        public Building(double height)
        {
            // The constructor body affects the argument `height`
            // to the private attribute `_height` to use this value
            // later in the code
            _height = height;
        }
    }
}

Le code ci-dessus définit une class Building qui représente un bâtiment. Cette classe dispose d'un attribut privé _height qui représente la hauteur dudit bâtiment. La classe Building dispose d'un constructeur qui prend en argument une hauteur. Ce constructeur affecte la valeur height passée en argument à l'attribut privé _height afin que le bâtiment fraîchement initialisé contienne une taille.

Si on jette un oeil au détail de ce code source, la signature de notre constructeur n'a pas de type de retour et porte le nom de la classe, déclaré comme suit :

c#
public Building(double height)

Maintenant que tu as compris ce que fait un constructeur et la syntaxe qui permet d'en écrire, as-tu une idée de comment l'invoquer ?

Avec le mot-clé new ?

Bingo ! Le mot-clé new - qui crée une instance de classe - réalise en fait un appel à un des constructeurs de la classe à instancier.

Après avoir ajouté un accesseur en lecture pour l'attribut privé height, voici comment on construit un objet Building et comment on affiche sa hauteur :

cs
namespace Constructors
{
    public static class Program
    {
        public static void Main()
        {
            // Here a new instance of `Building`is created by
            // calling the constructor I had written in the
            // `Building` class
            Building building = new Building(5.89);
            double height = building.GetHeight();
            System.Console.WriteLine(height);
        }
    } 

    public class Building
    {
        private double _height;

        public Building(double height)
        {
            _height = height;
        }

        public double GetHeight()
        {
            return _height;
        }
    }
}

Dans l'exemple ci-dessus, un simple accesseur en lecture a été ajouté par rapport au code précédent de la classe Building. Dans la méthode Program.main, on crée une nouvelle instance de la classe avec le mot-clé new qui appelle le constructeur écrit dans la classe Building. A la fin, on affiche la hauteur précédemment récupérée en ayant fait appel à l'accesseur en lecture de l'attribut privé _height.

Très bien, je sais maintenant comment définir un comportement lors de la construction d'une instance de classe. Mais si je veux plusieurs comportements différents lors de la construction, comment je fais ?

↗
Constructeur en C# - MSDN

Lire jusqu'à la fin de "Constructor Syntax"

docs.microsoft.com
↗
Constructeur en C# - Vidéo

Regarder jusqu'à 3:14

youtube.com

Plusieurs constructeurs

Il est possible de déclarer plusieurs constructeurs pour une seule classe. Ce qui veut dire que la classe peut avoir différents comportements lors de la construction, définis en fonction du nombre d'arguments que reçoit le constructeur.

Puisque le nom de méthode d'un constructeur doit être le nom de la classe qu'il construit, la seule manière de proposer plusieurs constructeurs est de changer le nombre et/ou le.s type.s des arguments que peut recevoir un constructeur.

Exemple avec du code :

cs
namespace MultipleConstructors
{
    public static class Program
    {
        public static void Main()
        {
            Room rectangularRoom = new Room(3.5, 4.5);
            Room squareRoom = new Room(3.3);
            Room[] rooms = { rectangularRoom, squareRoom };

            foreach (Room room in rooms)
            {
                double roomSize = room.GetAreaSize();
                System.Console.WriteLine("Size of the room area is " + roomSize + " m²");
            }
        }
    }

    public class Room
    {
        private double _width;
        private double _height;

        public Room(double size)
        {
            _height = _width = size;
        }

        public Room(double height, double width)
        {
            _height = height;
            _width = width;
        }

        public double GetAreaSize()
        {
            return _height * _width;
        }
    }
}

Le code au-dessus définit une classe Room qui représente une pièce d'un bâtiment. Une pièce possède une surface qui est le résultat de la multiplication de sa largeur et de sa longueur : c'est ce que fait la méthode Room.GetAreaSize qui renvoie la surface en mètres carrés.

Autre fait notable, la classe Room possède deux constructeurs : un où l'on spécifie la taille (size) et l'autre où l’on spécifie la longueur (height) et la largeur (width). Dans le point d'entrée du programme (Program.Main), deux objets de classe Room sont instanciés en utilisant chacun un constructeur différent :

  • Le constructeur ne prenant qu'un seul argument double size affecte size à Room._width et Room._height: il s'agit du cas où notre pièce est carrée, car sa longueur et sa largeur sont identiques.

  • Le deuxième constructeur permet de créer une pièce rectangulaire en spécifiant sa longueur (height) et sa largeur (width)

Ces deux constructeurs permettent d'affecter des attributs privés qui vont mettre l'instance de classe dans un état utilisable. Les deux constructeurs ont des comportements différents mais permettent au final de réaliser la responsabilité de la classe, qui est de donner la surface d'une pièce par rapport à ses longueur et largeur.

Ainsi, la sortie du programme ci-dessus donne ceci :

str
Size of the room area is 15.75 m²
Size of the room area is 10.89 m²

Comme on peut le constater, la pièce rectangulaire a une surface égale à 15.75m², ce qui est équivalent à la multiplication de 3.5 et 4.5. La pièce carrée a une surface égale à deux fois 3.3 qui est la valeur de sa largeur et de sa longueur.

Deux constructeurs différents ont permis d'initialiser un objet dans l'état où il est possible de l'utiliser tel quel. Le constructeur d'une pièce carrée nous fait gagner du temps car, au lieu d'écrire new Room(3.3, 3.3), on écrit new Room(3.3).

Comment est-ce qu'on fait pour réutiliser un constructeur dans un autre ?

↗
Surcharge de constructeur en C#
geeksforgeeks.org

Chaîner les constructeurs

Définir plusieurs constructeurs a un avantage certain : celui de permettre à un objet d'être construit de plusieurs manières. Cependant, si un constructeur est simplement l'extension d'un autre, il est inutile de réécrire un code qui existe déjà !

Notre exemple de l'étape suivante peut être repris en chaînant les constructeurs, c'est-à-dire en utilisant un constructeur déjà existant pour en fabriquer un autre.

cs
public class Room
{
    private double _width;
    private double _height;

    // The below constructor is chaining with Room(double, double)
    public Room(double size) : this(size, size)
    { }

    public Room(double height, double width)
    {
        _height = height;
        _width = width;
    }

    public double GetAreaSize()
    {
        return _height * _width;
    }
}

Pour créer une pièce carrée, on peut appeler le constructeur Room(double, double) avec deux arguments ayant exactement la même valeur. C'est exactement ce que fait le constructeur Room(double) qui assigne la même valeur à _height et 'à _width. Puisque Room(double, double) fait la même chose que Room(double), on peut chaîner Room(double, double) à Room(double) pour instancier une pièce carrée.

Afin de chaîner un constructeur à un autre, il faut ajouter, après sa déclaration et juste avant l'accolade délimitant le corps de la méthode :

  • Un double-point :

  • this

  • Et les arguments séparés par une virgule et entourés de parenthèses : (size, size)

Et c'est quoi, this ?

Je me doutais que cette question allait te traverser l'esprit. this est un mot-clé. this fait référence à l'instance courante de ta classe, c'est aussi simple que cela ! Tu verras d'autres utilisations du mot-clé this au fur et à mesure que tu avanceras. Garde en tête qu'il ne permet pas uniquement de chaîner des constructeurs entre eux, mais fait référence à l'instance courante de ta classe.

Et si je peux construire, est-ce que je peux détruire ?

↗
Chaînage de constructeurs en C#
codeproject.com

Les finaliseurs

De la même manière que l'on peut configurer le comportement de construction d'une instance de classe, on peut configurer le comportement de sa destruction.

Contrairement à ce que l'on pourrait penser, le mot qualifiant la méthode de destruction s'appelle un finaliseur. Mais on peut également l'appeler destructeur: ça fait un peu plus machiavélique ...

A quoi peut donc bien servir un finaliseur ?

Les finaliseurs sont beaucoup moins utilisés que les constructeurs et majoritairement parce que C# implémente un Garbage Collector qui va effacer de la mémoire toutes les variables n'étant plus référencées. Mais comme on ne peut pas toujours avoir confiance en l'automatique, dans certains cas, des ressources ne sont pas libérées automatiquement et il faut alors utiliser un finaliseur.

Un finaliseur peut aussi être d'une grande utilité lorsque l'on veut réaliser des actions spécifiques lors de la destruction d'un objet. Un finaliseur peut par exemple sauvegarder le contenu d'un fichier sur le disque lorsque l'objet le contenant est détruit, ou alors afficher un message lorsqu'une instance de classe est détruite pour aider au débogage.

cs
namespace Finalizers
{
    public static class Program
    {
        public static void Main()
        {
            Building building = new Building(5.89);
            System.Console.WriteLine(building.GetHeight());
        }
    } 

    public class Building
    {
        private double _height;

        public Building(double height)
        {
            _height = height;
        }

        // This is the finalizer
        ~Building()
        {
            System.Console.WriteLine("The building is destroying, for your safety stay away from it !");
        }

        public double GetHeight()
        {
            return _height;
        }

    }
}

Un destructeur a lui aussi une signature particulière :

  • Ne renvoie aucun type

  • Aucune visibilité (pas de private ou public) puisqu'il doit être appelable de n'importe où, dès lors que l'objet est détruit

  • Son nom est le nom de la classe qu'il détruit, précédé par un tilde (~)

  • Ne prend aucun argument

  • Il ne peut y en avoir qu'un seul par classe

Le code précédent affiche :

text
5.89
The building is destroying, for your safety stay away from it !

Dans l'exemple précédent, dès que l'objet Building est détruit, une phrase s'affiche à l'écran, spécifiant que le bâtiment est en train de se détruire et que les personnes alentour doivent vite se sauver.

↗
Les finaliseurs
intellitect.com

Challenge

Un métier de précision

Ta mission est de prévoir un nombre d'étages suffisant en fonction de la hauteur du bâtiment. Et, afin de fidéliser tes clients, tu veux leur proposer un programme qui le fait tout seul.

Il n'y a pas de limite concernant les interactions avec l'utilisateur. Essaie cependant de faire en sorte que les interactions soient les plus claires possible.

Critères de validation

  • Un lien vers le dépôt GitHub
  • Building possède un constructeur qui l'instancie en fonction du nombre d'étages et de la taille du bâtiment
  • Building possède un constructeur qui l'instancie en fonction du nombre d'étages uniquement. La hauteur idéale d'un étage étant de 3m, la taille du bâtiment doit être affectée en fonction (3 * nombre d'étages).
  • Une méthode GetFloorMaxSize renvoie la taille maximum que peut faire un étage du bâtiment (hauteur/nombre d'étages)
  • Une méthode GetFloorCount qui renvoie le nombre d'étages du bâtiment
  • Une méthode GetSize qui renvoie la taille du bâtiment