Archive pour le mot-clef ‘poo’

Programmation Orientée Objet en JAVA

Dimanche 24 janvier 2010

Dans cet article, second de la série de nos tutoriels sur java, nous vous proposons une petite introduction à la POO (Programmation Orientée Objet). Il existe de nombreux tutoriels sur ce sujet. Nous essayons ici de rester simple et concis. Nous présenterons un exemple complet après un rapide cadre théorique.

Nous recommandons les excellents tutoriaux de sun à ce sujet.

Un peu de Théorie

Sur Wikipedia, on trouve cette définition :

La programmation orientée objet (POO) ou programmation par objet, a été élaborée par Alan Kay dans les années 1970. C’est un paradigme de programmation informatique qui consiste en la définition et l’interaction de briques logicielles appelées objets.

Qu’est-ce qu’un objet ? C’est une représentation informatique d’une entité physique ou d’une idée. Un objet est caractérisé par des attributs et des méthodes. On parle aussi de variables d’état (permettant de caractériser l’état de l’objet) et de comportements. Un objet est aussi une instance d’un modèle. Le modèle, que l’on appelle une Classe, représente donc un concept. Une classe est un descripteur des objets, elle va préciser quels sont les attributs (et les valeurs que ces attributs peuvent prendre) et méthodes de l’objet.

L’une des motivations de la programmation Objet est la réutilisabilité. Le but est que des classes définies pour un projet puissent être réutilisées dans d’autres sans avoir pour autant à comprendre le mécanisme interne de ces classes. Pour cela, deux notions importantes : celle d’interface et celle d’héritage.

Dans cette optique de réutilisabilité, il est important de souligner la possibilité de communiquer la signature d’une Classe. La signature d’une classe est sa description, la liste de ses attributs, la liste de ses méthodes. Cette description permet à tout un chacun de savoir comment peut être utilisé un objet d’une classe, comment on peut interagir avec un objet (certains diront “comment on peut communiquer avec cet objet”, pour moi on leur donne des ordres tout au plus, ce n’est pas une véritable communication). La signature permet de voir une Classe et ses instances comme une sorte de boîte noire dont on ne connait que les points d’entrées et de sorties. Ces signatures peuvent être commentées et publiées, c’est le cas de l’api en ligne ou javadoc.

C’est dans cette optique de signature qu’ont été définies les interfaces. On dit d’une interface que c’est un contract entre la classe et le monde extérieur. Une interface décrit exactement une signature mais restreinte à un ensemble de méthodes. Et une interface ne définit en aucun cas le fonctionnement de ses méthodes. Une classe qui implémente une interface garantit donc que tous les objets instances de cette classe ont une méthode qui s’appelle de cette façon et qui renvoie ce type de résultat.

L’héritage est la possibilité de spécialiser ou généraliser les Classes. Prenez une classe A avec ses attributs et ses méthodes. Une classe B doit avoir les mêmes attributs et méthodes mais avec quelques éléments en plus. On dit que B spécialise A. On peut alors grâce à l’héritage dire explicitement B est identique à A avec ces éléments supplémentaires. L’héritage permet d’économiser du code. Il est aussi possible de redéfinir l’une des méthodes de A dans B. Ainsi, B a bien une méthode portant le même nom que celle de A mais elle ne fait pas le même traitement. En gros, une ferrari et une deux chevaux sont toutes les deux des voitures qui peuvent accélérer mais pas de la même façon. Il est important de retenir que tout objet instance de B est aussi instance de A. C’est ce que l’on appelle le polymorphisme.

Dernier point sur l’héritage : dans certains langages, il est possible de faire de l’héritage multiple. L’héritage multiple consiste à dire : soit deux classes distinctes (non liées par héritage) A et B, une troisième classe C peut hériter de A et de B. C’est à dire que C hérite toutes les méthodes et attributs de A et de B. Il n’est pas possible en Java de faire de l’héritage multiple. Cependant, il est possible d’implémenter plusieurs interfaces.

Concrétement on fait comment ?

De nombreux tutoriaux existent ou on vous fera coder des voitures ou des distributeurs de billets. Vous en trouverez de très bons sur développez par exemple.

Ici, nous allons nous contenter de l’essentiel. Comment on écrit une classe, comment on hérite d’une classe, comment on déclare une interface, comment on implémente une interface, et enfin comment une interface hérite d’une autre.

Je vous recommande de lire notre premier tutoriel débuter java. Nous ne détaillons toujours pas dans ce tutoriel tous les usages de tous les mots clefs java liés aux niveaux de droits d’accés. Nous consacrerons un tutoriel à ce sujet.

Avant de commencer, je voudrais insister sur l’ambiguité du mot objet. On l’utilise souvent à tort et à travers. On ne sait plus vraiment si on désigne une classe ou une instance de classe avec le mot objet. L’existence d’une classe Object en java n’aide pas à lever cette ambiguité. Donc une bonne fois pour toute : un objet est une instance d’une classe.

Définition d’une classe

Avant tout un petit rappel sur les classes en Java:

  • On peut déclarer autant de classes que l’on souhaite dans un fichier java.
  • On ne peut déclarer qu’une seule classe public.
  • Le fichier doit porter le même nom que la classe public s’il y en a une.
  • S’il n’y a pas de classe public dans le fichier, le nom du fichier n’a pas d’importance.

Il faut aussi noter qu’une classe en Java est censée faire partie d’un package. Ceci n’est pas obligatoire mais c’est une bonne pratique. Dans Eclipse par exemple, quand vous créez une classe sans la mettre dans un package, celle-ci est placée dans un package par défaut (default package).

Donc en règle générale, un fichier .java débute par une ligne de déclaration de package, par exemple pour signaler que les classes de ce fichier sont dans le package tutoriels lui même inclus dans le package natoine, on écrira :

package natoine.tutoriels;

Un point sur les différents niveaux de droits d’accés pour les classes. Celles-ci peuvent avoir comme droit d’accés : default ou public.

  • default : Le niveau d’accés par défaut. Dans ce cas, la classe n’est visible que par les autres classes de son package.
  • public: Le niveau d’accés publique. C’est à dire qu’elle est visible par toutes les autres classes, même les classes qui ne sont pas du même package.

Pour indiquer qu’une classe est de niveau d’accés par défaut ou publique, on utilise des mots-clefs que l’on appelle des modifiers. Le mot-clef pour le niveau par défaut est default, celui pour publique est public. En ce qui concerne le niveau d’accés par défaut, le mot-clef n’est pas indispensable. Ne rien mettre ou mettre le modifier default revient au même.

Pour déclarer une classe, on utilise le mot-clef class. Par convention, le nom d’une classe commence par une majuscule.

Déclaration d’une classe d’accés par défaut DefaultClass :

class DefaultClass

ou bien :

default class DefaultClass

Déclaration d’une classe d’accés publique PublicClass :

public class PublicClass

Ensuite il faut ouvrir le corps de la définition de la classe par une acolade puis refermer cette acolade une fois la définition finie.

Héritage de classe

Pour hériter d’une classe, il faut utiliser le mot-clef extends. Par exemple nous allons faire un classe ExtendDefaultClass :

public class ExtendDefaultClass extends DefaultClass

Vous remarquerez que vous êtes obligés de mettre cette nouvelle classe dans le même package que DefaultClass. Sinon, la classe DefaultClass ne sera pas visible.

A l’opposé si nous faisons unc classe ExtendPublicClass qui hérite de la classe PublicClass, nous pouvons la mettre dans un autre package que celui contenant la classe PublicClass.

Il reste deux modifiers important à aborder concernant la définition de classes. Ces mots-clefs sont final et abstract :

  • final : Le modifier final indique que la classe ne peut pas avoir de classe fille. Aucune classe ne pourra hériter d’une classe marquée final.
  • abstract : Le modifier abstract permet de déclarer une classe comme étant abstraite. Une classe abstraite ne peut pas être instanciée, sa seule raison d’être est d’être héritée. Notez qu’une classe abstraite n’est pas obligée de déclarer des méthodes abstraites. Par contre, une classe déclarant une méthode abstraite doit nécessairement être une classe abstraite.

Déclarons une classe final FinalExtendDefaultClass qui hérité de DefaultClass :

final class FinalExtendDefaultClass extends DefaultClass

Déclarons une classe abstraite AbstractPublicClass qui hérite de la classe PublicClass :

public abstract class AbstractPublicClass extends PublicClass

Implémentation d’une interface

Pour déclarer une interface, il faut utiliser le mot-clef interface. Les interfaces comme les classes peuvent être accessible par défaut ou publique. Elles suivent les mêmes règles de droit d’accés et les modifiers d’accés sont les mêmes (default, public).

L’intérêt d’une interface est de préciser des méthodes à obligatoirement implémenter. Une classe implémentant une interface doit nécessairement implémenter les méthodes de cette interface.

Pour l’exemple, nous allons déclarer une interface publique PublicInterface avec une méthode printName().

public interface PublicInterface
{
void printName();
}

Petite remarque au sujet des méthodes d’une interface (pour ceux qui ont pris de l’avance concernant les droits d’accés des méthodes) : elles sont abstraites et publiques obligatoirement. Elles sont nécessairement publiques parce que sinon, elles ne seraient pas visibles par les classes qui doivent les implémenter. Et elles sont abstraites parce que chaque classe doit définir leur implémentation et l’interface ne suppose rien d’autre au sujet des classes (l’interface ne sait pas quels sont les attributs des classes l’implémentant ni les autres méthodes).

La méthode printName aurait pu être écrite de la façon suivante, cela revient au même :

public abstract void printName();

Pour qu’une classe implémente une interface, il faut utiliser le mot-clef implements et bien sur, déclarer et implémenter les méthodes de l’interface en question. Par exemple, nous allons déclarer une classe publique PublicClassImplementPublicInterface.

public class PublicClassImplementPublicInterface implements PublicInterface
{
public void printName()
{
System.out.println(”PublicClassImplementPublicInterface”);
}
}

Si une classe doit implémenter plusieurs interfaces, il suffit de séparer chaque nom d’interface par une virgule.

Une classe peut hériter d’une autre classe et implémenter une ou plusieurs interfaces. Dans ce cas, le mot clef extends et la déclaration de la classe mère précéde le mot-clef implements et la liste des interfaces. Par exemple :

public class PublicClassExtendsImplements extends AbstractPublicClass implements PublicInterface

Héritage d’interface

Il est possible pour une interface d’hériter d’une autre interface. Il suffit de préciser l’interface héritée par le mot-clef extends.

Déclarons une interface InterfaceExtendsPublicInterface qui hérite de notre interface précédente PublicInterface :

public interface InterfaceExtendsPublicInterface extends PublicInterface
{
void printInterfaceList();
}

Cette interface n’a pas à redéclarer les méthodes héritées par ses interfaces mères mais les classes implémentant cette interface devront les implémenter.

Instanciation de classe

Instancier un objet se fait simplement par l’utilisation du mot clef new. Par exemple, instancions un objet de la classe PublicClassImplementInterface:

new PublicClassImplementPublicInterface();

Lors de l’appel à new PublicClassImplementPublicInterface(), un nouvel objet de la classe PublicClassImplementPublicInterface est construit. Cette construction fait appel au constructeur de la classe PublicClassImplementPublicInterface.

Dans notre exemple, nous n’avons pas défini de constructeur dans la classe PublicClassImplementPublicInterface. Du coup, le constructeur appelé est celui de la classe dont hérite PublicClassImplementPublicInterface. Si celle-ci ne définit pas de constructeur non plus, le constructeur appelé est celui de la classe mère de la classe dont hérite PublicClassImplementPublicInterface. Etc jusqu’à la classe Object qui elle déclare bien un constructeur.

En java, il existe une super classe, la classe Object. Toutes les classes que vous définirez héritent donc de cette classe Object et ce sans avoir à utiliser le mot-clef extends.

Nous allons maintenant déclarer une classe FinTutoClass qui elle déclare un constructeur. Un constructeur de classe doit porter le même nom que la classe et se déclare de la même façon qu’une méthode à part qu’il n’a pas de type de retour. Afin de bien finir ce tutoriel, cette classe FinTutoClass hérite de la classe PublicClassExtendsImplements et implémente l’interface InterfaceExtendsPublicInterface.

public class FinTutoClass extends PublicClassExtendsImplements implements InterfaceExtendsPublicInterface
{
public FinTutoClass()
{
super();
}
public void printInterfaceList()
{
for(Class _interface : this.getClass().getInterfaces())
{
System.out.println(” ” + _interface.getSimpleName());
}
}
}

Encore une fois, le constructeur peut avoir le même niveau de droits que toute méthode (donc default, public, private ou protected).

L’utilisation de super(); signifie que le constructeur a le même comportement que le constructeur de la classe mère. Ensuite il est toujours possible d’ajouter d’autres instructions. En réalité, super() est appelé dans tous les cas.

Maintenant nous allons consacrer la fin de ce tutoriel au polymorphisme. Nous allons écrire une classe MainClass avec une méthode main afin de pouvoir l’exécuter. Les questions auxquelles nous allons tenter de répondre sont : quelle est la nature (ou le type) de notre objet? Peut-il changer de nature?

Cette dernière partie va nous permettre d’introduire l’opération de casting d’une classe. Le casting d’une classe (on dit que l’on caste un objet d’une classe) consiste à demander à un objet d’agir en tant qu’objet d’un autre type que celui déclaré lors de sa construction. Il est possible de caster l’objet selon les classes dont il est instance (c’est à dire la classe déclarée lors de l’appel à new et toutes les classes méres) et selon les interfaces qu’il implémente.

Le contenu de la classe MainClass :

public class MainClass {
public static void main(String[] args)
{
FinTutoClass _object = new FinTutoClass();
_object.printInterfaceList();
_object.printName();
System.out.println(_object.getClass().getSimpleName());
System.out.println(((PublicClassExtendsImplements)_object).getClass().getSimpleName());
PublicClassExtendsImplements _object2 = new FinTutoClass();
_object2.printName();
((FinTutoClass)_object2).printInterfaceList();
System.out.println(_object2.getClass().getSimpleName());
PublicInterface _object3 = new FinTutoClass();
_object3.printName();
((InterfaceExtendsPublicInterface)_object3).printInterfaceList();
InterfaceExtendsPublicInterface _object4 = new FinTutoClass();
_object4.printInterfaceList();
_object4.printName();
}
}

Que fait-on dans ce main?

  • Le début est simple : on crée un objet _object de type FinTutoClass en appelant le constructeur de FinTutoClass.
  • ensuite, on appelle la méthode printInterfaceList qui est implémentée dans la classe FinTutoClass
  • La troisième ligne montre que notre objet _object peut aussi appeler les méthodes dont il a hérité. La méthode printName est implémentée dans la classe PublicClassExtendsImplements dont hérite la classe FinTutoClass et n’est pas réimplémentée dans FinTutoClass.
  • Dans la quatrième ligne, on demande à afficher la classe de l’objet _object. Il sera affiché FinTutoClass.
  • Dans la cinquième ligne on caste notre objet : (PublicClassExtendsImplements)_object signifie que _object est ici traité comme étant une instance de la classe PublicClassExtendsImplements. Puis on demande d’afficher le nom de la classe dont il est l’instance. Il sera affiché FinTutoClass malgrè le cast. Bien que l’on ait casté l’objet, celui-ci reste de la classe dont on a appelé le constructeur.
  • Dans la sixième ligne, on déclare un objet _object2 comme étant de la classe  PublicClassExtendsImplements, cependant on fait appel au constructeur de FinTutoClass. Ceci est possible car FinTutoClass hérite de PublicClassExtendsImplements.
  • Du coup, dans la septième ligne, on peut appeler une méthode implémentée dans la classe PublicClassExtendsImplements.
  • Par contre, pour appeler une méthode de la classe FinTutoClass, nous somme obligés de caster l’objet _object2 en FinTutoClass. Ceci est possible car on a effectivement affaire à un FinTutoClass.
  • D’ailleurs si on demande à _object2 d’afficher le nom de sa classe, il affichera bien FinTutoClass.
  • Ensuite, on construit un objet _object3 mais en déclarant cette fois qu’il est du type de l’interface PublicInterface. L’objet est construit via l’appel au constructeur de la classe FinTutoClass. On peut donc typer un objet selon les interfaces que sa classe implémente.
  • Du coup on peut appeler les méthodes décrites dans l’interface. Et que les méthodes de l’interface tant que l’on ne caste pas l’objet.
  • On est par exemple obligé de caster l’objet pour faire appel à la méthode printInterfaceList. On peut là aussi caster l’objet en un type d’interface (ici InterfaceExtendsPublicInterface) que sa classe implémente.
  • Cette fois on construit un objet _object4 que l’on type selon l’interface InterfaceExtendsPublicInterface.
  • Du coup on peut appeler sans cast la méthode printInterfaceList
  • On peut aussi appeler sans cast une méthode définie dans une interface dont hérite l’interface InterfaceExtendsPublicInterface.

Sources du tutoriel

Vous pouvez télécharger le contenu de mon projet eclipse pour ce tutoriel.

Bien entendu les noms de classe ont ici été choisis pour le tutoriel. Vous n’êtes pas obligé de mettre le mot Default dans le nom d’une classe d’accés pas défaut.