1. Les concepts de la programmation objet

1.2 Notion d'objet

Un objet est composé: Exemple: un compte bancaire peut être représenté par: Les données sont nommées par des variables appelées variables d'instance. On appelle état d'un objet la valeur de ces variables d'instance.

Les traitements sont décrits par des fonctions et des procédures que l'on appelle des méthodes.

Comment créer plusieurs comptes? Une solution consiste à réécrire le code qui définit un compte. C'est ce que permettent les langages modulaires sans généricité. Dans les langages objets, les objets vont pouvoir être dynamiquement cr'eés à partir de la notion de classe.

De plus, dans beaucoup de langages objets, il n'y a pas de possibilités de créer directement un objet. Il faut d'abord définir une classe.

1.3 Classe

Une classe décrit des schémas d'objets. Elle définit les variables d'instance (par leur nom et leur type, éventuellement une valeur initiale) et les méthodes.

Par exemple, les comptes bancaires décrits ci-dessus peuvent s'écrire dans la syntaxe java:

class Compte{
  int solde = 0;
  void depot (int n){
    solde = solde + n;
    }
  void retrait (int n){
    solde = solde - n;
    }
  void print (){
    System.out.println(solde);
    }
}
Un compte peut être créé à partir de cette classe par la construction prédéfinie:
new Compte();
On peut nommer l'objet créé en utilisant une variable déclarée préalablement.
Compte martin;
martin = new Compte();
Dans cette construction, new Compte() est l'appel à un opérateur appelé constructeur qui crée les objets de la classe Compte. Nous reviendrons plus tard sur la définition, l'utilisation, la sémantique, des constructeurs.

On dit qu'un objet créé à partir d'une classe est une instance de cette classe: martin est une instance de Compte.

Les méthodes sont appelées sur un objet et vont modifier son état. Par exemple:

    martin.depot(500);
    martin.retrait(100);
    martin.print();

1.4 Héritage

Il est possible de créer une nouvelle classe en enrichissant une classe déjà existante. Cet enrichissement peut porter sur: On appelle super-classe la classe que l'on souhaite enrichir et sous-classe la nouvelle classe enrichie.

Exemple: on va définir les comptes rémunérés. Il faut ajouter le taux d'intérêt et le calcul des intérêts.

class CompteRemunere extends Compte{
  int taux = 5; // exprime en %
  void interet(){
    solde = solde + ((solde * taux)/100);
  }
}

    ...
    CompteRemunere dupont = new CompteRemunere();
    dupont.depot(1000);
    dupont.interet();
    dupont.print();
    ...
Un objet créé à partir de la classe CompteRemunere possède aussi la variable solde et les méthodes de la classe Compte.

Autre exemple:

class CompteSecurise extends Compte{
  void retrait(int n){
    if (solde >= n)
      solde = solde - n;
    else{
      System.out.print("provision insuffisante :");
      print();
    };
  }
}

  ...

  CompteSecurise durand = new CompteSecurise();
  durand.depot(200);
  durand.retrait(300);
  ...
Les classes CompteRemunere et CompteSecurise héritent de la classe Compte.

1.5 Hiérarchie des classes

La relation d'héritage entre classes définit une hiérarchie. Lorsqu'une classe ne peut hériter au plus que d'une autre classe, on dit que l'héritage est simple. C'est le cas dans le langage java. La hiérarchie est alors un arbre.

Lorsqu'une classe peut hériter de plusieurs super-classes, on parle d'héritage multiple. C'est la cas pour les langages C++, Eiffel, Ocaml. La hiérarchie forme un treillis.

Dans notre exemple, la relation d'héritage est:


           Object
             |
             |
           Compte
             /\
            /  \
CompteRemunere  CompteSecurise
En java, la hiérarchie a toujours pour racine la classe prédéfinie Object qui fournit un certain nombre de méthodes et constructeurs.

Encapsulation

L'encapsulation est dépendante des règles de visibilité qui varient d'un langage à un autre.

Limitations de la structure de bloc d'un langage procédural: Prenons l'exemple suivant:

declare
 cptF: integer; cptG: integer;
 function F () ;
  ...
  cptF:= cptF +1;
end F;
 function G () ;
  ...
  cptG:= cptG +1;
end F;
begin
 cptF:=0;  cptG:=0;
end
L'initialisation des compteurs cptF et cptG ne peut être faite que dans le programme principal. Ces deux compteurs doivent donc être des variables globales et sont donc accessibles par tous les objets du programme.

Dans les langages objets, ce problème ne se pose plus. Par exemple, la variable solde est locale à chaque objet.

En java, il est possible de préciser la visibilité des différents composants d'une classe (variables et méthodes) avec des déclarations appropriées:

En Ocaml, les variables d'instances sont toujours invisibles.

En JAVA, si nous souhaitons rendre inaccessible le solde d'un compte, nous écrivons:

class Compte{
  protected int solde = 0;
  void depot (int n){
    solde = solde + n;
    }
  void retrait (int n){
    solde = solde - n;
    }
  void print (){
    System.out.println(solde);
    }
}
Nous utilisons protected et non private parce que les méthodes de CompteRemunere et de CompteSecurise utilisent solde. Dans le CompteRemunere, le taux peut être déclaré private, ce qui le rend inaccessible hors de la classe.

1.6 Liaison retardée

Dans une hiérarchie, des méthodes peuvent être redéfinies. Il n'est pas toujours possible de déterminer statiquement quel code doit être exécuté lorsqu'une telle méthode est invoquée.

Soit l'exemple:

class Banque {
  public Compte ouverture(){
    Compte C;
    String reponse;
    DataInputStream In = new DataInputStream(System.in);
    System.out.println("Voulez-vous un compte securise?");
    reponse=In.readLine();
    if (reponse == "oui")
      C = new CompteSecurise();
    else
      C = new Compte();
    return C;
  }
 ...
    Compte Nguyen = ouverture();
    Nguyen.depot(300);
    Nguyen.retrait(400);
    Nguyen.print();
 ...
}
La classe de l'objet Nguyen dépend d'une valeur entrée par l'utilisateur, qui n'est donc connue qu'à l'exécution. La méthode retrait n'est pas la même selon la classe (Compte ou CompteSecurise). Son code ne pourra être déterminé que lors de l'exécution. La liaison du nom au code est donc dynamique.

On dit qu'on a une liaison retardée. Par contre, la méthode print est la même dans les deux classes. La liaison nom-code est déterminée statiquement à la compilation.

La liaison retardée est parfois appelée polymorphisme. Nous préférons le terme liaison retardée.

1.7 Lexique

OO -> langages procéduraux
objet -> module
classe -> schéma de module
variable d'instance -> variable locale
méthode -> fonction ou procédure
message -> identificateur de fonction ou procédure
envoi de message -> appel de fonction ou procédure

1.8 Avantages de la programmation objet

Production: Fiabilité:

1.9 Limites de la programmation OO

Sémantique pas toujours très claire: Coût de la liaison retardée.

Classes spaghetti: difficulté de lecture et nécessité d'outils adaptés.

1.10 Historique

1.10.1 Simula (1967)

Pas d'encapsulation
héritage
liaison retardée

Exemple dans la syntaxe Simula:

Pile class PileUndo
begin
 ref(Element) last;
 Element procedure Pop
 begin
  last:-s(haut);
 end
 procedure Undo;
 begin
  Push(last)
 end
end PileUndo
Element est le nom d'une classe et last est une variable dont la valeur est un element d'Element ou d'une sous classe d'Element.

Simula est un langage typé.

class A ...
A class B...
...
ref(A)a; ref(B)b;
...
a:-b; -- correct statiquement
...
b=:-a;--peut etre correct dynamiquement

1.10.2 C++(1980)

Encapsulation
Héritage
Liaison retardée
langage typé : un type est un nom de classe ou un type C
class A ...
class B: public A ...
...
A*a; B*b;
...
a=b;
...
b=(B*)a;-- mais b=a est incorrect

1.10.3 Smalltalk (1970)

Encapsulation
Héritage
Liaison retardée