Une interface est un ensemble de prototypes de méthodes ou de propriétés qui forme un contrat. Une classe qui décide d'implémenter une interface s'engage à fournir une implémentation de toutes les méthodes définies dans l'interface. C'est le compilateur qui vérifie cette implémentation.
Voici par exemple la définition de l'interface System.Collections.Ienumerator :
// from module 'c:\winnt\microsoft.net\framework\v1.0.2914\mscorlib.dll'
public interface System.Collections.IEnumerator {
// Properties
object Current { get; } // Methods
bool MoveNext(); void Reset();
} // end of System.Collections.IEnumerator
Toute classe implémentant cette interface sera déclarée comme public class C : IEnumerator{
...
object Current{ get {...}} bool MoveNext{...} void Reset(){...} }
La propriété Current et les méthodes MoveNext et Reset devront être définies dans la classe C. Considérons le code suivant :
using System;
public struct élève{
public string nom;
public double note;
// constructeur
public élève(string NOM, double NOTE){ nom=NOM;
note=NOTE; }//constructeur
}//élève
public class notes{
// attribut
protected string matière;
protected élève[] élèves;
// constructeur
public notes (string MATIERE, élève[] ELEVES){ // mémorisation élèves & matière
matière=MATIERE; élèves=ELEVES; }//notes
// ToString
public override string ToString(){
string valeur="matière="+matière +", notes=("; int i;
// on concatène toutes les notes
for (i=0;i<élèves.Length-1;i++){ valeur+="["+élèves[i].nom+","+élèves[i].note+"],"; }; //dernière note if(élèves.Length!=0){ valeur+="["+élèves[i].nom+","+élèves[i].note+"]";} valeur+=")"; // fin return valeur; }//ToString }//classe
La classe notes rassemble les notes d'une classe dans une matière :
public class notes{
// attribut
protected string matière; protected élève[] élèves;
Classes, Structures, Interfaces 59
Les attributs sont déclarés protected pour être accessibles d'une classe dérivée. Le type élève est une structure mémorisant le nom de l'élève et sa note dans la matière :
public struct élève{ public string nom; public double note;
Nous décidons de dériver cette classe notes dans une classe notesStats qui aurait deux attributs supplémentaires, la moyenne et l'écart-type des notes :
public class notesStats : notes, Istats {
// attribut
private double _moyenne;
private double _écartType;
La classe notesStats implémente l'interface Istats suivante :
public interface Istats{ double moyenne(); double écartType(); }//
Cela signifie que la classe notesStats doit avoir deux méthodes appelées moyenne et écartType avec la signature indiquée dans l'interface Istats. La classe notesStats est la suivante :
public class notesStats : notes, Istats {
// attribut
private double _moyenne;
private double _écartType;
// constructeur
public notesStats (string MATIERE, élève[] ELEVES): base(MATIERE, ELEVES){ // calcul moyenne des notes
double somme=0;
for (int i=0;i<élèves.Length;i++){ somme+=élèves[i].note; } if(élèves.Length!=0) _moyenne=somme/élèves.Length; else _moyenne=-1; // écart-type double carrés=0;
for (int i=0;i<élèves.Length;i++){
carrés+=Math.Pow((élèves[i].note-_moyenne),2); }//for if(élèves.Length!=0) _écartType=Math.Sqrt(carrés/élèves.Length); else _écartType=-1; }//constructeur // ToString
public override string ToString(){
return base.ToString()+",moyenne="+_moyenne+",écart-type="+_écartType; }//ToString
// méthodes de l'interface Istats public double moyenne(){
// rend la moyenne des notes
return _moyenne; }//moyenne
public double écartType(){ // rend l'écart-type
return _écartType; }//écartType
}//classe
La moyenne _moyenne et l'écart-type _ecartType sont calculés dès la construction de l'objet. Aussi les méthodes moyenne et écartType n'ont-elles qu'à rendre la valeur des attributs _moyenne et _ecartType. Les deux méthodes rendent -1 si le tableau des élèves est vide. La classe de test suivante :
// classe de test
public class test{
public static void Main(){ // qqs élèves & notes
élève[] ELEVES=new élève[] { new élève("paul",14),new élève("nicole",16), new élève("jacques",18)}; // qu'on enregistre dans un objet notes
notes anglais=new notes("anglais",ELEVES); // et qu'on affiche
Console.Out.WriteLine(""+anglais); // idem avec moyenne et écart-type anglais=new notesStats("anglais",ELEVES); Console.Out.WriteLine(""+anglais); }
Classes, Structures, Interfaces 60 donne les résultats :
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18])
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18]),moyenne=16,écart-type=1,63299316185545
La classe notesStats aurait très bien pu implémenter les méthodes moyenne et écartType pour elle-même sans indiquer qu'elle implémentait l'interface Istats. Quel est donc l'intérêt des interfaces ? C'est le suivant : une fonction peut admettre pour paramètre une donnée ayant le type d'une interface I. Tout objet d'une classe C implémentant l'interface I pourra alors être paramètre de cette fonction. Considérons l'exemple suivant :
using System;
// une interface Iexemple public interface Iexemple{
int ajouter(int i,int j);
int soustraire(int i,int j); }
public class classe1: Iexemple{
public int ajouter(int a, int b){ return a+b+10;
}
public int soustraire(int a, int b){ return a-b+20;
} }//classe
public class classe2: Iexemple{
public int ajouter(int a, int b){ return a+b+100;
}
public int soustraire(int a, int b){ return a-b+200;
} }//classe
L'interface Iexemple définit deux méthodes ajouter et soustraire. Les classes classe1 et classe2 implémentent cette interface. On remarquera que ces classes ne font rien d'autre ceci par souci de simplification de l'exemple. Maintenant considérons l'exemple suivant :
// classe de test
public class test{
// une fonction statique
private static void calculer(int i, int j, Iexemple inter){ Console.Out.WriteLine(inter.ajouter(i,j));
Console.Out.WriteLine(inter.soustraire(i,j)); }//calculer
// la fonction Main
public static void Main(){
// création de deux objets classe1 et classe2 classe1 c1=new classe1();
classe2 c2=new classe2();
// appels de la fonction statique calculer calculer(4,3,c1);
calculer(14,13,c2); }//Main
}//classe test
La fonction statique calculer admet pour paramètre un élément de type Iexemple. Elle pourra donc recevoir pour ce paramètre aussi bien un objet de type classe1 que de type classe2. C'est ce qui est fait dans la fonction Main avec les résultats suivants :
17 21 127 201
On voit donc qu'on a là une propriété proche du polymorphisme vu pour les classes. Si donc un ensemble de classes Ci non liées entre-elles par héritage (donc on ne peut utiliser le polymorphisme de l'héritage) présentent un ensemble de méthodes de même signature, il peut être intéressant de regrouper ces méthodes dans une interface I dont hériteraient toutes les classes concernées. Des instances de ces classes Ci peuvent alors être utilisées comme paramètres de fonctions admettant un paramètre de type I, c.a.d. des fonctions n'utilisant que les méthodes des objets Ci définies dans l'interface I et non les attributs et méthodes particuliers des différentes classes Ci.
Notons enfin que l'héritage d'interfaces peut être multiple, c.a.d. qu'on peut écrire public class classeDérivée:classeDeBase,i1,i2,..,in{
Classes, Structures, Interfaces 61 }
où les ij sont des interfaces.