C# – Héritage et interfaces

Ceci est un exemple illustrant les interfaces et l'héritage en C#. Vous pourrez comparer par vous-mêmes avec des programmes équivalents dans les langages que vous connaissez. Voir https://dotnetfiddle.net/wJ8ixM pour une version en-ligne.

L'interface que nous utiliserons ici se nomme IDessinable (car l'usage dans la plateforme .NET est d'apposer le préfixe I aux noms d'interfaces), et expose un seul service, Dessiner(), qui prend en paramètre un flux sur lequel écrire (en fait, il s'agit plus d'une interface vers un objet capable de formater du texte en vue d'une projection sur un flux, mais bon).

Le type ConsoleTypique est un type valeur, un struct, qui serait placé sur la pile s'il était instancié; cela dit, ici, nous ne l'instancierons pas puisqu'il ne contient que des membres de classe. Notez que les constantes (const) d'une classe sont nécessairement des membres de classe (en Java et en C++, les constantes d'instance sont aussi possibles).

La classe Affichable gère la politique de validation d'un symbole affichable (on évite les blancs et les caractères de contrôles). Remarquez les services de System.Char qui sont utilisés pour mettre en place la politique de validité en question. La propriété Symbole d'instances d'Affichable est publique, donc accessible aux enfants comme aux clients.

Deux cas particulier, chacun à la fois Affichable et IDessinable, sont définis. L'un représente un Carré, l'autre représente un Triangle. L'héritage d'un parent et l'implémentation d'une interface sont exprimés par la même notation, mais C# exige que la classe parent, lorsqu'elle est indiquée, précède les interfaces. Ainsi, ici,

class Carré
   : Affichable, IDessinable 
{
   // ...
}

...est légal, mais

class Carré
   : IDessinable, Affichable 
{
   // ...
}

ne le serait pas.

Remarquez la syntaxe utilisée pour invoquer, dans le constructeur de ces classes, celui de leur parent (Affichable) : un peu comme les appels à super() en Java, le fait que seul l'héritage simple d'implémentation soit permis en C# a pour conséquence qu'un mot clé, base, puisse être utilisé de manière homogène pour référer au parent immédiat. La forme que prend l'appel au constructeur du parent s'apparente, elle, à celle qu'on trouve en C++.

Dans la classe Program, la méthode de classe Afficher() reçoit un IDessinable et invoque, par polymorphisme, la méthode Dessiner() d'un objet du type véritablement référé.

 

using System;
using System.IO;
using static System.Math;

Afficher(new Carré(5, '*'));
Afficher(new Triangle(7, '#'));

static void Afficher(IDessinable d)
{
   TextWriter scribe = new StringWriter();
   d.Dessiner(scribe);
   Console.Write(scribe);
}

// exemple d'interface
public interface IDessinable
{
   void Dessiner(System.IO.TextWriter flux);
}

public static class ConsoleTypique
{
   public const int LARGEUR = 80,
                    HAUTEUR = 25;
   public static bool EstLargeurValide(int val) =>
      0 <= val && val < LARGEUR;
   public static bool EstHauteurValide(int val) =>
      0 <= val && val < HAUTEUR;
}

// exemple d'une classe de base simple
public class SymboleNonAffichableException : Exception {}
public class Affichable
{
   private char symbole;
   public char Symbole
   {
      get => symbole;
      set
      {
         if (Char.IsControl(value) ||
             Char.IsWhiteSpace(value))
            throw new SymboleNonAffichableException();
         symbole = value;
      }
   }
   public Affichable(char sym)
   {
      Symbole = sym;
   }
}
public class TailleInvalideException : Exception {}
// exemple de classe implémentant une interface et dérivant d'un parent
public class Carré
   : Affichable, IDessinable // héritage (classe avant interface)
{
   private int taille;
   private static bool EstTailleValide(int n) =>
      n > 0 &&
      n < Min(ConsoleTypique.LARGEUR, ConsoleTypique.HAUTEUR);
   public Carré(int n, char symbole)
      : base(symbole) // constructeur du parent
   {
      if (!EstTailleValide(n))
         throw new TailleInvalideException();
      taille = n;
   }
   public void Dessiner(TextWriter flux)
   {
      for (int i = 1; i <= taille; ++i)
      {
         for (int j = 1; j <= taille; ++j)
            flux.Write(Symbole);
         flux.WriteLine();
      }
   }
}
// autre exemple de classe implémentant une interface et dérivant d'un parent
public class Triangle
   : Affichable, IDessinable // héritage (classe avant interface)
{
   private int hauteur;
   private static bool EstHauteurValide(int n) =>
      ConsoleTypique.EstHauteurValide(n);
   public Triangle(int n, char symbole)
      : base(symbole)
   {
      if (!EstHauteurValide(hauteur))
         throw new TailleInvalideException();
      hauteur = n;
   }
   public void Dessiner(TextWriter flux)
   {
      for (int i = 1; i <= hauteur; ++i)
      {
         for (int j = 1; j <= i; ++j)
            flux.Write(Symbole);
         flux.WriteLine();
      }
   }
}

Valid XHTML 1.0 Transitional

CSS Valide !