• Aucun résultat trouvé

Une fois n’est pas coutume, plutôt que de commencer par étudier le plus simple, nous allons étudier le plus logique puis nous reviendrons sur le plus simple.

C’est-à-dire que nous allons pousser un peu plus loin la comparaison en nous servant des interfaces et nous reviendrons ensuite sur comment créer une interface.

Nous avons donc dit que les interfaces étaient un contrat que s’engageait à respecter un objet. C’est tout à fait ce dont on a besoin ici. Notre objet doit s’engager à fonctionner pour tous les types de comparaison. Il doit être comparable. Mais comparable ne veut pas forcément dire « égal », nous devrions être aussi capables d’indiquer si un objet est supérieur à un autre.

Pourquoi ? Imaginons que nous possédions un tableau de voitures et que nous souhaitions le trier comme on a vu dans un chapitre précédent. Pour les entiers c’était une opération plutôt simple, avec la méthode Array.Sort() ils étaient automatiquement triés par ordre croissant. Mais dans notre cas, comment un tableau sera capable de trier nos voitures ? Nous voici donc en présence d’un cas concret d’utilisation des interfaces. L’interface IComparable permet de définir un contrat de méthodes destinées à la prise en charge de la comparaison entre deux instances d’un objet.

Une fois ces méthodes implémentées, nous serons certains que nos objets seront comparables correctement. Pour cela, nous allons faire en sorte que notre classe « implémente » l’interface.

Reprenons notre classe Voiture avec uniquement ses propriétés et faisons lui implémenter l’interface IComparable. Pour implémenter une interface, on utilisera la même syntaxe que pour hériter d’une classe, c'est-à-dire qu’on utilisera les deux points suivis du type de l’interface. Ce qui donne :

Code : C#

public class Voiture : IComparable {

public string Couleur { get; set; } public string Marque { get; set; }

public int Vitesse { get; set; } }

Il est conventionnel de démarrer le nom d’une interface par un I majuscule. C’est le cas de toutes les interfaces du framework .NET.

Si vous tentez de compiler ce code, vous aurez le message d’erreur suivant :

Le compilateur nous rappelle à l’ordre : nous annonçons que nous souhaitons respecter le contrat de comparaison, sauf que nous n’avons pas la méthode adéquate !

Eh oui, le contrat indique ce que nous nous engageons à faire mais pas la façon de le faire. L’implémentation de la méthode est à notre charge.

Toujours dans l’optique de simplifier la tâche du développeur, Visual C# Express nous aide pour implémenter les méthodes d’un contrat. Faites un clic droit sur l’interface et choisissez dans le menu contextuel « Implémenter l’interface » et « Implémenter l’interface » :

Visual C# Express nous génère le code suivant : Code : C#

public class Voiture : IComparable {

public string Couleur { get; set; } public string Marque { get; set; } public int Vitesse { get; set; } public int CompareTo(object obj) {

throw new NotImplementedException(); }

}

C'est-à-dire la signature de la méthode qu’il nous manque pour respecter le contrat de l’interface et un contenu que nous ne comprenons pas pour l’instant. Nous y reviendrons plus tard, pour l’instant, vous n’avez qu’à supprimer la ligne :

throw new NotImplementedException();

Il ne reste plus qu’à écrire le code de la méthode.

Pour ce faire, il faut définir un critère de tri. Trier des voitures n’a pas trop de sens, aussi nous dirons que nous souhaitons les trier suivant leurs vitesses.

Pour respecter correctement le contrat, nous devons respecter la règle suivante qui se trouve dans la documentation de la méthode de l’interface :

Si une voiture est inférieure à une autre, alors nous devons renvoyer une valeur inférieure à 0, disons -1. Si elle est égale, alors nous devons renvoyer 0.

Enfin, si elle est supérieure, nous devons renvoyer une valeur supérieure à 0, disons 1.

Ce qui donne : Code : C#

public int CompareTo(object obj) {

Voiture voiture = (Voiture)obj; if (this.Vitesse < voiture.Vitesse) return -1;

if (this.Vitesse > voiture.Vitesse) return 1;

return 0; }

À noter que la comparaison s’effectue entre l’objet courant et un objet qui lui est passé en paramètres. Pour que ce soit un peu plus clair, j’ai utilisé le mot-clé this qui permet de bien identifier l’objet courant et l’objet passé en paramètres.

Comme il est facultatif, nous pouvons le supprimer.

Vous aurez également remarqué que j’utilise un cast explicite avant de comparer. Ceci permet de renvoyer une erreur si jamais l’objet à comparer n’est pas du bon type. En effet, que devrais-je renvoyer si jamais l’objet qu’on me passe n’est pas une voiture ? Une erreur, c’est très bien.

Le code est suffisamment explicite pour que nous comprenions facilement ce que l’on doit faire : comparer les vitesses. Il est possible de simplifier grandement le code car pour comparer nos deux voitures, nous effectuons la comparaison sur la valeur d’un entier, ce qui est plutôt trivial.

D’autant plus que l’entier, en bon objet comparable, possède également la méthode CompareTo(). Ce qui fait qu’il est possible d’écrire notre méthode de comparaison de cette façon :

Code : C#

public int CompareTo(object obj) {

Voiture voiture = (Voiture)obj;

return Vitesse.CompareTo(voiture.Vitesse); }

En effet, Vitesse étant un type intégré, il implémente déjà correctement la comparaison. C’est d’ailleurs pour ça que le tableau d’entier que nous avons vu précédemment a été capable de se trier facilement.

Code : C#

Voiture[] voitures = new Voiture[] { new Voiture { Vitesse = 100 },

new Voiture { Vitesse = 40 }, new Voiture { Vitesse = 10 }, new

Voiture { Vitesse = 40 }, new Voiture { Vitesse = 50 } }; Array.Sort(voitures);

foreach (Voiture v in voitures) {

Console.WriteLine(v.Vitesse); }

Ce qui donne :

Voilà pour le tri, mais si je peux me permettre, je trouve que ce code-là est un peu moche. J’y reviendrai un peu plus tard.

Nous avons donc implémenté notre première interface. Finalement, ce n’était pas si compliqué. Voyons à présent comment créer nos propres interfaces.

Une interface se définit en C# comme une classe, sauf qu’on utilise le mot-clé interface à la place de class.

En tant que débutant, vous aurez rarement besoin de créer des interfaces. Cependant, il est utile de savoir le faire. Par contre, il sera beaucoup plus fréquent que vos classes implémentent des interfaces existantes du framework .NET, comme nous venons de le faire.

Voyons à présent comment créer une interface et examinons le code suivant : Code : C#

public interface IVolant {

int NombrePropulseurs { get; set; } void Voler();

la convention qui fait que les interfaces doivent commencer par un i majuscule.

Nous définissons ici une interface IVolant qui possède une propriété de type int et une méthode Voler() qui ne renvoie rien.

Voilà, c’est tout simple.

Nous avons créé une interface. Les objets qui choisiront d’implémenter cette interface seront obligés d’avoir une propriété entière NombrePropulseurs et une méthode Voler() qui ne renvoie rien. Rappelez-vous que l'interface ne contient que le contrat et aucune implémentation. C'est-à-dire que nous ne verrons jamais de corps de méthode dans une interface ni de

variables membres ; uniquement des méthodes et des propriétés. Un contrat.

Notez quand même qu’il ne faut pas définir de visibilité sur les membres d’une interface. Nous serons obligés de définir les visibilités en public sur les objets implémentant l’interface.

Créons désormais deux objets Avion et Oiseau qui implémentent cette interface, ce qui donne : Code : C#

public class Oiseau : IVolant {

public int NombrePropulseurs { get; set; } public void Voler()

{

Console.WriteLine("Je vole grâce à " + NombrePropulseurs + " ailes");

} }

public class Avion : IVolant {

public int NombrePropulseurs { get; set; } public void Voler()

{

Console.WriteLine("Je vole grâce à " + NombrePropulseurs + " moteurs");

} }

Grâce à ce contrat, nous savons maintenant que n’importe lequel de ces objets saura voler.

Il est possible de traiter ces objets comme des objets volants, un peu comme ce que nous avions fait avec les classes mères, en utilisant l’interface comme type pour la variable. Par exemple :

Code : C#

IVolant oiseau = new Oiseau { NombrePropulseurs = 2 }; oiseau.Voler();

Nous instancions vraiment un objet Oiseau, mais nous le manipulons en tant que IVolant.

Un des intérêts dans ce cas sera de pouvoir manipuler des objets qui partagent un comportement de la même façon : Code : C#

Oiseau oiseau = new Oiseau { NombrePropulseurs = 2 }; Avion avion = new Avion { NombrePropulseurs = 4 };

List<IVolant> volants = new List<IVolant> { oiseau, avion };

{

volant.Voler(); }

Ce qui produira :

Grâce à l’interface, nous avons pu mettre dans une même liste des objets différents, qui n’héritent pas entre eux mais qui partagent une même interface, c'est-à-dire un même comportement : IVolant. Pour accéder à ces objets, nous devrons utiliser leurs interfaces.

Il sera possible quand même de caster nos IVolant en Avion ou en Oiseau, si jamais nous souhaitons rajouter une propriété propre à l’avion.

Par exemple je rajoute une propriété NomDuCommandant à mon avion mais qui ne fait pas partie de l’interface : Code : C#

public class Avion : IVolant {

public int NombrePropulseurs { get; set; } public string NomDuCommandant { get; set; } public void Voler()

{

Console.WriteLine("Je vole grâce à " + NombrePropulseurs + " moteurs");

} }

Cela veut dire que l’objet Avion pourra affecter un nom de commandant mais qu’il ne sera pas possible d’y accéder par l’interface :

Code : C#

IVolant avion = new Avion { NombrePropulseurs = 4, NomDuCommandant = "Nico" };

L’erreur de compilation nous indique que IVolant ne possède pas de définition pour NomDuCommandant. Ce qui est vrai ! Pour accéder au nom du commandant, nous pourrons tenter de caster nos IVolant en Avion. Si le cast est valide, alors nous pourrons accéder à notre propriété :

Code : C#

Oiseau oiseau = new Oiseau { NombrePropulseurs = 2 };

Avion avion = new Avion { NombrePropulseurs = 4, NomDuCommandant = "Nico" };

List<IVolant> volants = new List<IVolant> { oiseau, avion };

foreach (IVolant volant in volants) {

volant.Voler();

Avion a = volant as Avion; if (a != null)

{

Console.WriteLine(a.NomDuCommandant); }

}

Voilà, c’est tout simple et ça ressemble un peu à ce qu’on a déjà vu.

Note : lorsque nous avons abordé la boucle foreach, j’ai dit qu’elle nous servait à parcourir des éléments «

énumérables ». En disant ça, je disais en fait que la boucle foreach fonctionne avec tous les types qui implémentent l’interface IEnumerable. Maintenant que vous savez ce qu’est une interface, vous comprenez mieux ce à quoi je faisais vraiment référence.

Il faut également noter que les interfaces peuvent hériter entre elles, comme c'est le cas avec les objets.

C'est-à-dire que je vais pouvoir déclarer une interface IVolantMotorise qui hérite de l'interface IVolant. Code : C#

public interface IVolant {

int NombrePropulseurs { get; set; } void Voler();

}

public interface IVolantMotorise : IVolant {

void DemarrerLeMoteur(); }

Ainsi, ma classe Avion qui implémentera IVolantMotorise devra obligatoirement implémenter les méthodes/propriétés de IVolant ainsi que la méthode de IVolantMotorise :

Code : C#

public class Avion : IVolantMotorise {

public void DemarrerLeMoteur() {

}

public void Voler() {

} }

Enfin, et nous nous arrêterons là pour les interfaces, il est possible pour une classe d'implémenter plusieurs interfaces. Il suffira pour cela de séparer les interfaces par une virgule et d'implémenter bien sûr tout ce qu'il faut derrière. Par exemple :

Code : C#

public interface IVolant {

void Voler(); }

public interface IRoulant {

void Rouler(); }

public class Avion : IVolant, IRoulant {

public void Voler() {

Console.WriteLine("Je vole"); }

public void Rouler() {

Console.WriteLine("Je Roule"); }

}