• 20 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

J'ai tout compris !

Mis à jour le 10/03/2017

TP : Créez un simulateur météo

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Bienvenue dans le dernier TP de cette partie.
Tenez bon, après on change de domaine pour aborder d'autres notions.
Dans ce TP, nous allons pratiquer les événements. Le but est de savoir en créer un et de pouvoir s'y abonner pour être notifié d'une information.

Vous êtes prêts ? Alors c'est parti ! :)

Instructions pour réaliser le TP

Nous allons réaliser ici un mini simulateur de météo qui sera utilisé par un statisticien afin d’en faire des statistiques (logique :p ). Bon, c’est le contexte, mais c’est juste un exemple. N’espérez pas non plus réaliser un vrai simulateur météo dans ce TP. :)

Bref.
Nous devons créer un simulateur de météo. Lorsque celui-ci est démarré, il génère autant de nombres aléatoires que demandé, des nombres entre 0 et 100.
Si le nombre aléatoire est inférieur à 5, alors cela veut dire que le temps est au soleil.
S’il est supérieur ou égal à 5 et inférieur à 50, alors nous aurons des nuages.
S’il est supérieur ou égal à 50 et inférieur à 90, alors nous aurons de la pluie. Sinon, nous aurons de l’orage.

Un événement sera levé à chaque changement de temps. Le but de notre statisticien est de s’abonner aux événements du simulateur météo afin de compter le nombre de fois où il a fait soleil et le nombre de fois où le temps a changé. Il affichera ensuite son rapport en indiquant ces deux résultats et le pourcentage de fois où il a fait soleil. (Je veux bien que ce pourcentage soit un entier).

C’est tout pour l’énoncé. Maintenant, vous avez assez de connaissances pour que je ne détaille pas plus.
Bon courage !

Correction

Allez, c’est parti pour la correction.
Tout d’abord, nous devons créer notre simulateur météo. Il sera représenté par une classe :

public class SimulateurMeteo
{
}

Nous aurons également besoin de quelque chose pour représenter le temps, soleil, nuage, pluie et orage. Une énumération semble appropriée :

public enum Temps
{
    Soleil,
    Nuage,
    Pluie,
    Orage
}

Enfin, nous aurons notre statisticien :

public class Statisticien
{
}

Commençons par le simulateur de météo. Nous aurons besoin de plusieurs variables membres afin de stocker notre générateur de nombre aléatoires, le dernier temps qu’il a fait et le nombre de répétitions.
Le nombre de répétitions pourra être un paramètre du constructeur :

public class SimulateurMeteo
{
    private Temps? ancienTemps;
    private int nombreDeRepetitions;
    private Random random;

    public SimulateurMeteo(int nombre)
    {
        random = new Random();
        ancienTemps = null;
        nombreDeRepetitions = nombre;
    }
}

Étant donné que nous allons déterminer plusieurs nombres aléatoires, il est pertinent de ne pas ré-allouer à chaque fois le générateur de nombre aléatoire. C’est pour cela qu’on le crée une unique fois dans le constructeur de la classe.
Créons désormais une méthode permettant de démarrer le simulateur et codons les règles métiers du simulateur :

public class SimulateurMeteo
{
    //[…] Code supprimé pour plus de clarté […]

    public void Demarrer()
    {
        for (int i = 0; i < nombreDeRepetitions; i++)
        {
            int valeur = random.Next(0, 100);
            if (valeur < 5)
                GererTemps(Temps.Soleil);
            else
            {
                if (valeur < 50)
                    GererTemps(Temps.Nuage);
                else
                {
                    if (valeur < 90)
                        GererTemps(Temps.Pluie);
                    else
                        GererTemps(Temps.Orage);
                }
            }
        }
    }
}

C’est très simple, on boucle sur le nombre de répétitions. Un nombre aléatoire est déterminé à chaque itération. La méthode GererTemps prend en paramètre le temps déterminé à partir du nombre aléatoire.
C’est cette méthode GererTemps qui aura pour but de lever un événement quand le temps change :

public class SimulateurMeteo
{
    public delegate void IlFaitBeauDelegate(Temps temps);
    public event IlFaitBeauDelegate QuandLeTempsChange;

    // […] Code supprimé pour plus de clarté […]

    private void GererTemps(Temps temps)
    {
        if (ancienTemps.HasValue && ancienTemps.Value != temps && QuandLeTempsChange != null)
            QuandLeTempsChange(temps);
        ancienTemps = temps;
    }
}

Ici, j’ai choisi de créer un seul événement quand le temps change et de lui indiquer le temps qu’il fait en paramètres.
Nous avons donc besoin d’un délégué qui prend un Temps en paramètres et qui ne renvoie rien. (C’est d’ailleurs souvent le cas des événements). Puis nous avons besoin d’un événement du type du délégué.
Ensuite, si le temps a changé et que quelqu’un s’est abonné à l’événement, alors nous levons l’événement.

Il ne reste plus qu’à remplir notre classe Statisticien. Cette classe a besoin de travailler sur une instance de la classe SimulateurMeteo, nous pouvons donc lui en passer une dans les paramètres du constructeur. Nous utiliserons également des variables membres privées permettant de stocker ses analyses :

public class Statisticien
{
    private SimulateurMeteo simulateurMeteo;
    private int nombreDeFoisOuLeTempsAChange;
    private int nombreDeFoisOuIlAFaitSoleil;

    public Statisticien(SimulateurMeteo simulateur)
    {
        simulateurMeteo = simulateur;
        nombreDeFoisOuLeTempsAChange = 0;
        nombreDeFoisOuIlAFaitSoleil = 0;
    }

    public void DemarrerAnalyse()
    {
        simulateurMeteo.QuandLeTempsChange += simulateurMeteo_QuandLeTempsChange;
        simulateurMeteo.Demarrer();
    }

    public void AfficherRapport()
    {
        Console.WriteLine("Nombre de fois où le temps a changé : " + nombreDeFoisOuLeTempsAChange);
        Console.WriteLine("Nombre de fois où il a fait soleil : " + nombreDeFoisOuIlAFaitSoleil);
        Console.WriteLine("Pourcentage de beau temps : " + nombreDeFoisOuIlAFaitSoleil * 100 / nombreDeFoisOuLeTempsAChange + " %");
    }

    private void simulateurMeteo_QuandLeTempsChange(Temps temps)
    {
        if (temps == Temps.Soleil)
            nombreDeFoisOuIlAFaitSoleil++;
        nombreDeFoisOuLeTempsAChange++;
    }
}

Notons que dans la méthode DemarrerAnalyse, nous nous abonnons à l’événement de changement de temps. La méthode qui est appelée lors de la notification est très simple, elle incrémente les compteurs.
Enfin, l’affichage du rapport est trivial. Ici, comme nous n’avons que des entiers, la division produira un entier également.
Il ne reste plus qu’à faire fonctionner nos objets :

class Program
{
    static void Main(string[] args)
    {
        SimulateurMeteo simulateurMeteo = new SimulateurMeteo(1000);
        Statisticien statisticien = new Statisticien(simulateurMeteo);
        statisticien.DemarrerAnalyse();
        statisticien.AfficherRapport();
    }
}

Ici, je travaille sur 1000 répétitions.
Lorsque j’exécute l’application, j’obtiens :

Image utilisateur

Évidemment, vu que nous travaillons avec des nombres aléatoires, chacun aura un résultat différent.

Et voilà, c’est terminé pour ce TP. Notre application est fonctionnelle …
Terminé ? mmmhhh … pas tout à fait, allons un peu plus loin.

Aller plus loin

En l’état, ce code est fonctionnel. C’est parfait. Mais que se passe-t-il si nous démarrons plusieurs fois l’analyse ?
Nous pouvons essayer :

static void Main(string[] args)
{
    SimulateurMeteo simulateurMeteo = new SimulateurMeteo(1000);
    Statisticien statisticien = new Statisticien(simulateurMeteo);
    statisticien.DemarrerAnalyse();
    statisticien.AfficherRapport();

    statisticien.DemarrerAnalyse();
    statisticien.AfficherRapport();

    statisticien.DemarrerAnalyse();
    statisticien.AfficherRapport();
}

Nous obtiendrons quelque chose du genre :

Image utilisateur

Les valeurs augmentent … alors qu’elles ne devraient pas.
Eh oui, nous ne réinitialisons pas les entiers permettant de stocker les statistiques. Ce n’est pas forcément un bug ici, comme je n’avais rien dit dans l’énoncé, il peut paraitre pertinent de continuer à incrémenter ces valeurs, comme ça, je peux travailler sur une moyenne.
Bon, disons que nous souhaitons les réinitialiser à chaque fois, il suffit de remettre à zéro les compteurs dans la méthode :

public void DemarrerAnalyse()
{
    nombreDeFoisOuLeTempsAChange = 0;
    nombreDeFoisOuIlAFaitSoleil = 0;
    simulateurMeteo.QuandLeTempsChange += simulateurMeteo_QuandLeTempsChange;
    simulateurMeteo.Demarrer();
}

Cependant, les compteurs augmentent toujours, moins vite, mais quand même !
Je suis sûr que vous l’avez deviné et que vous n’avez-vous-même pas fait l’erreur. En fait, cela vient de l’abonnement à l’événement. Chaque fois que nous démarrons l’analyse, nous nous réabonnons à l’événement. Comme l’événement est multidiffusion, nous rajoutons en fait à chaque fois un appel à la méthode avec le +=. Ce qui veut dire qu’à la deuxième fois, nous appellerons la méthode deux fois, ce qui fera doubler les résultats. À la troisième fois, ils triplent …
Nous avons donc une erreur. Soit il faut s’abonner une seule fois à l’événement, par exemple dans le constructeur, soit nous pouvons nous désabonner à la fin de l’analyse, quand ce n’est plus utile de recevoir l’événement.
C’est cela que je souhaite montrer. Il suffit de faire :

public void DemarrerAnalyse()
{
    nombreDeFoisOuLeTempsAChange = 0;
    nombreDeFoisOuIlAFaitSoleil = 0;
    simulateurMeteo.QuandLeTempsChange += simulateurMeteo_QuandLeTempsChange;
    simulateurMeteo.Demarrer();
    simulateurMeteo.QuandLeTempsChange -= simulateurMeteo_QuandLeTempsChange;
}

On utilise l’opérateur -= pour enlever la méthode du délégué.

Cela permet d’éviter d’encombrer la mémoire qui ne saurait pas forcément se libérer toute seule. Je n’en dis pas plus car ceci est un concept avancé de gestion de mémoire. Gardez seulement à l’esprit que si on a l’opportunité de se désabonner d’un événement, il faut le faire.

Enfin, nous pouvons simplifier notre code en ne créant pas notre délégué. Effectivement, dans la mesure où celui-ci possède un seul paramètre, il est possible de le remplacer par le délégué Action<T>.
Il faut juste supprimer la déclaration du délégué et remplacer la déclaration de l’événement par :

public class SimulateurMeteo
{
    public event Action<Temps> QuandLeTempsChange;
}

Voilà pour ce TP sur les événements. Il nous a permis de nous entrainer un peu sur les événements et de continuer à nous entrainer sur la modélisation d’applications en utilisant la POO.
Pas très compliqué en soit, mais on peut vite se rendre compte qu’une application peut fonctionner dans un cas, mais avoir un comportement inadapté dans un autre cas.
D’une manière générale, il est important de retenir qu’il faut se désabonner d’un événement quand cela est possible.

Ouf, c’est enfin fini pour ce chapitre.

Comme vous avez pu le constater, la programmation orientée objet appliquée au C# est un vaste domaine. Et encore, je n’ai pas tout montré pour conserver une taille raisonnable.
Nous avons pu voir pas mal de choses dont les concepts de la programmation orientée objet et comment les utiliser en C#. Nous avons également vu que les génériques étaient un élément important du C#.
Puis nous avons terminé le chapitre en étudiant les événements et les exceptions qui sont des éléments incontournables pour réaliser une vraie application.

Il y a beaucoup de domaines que je n’ai pas traité, n’hésitez pas à être curieux et à aller farfouiller dans la documentation ou sur internet.

Exemple de certificat de réussite
Exemple de certificat de réussite