Programmation pour le TAL

Classes et méthodes abstraites

Lecture : Les classes abstraites, section 8 du chapitre 11 du livre "Programmer en Java".

Classe abstraite

Un classe abstraite est une classe que l'on ne peut pas instancier. Elle est destinée uniquement à servir de base à des classes dérivées. Une classe abstraite peut contenir des membres et des méthodes implémentées, ou des méthodes abstraites (cf. plus bas).

Pour les objets géométriques, la classe mère GeomObject devrait être déclarée abstraite. Cela ne l'empèche pas de contenir tout (membres et méthodes) ce qui est commun à l'ensemble des classes dérivées (e.g. nom de l'objet, getter et setter sur ce membre, couleur de l'objet, getter et setter sur ce membre, et méthode toString() qui surdéfinit celle de la super-classe Object, qui s'exécute pour toute classe dérivée qui ne surdéfinit pas à son tour cette méthode, mécanisme de comptage et de nommage automatique) : c'est même sa seule raison d'être. Mais que signifierait que l'on puisse instancier un objet de la classe GeomObject ? Si l'on souhaite, comme il était spécifié dans l'énoncé initial de l'exercice, afficher un objet géométrique dans une fenêtre (via une API graphique de Java), on sait afficher un point, un segment, un cercle, etc. Mais que veut dire afficher un "objet géométrique" (tant que la nature de cet objet n'est pas spécifiée) ?
Dans le code ci-dessous, le mot-clé abstract déclare la classe GeomObject comme étant abstraite. On ne peut donc pas écrire GeomObject geomObj = new GeomObject();. Une classe dérivée peut rester abstraite si elle est également déclarée comme telle cf. la classe Point : on veut empêcher l'instanciation d'un objet de ce type pour obliger à préciser, lors de la création d'un point, s'il s'agit d'un point fixe ou d'un point mobile. Une classe devient concrète dès lors qu'elle ne mentionne plus, dans sa déclaration, le mot-clé abstract (et qu'elle ne possède pas de méthode abstraite, cf. ci-dessous).

    public abstract class GeomObject
    {	
	private static int   nbCreatedObjects = 0;
	private String objName  = null;
	private static AbstractColor defaultColor = ReferenceColor.BLACK;
	private AbstractColor objColor = null;
	
	public GeomObject ()
	{
		nbCreatedObjects++;
		setObjName("anonymous_" + String.valueOf(nbCreatedObjects));
	} // GeomObject ()

	public String getObjName()
	{
		return objName;
	} // getObjName()

	public void setObjName (String objName)
	{
		this.objName = objName;		
	} // setObjName()
	
	public String toString ()
	{
		return getObjName() + " [GeomObj]";
        } // toString ()

	public void setObjColor (AbstractColor objColor)
	{
		this.objColor = objColor;
	} // setObjColor ()
	
	public AbstractColor getObjColor ()
	{
		return objColor;
	} // getObjColor ()
    }      

    // on ne peut pas instancier d'objet de type Point
    public abstract class Point extends GeomObject
    {
        // ...  
    }

    // mais on peut instancier un objet de type MobilePoint
    public abstract class MobilePoint extends Point
    {
        // ...  
    }

Note : même si l'on ne peut pas instancier d'objet d'une classe abstraite, on peut implémenter un constructeur pour ce type de classe. Ce constructeur est exécuté par invocation de super () dans les classes dérivées ou par un appel automatique au constructeur par défaut (revoir si besoin la présentation sur les constructeurs, séance 4).
Par exemple, on a vu qu'on ne pouvait pas instancier la classe GeomObject en écrivant GeomObject geomObj = new GeomObject();. Mais on peut implémenter un constructeur dans cette classe pour mettre en œuvre le mécanisme de comptage et de nommage automatique).

Méthode abstraite

Dans une classe abstraite, on peut ajouter la signature (nom, type de retour et liste du type des arguments) d'une méthode déclarée abstraite. Cela veut dire qu'une classe dérivée devra implémenter cette méthode pour ne plus être abstraite.

Par exemple, imaginons que l'on veuille calculer, pour chaque type d'objet graphique, l'étendue horizontale (sur l'axe des abscisses) et l'étendue verticale (sur l'axe des ordonnées) de l'objet. Pour les objets de type cercle, segment et point, c'est simple. Mais pour un groupe, cela nécessite de connaître le point le plus à gauche (l'abscisse minimale) et le plus à droite (l'abscisse maximale) des objets de ce groupe dans le repère ainsi que le point le plus bas (l'ordonnée minimale) et le plus haut (l'ordonnée maximale) des objets de ce groupe dans le repère. On décide d'ajouter les méthodes int getMaxX(), int getMinX(), int getMaxY(), int getMinY() que tout objet doit implémenter (l'implémentation étant différente pour chaque type d'objet).
Ci-contre : illustration du calcul de l'étendue horizontale d'une groupe constitué d'un segment et d'un cercle.

D'une part, on veut contraindre toutes les classes "concrètes" qui dérivent de GeomObject à implémenter ces méthodes mais on ne peut pas les implémenter dans la classe-mère GeomObject (de la même manière qu'on ne peut pas "afficher" graphiquement une instance de GeomObject dont on ne sait pas quelle forme elle a). C'est le rôle des méthodes abstraites.

    public abstract class GeomObject
    {	
        private String objName = null;
        // ...

	public GeomObject ()
	{
		nbCreatedObjects++;
		setObjName("anonymous_" + String.valueOf(nbCreatedObjects));
	} // GeomObject ()

        public String toString ()
	{
		return getObjName() + " [GeomObj]";
	} // toString ()

  	public abstract int getMaxX ();
	public abstract int getMinX ();
	public abstract int getMaxY ();
	public abstract int getMinY ();
    }

Illustration : calcul de l'étendue horizontale d'un goupe

La méthode toString() est concrète : on en donne ici une implémentation, même si la classe est abstraite (i.e. même si on ne peut pas instancier d'objet de type GeomObj). Les classes dérivées héritent toutes de cette même méthode (qu'elles peuvent redéfinir, comme on l'a vu). En revanche, les différentes méthodes getMin/getMax sont abstraites : on n'en donne pas l'implémentation.

Si, dans la classe Circle, on n'implémente pas ces 4 méthodes, cette classe doit être déclarée abstraite (et on ne pourra pas instancier d'objet de ce type). Sinon, on obtiendra l'erreur "The type Cicrcle must implement the inherited abstract method GeomObject.getMaxX ()".
Nouvelle classe Circle qui implémente les 4 méthodes (et peut donc ne pas être déclarée abstract) :

    public class Circle extends GeomObject
    {
	private MobilePoint center = null;
	private int radius;

	// ...

	public int getMaxX ()
	{
		return getCenter().getX() + getRadius();
	}
	
	public int getMinX ()
	{
		return getCenter().getX() - getRadius();
	}
	
	public int getMaxY ()
	{
		return getCenter().getY() + getRadius();
	}
	
	public int getMinY ()
	{
		return getCenter().getY() - getRadius();
	}
    }

Dans une classe abstraite, une méthode peut invoquer, dans son implémentation, une méthode déclarée abstraite. C'est par exemple le cas des méthodes getHorizontalSpan() et getVerticalSpan() dans la classe GeomObject :

    public abstract class GeomObject
    {	
        // ...

  	public abstract int getMaxX ();
	public abstract int getMinX ();
	public abstract int getMaxY ();
	public abstract int getMinY ();
  
	public int getHorizontalSpan ()
	{
		return getMaxX() - getMinX();
	} // getHorizontalSpan ()
	
	public int getVerticalSpan ()
	{
		return getMaxY() - getMaxY();
	} // getVerticalSpan ()
    }

Représentation UML

Dans un diagramme de classes UML, le nom d'une classe ou d'une méthode abstraite est représenté en italique. Sous Dia, il existe, pour le nom de classe une case à cocher "abstraite" et, pour la méthode (opération dans Dia) une combo-box "type d'héritage" (sélectionner "abstraite").



Retour page séance 7 ]

Mention légale ]