• Aucun résultat trouvé

Réaliser des interfaces Ada Avec une seule interface

Dans le document Apprenez à programmer avec Ada (Page 175-178)

Venons-en maintenant au code : Code : Ada

with P_Item.Unit ; use P_Item.Unit ; package I_Mounted is

type T_Mounted is interface ;

procedure Attaque_de_flanc(Attaquant : in T_Mounted ; Defenseur : in out T_Unit'class) is abstract ;

end I_Mounted ;

Et le corps du package ?

Le corps ? Quel corps ? Le type T_Mounted étant une INTERFACE, il est 100% abstrait et ses méthodes associées (comme Attaque_de_flanc()) doivent obligatoirement être abstraites. Donc ce package ne peut pas avoir de corps. De plus, il est recommandé de créer un package spécifique pour votre type interface si vous ne voulez pas que GNAT rechigne à compiler. C'est d'ailleurs pour cela que mon package s'appelle I_Mounted et non P_Mounted (I pour INTERFACE bien sûr).

Bien ! Notre interface et ses méthodes étant créées, nous pouvons revenir aux spécifications de notre type T_Mounted_Unit qui doit désormais implémenter notre interface :

Code : Ada - P_Item-Unit-Mounted.ads

With I_Mounted ; Use I_Mounted ; package P_Item.Unit.Mounted is

type T_Mounted_Unit is new T_Unit and T_Mounted with private ; overriding

procedure Attaque_de_flanc(Attaquant : in T_Mounted_Unit ; Defenseur : in out T_Unit'class) ;

private

type T_Mounted_Unit is new T_Unit and T_Mounted with null record

;

end P_Item.Unit.Mounted ;

Partie 4 : Ada : Notions avancées et Programmation Orientée Objet 175/312

Il n'est pas utile de modifier le corps de notre package puisque la procédure Attaque_de_flanc() existait déjà. Comme vous avez du vous en rendre compte, il suffit d'ajouter « AND T_Mounted » à la définition de notre type pour implémenter l'interface.

Vos types dérivés ne peuvent pas dériver d'une interface, seulement d'un type étiqueté. L'ordre de déclaration est important : d'abord, le type étiqueté père puis la ou les interfaces. Ce qui nous donne le schéma suivant : « type MonNouveauType is new TypeEtiquetéPère and InterfaceMère1 and InterfaceMère2 and... with... »

Venons-en maintenant à notre objectif principal : créer un type T_Mounted_Archer.

Code : Ada - P_Item-Unit-Archer-Mounted.ads

With I_Mounted ; Use I_Mounted ; package P_Item.Unit.Archer.Mounted is

type T_Mounted_Archer is new T_Archer_Unit and T_Mounted with private ;

overriding

procedure Attaque_de_flanc(Attaquant : in T_Mounted_Archer ; Defenseur : in out T_Unit'class) ;

private

type T_Mounted_Archer is new T_Archer_Unit and T_Mounted with null record ;

end P_Item.Unit.Archer.Mounted ;

Code : Ada - P_Item-Unit-Archer-Mounted.adb

With ada.text_IO ; Use Ada.Text_IO ; package body P_Item.Unit.Archer.Mounted is

procedure Attaque_de_flanc(Attaquant : in T_Mounted_Archer ; Defenseur : in out T_Unit'class) is

begin

Put_line(" >> Décochez vos flèches sur le flanc ouest !") ; Defenseur.vie := integer'max(0,Defenseur.vie

-Attaquant.tir*Attaquant.mov) ; end Attaque_de_flanc ; end P_Item.Unit.Archer.Mounted ;

Le code est finalement très similaire à celui des unités montées, à ceci près que vous devez écrire le corps du package cette fois.

Remarquez que j'ai changé la méthode de calcul ainsi que le texte affiché, mais vous pouvez bien-sûr écrire exactement le même code que pour les T_Mounted_Unit.

Mais quel intérêt de réécrire la même chose ? Je pensais que l'héritage nous permettait justement d'éviter cette redondance de code?

J'avoue avoir eu moi aussi du mal à comprendre cela à mes débuts. Mais le but principal des types abstraits et des interfaces n'est pas de limiter la redondance du code, mais de permettre le polymorphisme. Sans interface, nous aurions deux méthodes Attaque_de_flanc distinctes ; avec l'interface, ces deux méthodes n'en forme plus qu'une, mais polymorphe ! Souvenez-vous de nos héros : ils pouvaient changer de classe sans que le compilateur ne le sache. Il est donc important de privilégier une méthode polymorphe plutôt qu'une méthode simplement surchargée.

Avec plusieurs interfaces

Dans le même esprit, pourquoi ne pas créer une interface I_Archer ? Ainsi, les archers montés hériteraient directement de T_Unit et implémenteraient I_Mounted et I_Archer. La réalisation de l'interface I_Archer n'est pas très compliquée :

Code : Ada

with P_Item.Unit ; use P_Item.Unit ; package I_Archer is

type T_Archer is interface ;

procedure Attaque_a_distance(Attaquant : in T_Archer ; Defenseur : in out T_Unit'class) is abstract ;

end I_Archer ;

Que devient alors notre type T_Archer_Unit ? Là encore, rien de bien compliqué : Code : Ada - P_Item-Unit-Archer.ads

With I_Archer ; Use I_Archer ; package P_Item.Unit.Archer is

type T_Archer_Unit is new T_Unit and T_Archer with private ; overriding

function Init(Att, Def, Mov, Vie : Integer := 0) return T_Archer_Unit ;

function Init(Att, Def, Mov, Vie, Tir : Integer := 0) return T_Archer_Unit ;

-- Réécriture de la méthode héritée de l'interface I_Archer overriding

procedure Attaque_a_distance(Attaquant : in T_Archer_Unit ; Defenseur : in out T_Unit'class) ;

-- Réécriture de la méthode héritée de la classe T_Unit overriding

procedure Attaque(Attaquant : in T_Archer_Unit ; Defenseur : in out T_Unit'class) ;

private

type T_Archer_Unit is new T_Unit and T_Archer with record Tir : integer ;

end record ; end P_Item.Unit.Mounted ;

Mais ce qui nous intéresse vraiment, c'est notre type T_Mounted_Archer : Code : Ada - P_Item-Unit-Archer-Mounted.ads

With I_Mounted ; Use I_Mounted ; package P_Item.Unit.Archer.Mounted is

type T_Mounted_Archer is new T_Archer_Unit and T_Mounted and T_Archer with private ;

overriding

procedure Attaque_de_flanc(Attaquant : in T_Mounted_Archer ; Defenseur : in out T_Unit'class) ;

overriding

procedure Attaque_a_distance(Attaquant : in T_Mounted_Archer ; Defenseur : in out T_Unit'class) ;

private

type T_Mounted_Archer is new T_Archer_Unit and T_Mounted and T_Archer with null record ;

end P_Item.Unit.Archer.Mounted ;

Code : Ada - P_Item-Unit-Archer-Mounted.adb

With ada.text_IO ; Use Ada.Text_IO ; package body P_Item.Unit.Archer.Mounted is

procedure Attaque_de_flanc(Attaquant : in T_Mounted_Archer ; Defenseur : in out T_Unit'class) is

begin

Put_line(" >> Décochez vos flèches sur le flanc ouest !") ; Defenseur.vie := integer'max(0,Defenseur.vie

-Attaquant.tir*Attaquant.mov) ;

Partie 4 : Ada : Notions avancées et Programmation Orientée Objet 176/312

end Attaque_de_flanc ;

procedure Attaque_a_distance(Attaquant : in T_Mounted_Archer ; Defenseur : in out T_Unit'class) is

begin

Put_line(" >> Parez ... Tirez !") ;

Defenseur.vie := integer'max(0,Defenseur.vie - Attaquant.tir) ; end Attaque_a_distance ;

end P_Item.Unit.Archer.Mounted ;

Comme vous pouvez le constater, mon type T_Mounted_Archer hérite désormais ses multiples attributs du type T_Archer_Unit et de deux interfaces I_Archer et I_Mounted. Il est ainsi possible de multiplier les implémentations : pourquoi ne pas imaginer des classes implémentant 4 ou 5 interfaces ?

Interface implémentant une autre interface

Et nos artilleries ? Nous pourrions également créer une interface I_Artillerie ! Et comme les artilleries héritent des archers, notre interface I_Artillerie hériterait de I_Archer.

Attends, là. Une interface peut hériter d'une autre interface ?

Bien sûr ! Tout ce que vous avez à faire, c'est de lui adjoindre de nouvelles méthodes (abstraites bien sûr). Cela se fait très facilement. Regardez ce que cela donnerait avec nos artilleries :

Code : Ada

with I_Archer ; use I_Archer ; with P_Item.Unit ; use P_Item.Unit ; package I_Artillery is

type T_Artillery is interface and T_Archer ;

procedure Bombarde(Attaquant : in T_Artillery ; Defenseur : in out T_Unit'class) ;

end I_Artillery ;

Inutile de préciser que T_Artillery hérite de la méthode abstraite Attaque_a_distance() et qu'elle pourrait hériter de plusieurs interfaces (si l'envie vous prenait). Bien. Avant de clore ce chapitre, il est temps de faire un point sur notre diagramme de classe. Celui-ci a en effet bien changé depuis le début du chapitre précédent. Je vous fais un résumé de la situation ?

Diagramme de classe final du projet

Fatigués ? C'est normal, vous venez de lire un chapitre intense. Polymorphisme et abstraction ne sont pas des notions évidentes à appréhender. Rassurez-vous, nous en aurons bientôt terminé avec la POO. Il nous reste toutefois un dernier chapitre à aborder avant de nous faire la main sur un nouveau TP : la finalisation et les types contrôlés. Ce chapitre sera bien plus court que les précédents. Alors, encore un peu de courage !

En résumé :

Pour qu'une même méthode ait des effets différents selon les sous-classes, vous devez créer une méthode polymorphe. Il faudra alors réécrire la méthode en précisant : « OVERRIDING ».

Afin de mieux hiérarchiser vos classes, de regrouper sous une même classe différents types tout en conservant les propriétés de polymorphisme, faites appel à un type abstrait (ABSTRACT). Mais aucun objet ne peut être de type abstrait !

Pour accompagner votre type abstrait, vous pouvez créer des méthodes abstraites ou non. Les méthodes abstraites devront ensuite être réécrite, les autres seront automatiquement dérivée pour les classes filles.

En Ada, l'héritage multiple est contourné grâce aux interfaces. N'importe quelle classe peut implémenter plusieurs interfaces, ce qui permet de garantir le polymorphisme des méthodes.

Partie 4 : Ada : Notions avancées et Programmation Orientée Objet 177/312

La programmation modulaire VI : Finalisation et

Dans le document Apprenez à programmer avec Ada (Page 175-178)