Programmation pour le TAL

Les interfaces

Lecture : chapitre 8, section 12 du livre "Programmer en Java".

Description

Une interface est la donnée d'un ensemble (potentiellement vide) de signatures de méthodes. La notion d'interface se rapproche de celle de classe abstraite, dans laquelle il n'y aurait pas de membre et uniquement des méthodes abstraites (jamais d'implémentation).
Une interface matérialise le "comportement possible" d'une classe, i.e. quelque chose qu'une classe peut faire (ou qu'on peut faire faire à une classe). Une classe peut implémenter une interface. Cette notion n'est pas exclusive avec celle de dérivation : une classe peut dériver d'une classe-mère et implémenter une interface. Contrairement à ce qui se passe (en Java) pour l'héritage où une classe ne peut dériver que d'une seule classe-mère, une classe peut implémenter plusieurs interfaces.

Exemple d'utilisation

Dans le cas des objets graphiques, la classe GeomObject est une classe abstraite qui regroupe les caractéristiques communes des différents types d'objets géométriques : des membres (e.g. le nom de l'objet et sa couleur) et des méthodes (e.g. toString(), getMaxX(), getMinX(), etc.).
Mais beaucoup d'objets partagent certaines caractéristiques, comme le fait de pouvoir être déplacés (e.g. via une translation)... mais pas d'autres (comme la classe FixedPoint).
Dans la classe Group, comment implémenter la méthode translate() qui consiste à opérer une translation sur tous les objets... qui sont déplaçables (et seulement ceux-là) ?
Une solution consiste à itérer sur la liste des objets et tester la classe de l'objet courant. Pour toutes les classes qui disposent d'une méthode translate(), effectuer un transtypage :

	public void translate1 (int dx, int dy)
	{
		Iterator goIt = objList.iterator();
		
		while (goIt.hasNext())
		{
			GeomObject currentObj = goIt.next(); 
			if (currentObj instanceof MobilePoint)
				((MobilePoint) currentObj).translate(dx, dy);
			else if (currentObj instanceof Circle)
				((Circle) currentObj).translate(dx, dy);
			else if (currentObj instanceof Segment)
				((Segment) currentObj).translate(dx, dy);
			// ...
		}
	}

Problème : lorsqu'on ajoute de nouvelles classes qui dérivent de GeomObject, le code de la méthode devient incomplet.
Une solution consiste à définir une interface qui matérialise le comportement "peut subir une translation" et faire en sorte que les classes concernées implémentent la méthode correspondant à la signature est donnée dans l'interface. En l'occurrence, il s'agira de la méthode void translate (int, int) que nous avons déjà implémentée pour chacune des classes "mobiles". Comme une classe, la définition d'une interface en Java se fait dans un fichier qui porte le nom de l'interface.

Fichier Mobile.java
    public interface Mobile
    {
	public void translate (int dx, int dy);
    }
    
Fichier Segment.java
    public class Segment extends GeomObject implements Mobile
    {
        public void translate (int dx, int dy)
        {
            getEndPoint1().translate (dx, dy);
            getEndPoint2().translate (dx, dy);
        }
    }
    
Fichier Circle.java
    public class Circle extends GeomObject implements Mobile
    {
        public void translate (int dx, int dy)
        {
            getCenter().translate (dx, dy);
        }
    }
    

Dans la classe Group, on n'a plus qu'à tester si les classes dérivées de GeomObject "implémentent" l'interface Mobile. Cela se fait avec le même mot-clé que pour tester si un objet est une instance d'une classe données : instanceof.

    public class Group extends GeomObject implements Mobile
    {
	private List<GeomObject> objList = new ArrayList<GeomObject>();

	public void translate (int dx, int dy)
	{
		Iterator goIt = objList.iterator();
		
		while (goIt.hasNext())
		{
			GeomObject currentObj = goIt.next(); 
			
			if (currentObj instanceof Mobile)
				((Mobile) currentObj).translate(dx, dy);
		}
	}
Quand une classe dérivée de GeomObj est rajoutée, c'est au concepteur de la classe de décider si on peut opérer ou non une translation sur une instance de la classe et donc si la classe doit implémenter l'interface Mobile. Dans un cas comme dans l'autre, le code ci-dessus reste valide et prendra en compte la nouvelle classe sans qu'il y ait besoin de d'apporter une modification à la classe Group.

Représenter une interface en UML / sous Dia

En UML, on représente une interface comme une classe en faisant précéder le nom par le mot interface entre chevrons : <<interface>>. Il y a un étage pour le nom de l'interface, un étage pour les signatures des méthodes, mais l'étage prévu pour les membres d'une classe n'apparaît pas dans la représentation d'une interface. Sous Dia, sélectionner la même boîte que pour une classe, saisissez de nom de votre interface à l'endroit prévu pour le nom de la classe, et saisissez à la main "interface" dans "Stéréotype". On ajoute le cas échéant les méthodes de l'interface de la même manière que les méthodes d'une classe.

Pour représenter qu'une classe implémente une interface, on utilise une flèche pointillée qui ressemble à celle de l'héritage.


Archive des classes Java correspondantes : geom_color_interface.tar.bz2

Héritage multiple

Problème de l'héritage multiple

Revenons sur la modélisation des moyens de déplacement, en nous intéressant seulement aux milieux dans lesquels évoluent les véhicules (on ignore la représentation de la distinction motorisé/non-motorisé). On commence par une super-classe Véhicule qui comporte, par exemple, une chaîne de caractères correspondant au numéro d'immatriculation. Comme on veut pouvoir déplacer tout véhicule quel que soit le milieu, mais qu'on ne sait pas comment déplacer le véhicule tant qu'on ne connaît pas le milieu dans lequel il se déplace, on ajoute une méthode abstraite déplacer (float, float). On dérive les classes VéhiculeTerrestre, VéhiculeMaritime et VéhiculeAérien qui implémentent chacune la méthode déplacer (float, float) en tenant compte du milieu. Si l'on souhaite modéliser la classe Hydroglisseur (véhicule qui se déplace sur terre et sur mer) et la classe Hydravion (mer et air), on peut faire dériver ces classes de VéhiculeTerrestre et VéhiculeMaritime (pour l'hydroglisseur) et de VéhiculeMaritime et VéhiculeAérien (pour l'hydravion).


Problèmes : une classe hérite de tous les membres et de toutes les méthodes dérivées de sa classe-mère. Lorsqu'il y a plusieurs classes-mères, avec des noms et types de membres identiques, ou des méthodes de signatures identiques, que se passe-t-il ?

Certains langages orientés objets offrent des mécanismes pour régler ces problèmes. Pour les éviter, Java interdit l'héritage multiple et propose le système des interfaces. Pour le cas des véhicules, on pourrait imaginer la modélisation suivante :


Ici, tous les véhicules dérivent de la classe Véhicule (qui regroupe un certain nombre de membres et de méthodes) mais implémentent éventuellement plusieurs interfaces. Une application concrète aux objets graphiques est donnée dans la section ci-dessous.

Java : implémentations de plusieurs interfaces

Nous avons introduit plus haut l'interface Mobile qu'implémentent les objets graphiques que l'on peut déplacer. On veut pouvoir afficher les objets dans une fenêtre graphique (l'implémentation de cette fonctionnalité n'est pas abordée ici), en donner une représentation textuelle dans la console (c'est à cela que sert par exemple la méthode toString()) ou exporter les objets au format SVG (Scalar Vector Graphics), un format standard libre pour les images vectorielles utilisé notamment par Inkscape. À chaque type d'objet graphique correspond un élément XML particulier dans le format SVG. On pourrait ajouter une méthode String toSVG () dans la classe GeomObject, que l'on déclarerait abstraite. Un problème est que tout objet graphique n'a pas nécessairement vocation à être exportée au format SVG. Si l'on imagine que l'on ajoute comme classe dérivée de GeomObject une classe qui permet de gérer des images matricielles (bitmap), comme une photo, cette classe ne peut pas produire sa propre représentation vectorielle. Il faut donc pouvoir faire en sorte qu'une classe dérivée ne soit pas obligée d'implémenter cette méthode. La solution passe par l'utilisation d'une interface. Une modélisation peut être la suivante :


Les classes dérivées de GeomObject peuvent implémenter ou non l'interface Mobile; elles peuvent également (et indépendamment) implémenter ou non l'interface SVGobject. Dans la classe Group, lorsqu'on itère sur la liste d'objets géométriques, on peut tester si un objet donné est une instance (instanceof) d'une interface donnée et, le cas échéant, invoquer telle ou telle méthode (après transtypage). Voir notamment l'implémentation des méthodes String toSVG() et translate(dx, dy) dans la classe Group.


Archive des classes Java avec interfaces Mobile et SVGobject : geom_color_interfaceMobile_SVG.tar.bz2

Une interface = une logique, plusieurs projets

Une même interface peut être utilisée pour modéliser le même "comportement" dans des projets différents. Ici, par exemple, l'interface SVGobject pourrait être utilisée pour mettre en œuvre un système de génération d'image SVG correspondant à l'analyse d'une phrase en relations de dépendances syntaxiques.



Il existe dans l'API de nombreuses interfaces :



Retour page séance 7 ]

Mention légale ]