Précédent Suivant Index

3   O2 : le modèle

Cette section contient tout ce qui concerne la définition d'un schéma. L'implantation ave O2C fait l'objet de la section suivante.

3.1   Rappels sur les principaux concepts de l'orienté-objet

La structure de base utilisée pour modéliser l'information est l'objet, équivalent pour les SGBDOO de la table dans les SGBDR ou de l'enregistrement dans les systèmes de fichiers.
Les différences essentielles entre le modèle orienté objet et les autres (modèle relationnel, modèle réseau, ...) sont les suivantes:
  1. Un objet comprend à la fois des données et les modules (ou méthodes) qui agissent sur ces données. De plus tout accès ou manipulation de l'information contenue dans un objet doit en principe se faire par les méthodes. En d'autres termes l'entité qui communique avec un objet ne connaît pas la structuration interne des données de l'objet auquel il s'adresse. On désigne par encapsulation cette technique de protection du contenu d'un objet.
  2. Les types des attributs peuvent être d'une structuration beaucoup plus complexe que celle qu'on trouve dans le modèle relationnel. De plus un objet peut contenir des attributs multimédia: image, son, etc... La définition du contenu d'un objet est une classe. Elle comprend un type (décrivant la structuration des attributs de l'objet) et des méthodes agissant sur ce type. Les ensembles d'objets ayant la même structure sont donc groupés en classes.
  3. Les classes peuvent avoir des sous-classes définies par un procédé qu'on appelle l'héritage. Une sous-classe reprend la définition de sa super-classe et y ajoute des attributs et des méthodes. Une sous-classe peut également redéfinir les méthodes de sa super-classe.
  4. Un objet a une identité, indépendante des valeurs qu'il contient. Cette notion d'identité est analogue à celle de clé primaire dans le modèle relationnel. Les deux différences essentielles sont que
    1. le concepteur de la base n'a pas à se soucier de définir cette clé primaire par un ensemble d'attributs.
    2. L'identité d'un objet n'est porteuse d'aucune autre information que celle pour laquelle elle est conçue: permettre de faire référence à un objet.
En résumé: Un objet est construit par référence à une classe. Les classes sont définies par un type et des méthodes. Nous allons voir successivement comment définir un type et une méthode dans O2.

3.2   Types

3.2.1   Définition d'un type

Un type est constitué de types dits "atomiques" auxquels on applique récursivement des constructeurs. Dans l'ordre, cette partie présente donc: les types atomiques, les constructeurs, et les types O2. On trouvera ces informations, avec plus de détails, dans la documentation O2. Cependant l'ordre de la présentation a été légèrement changé.

3.2.2   Types "atomiques"

Il y en a six :
  1. integer
  2. char
  3. boolean (ne prend que les valeurs false ou true).
  4. real
  5. string (chaîne de caractères).
  6. bits (chaîne d'octets: peut contenir des caractères non-ascii).
Comme on le voit, ces types de base correspondent globalement à ce qu'offrent les SGBD relationnels. L'intérêt du SGBDOO est d'offrir des constructeurs pour définir des types structurés de façon complexe.

3.2.3   Constructeurs de types

Il y en a trois:
  1. Le constructeur tuple : Il permet de regrouper des attributs de types distincts, en leur associant un identifiant. Un tuple est équivalent à la notion d'enregistrement, telle qu'on la trouve par exemple dans les langages de programmation ou dans le modèle réseau. Les attributs contenus dans un tuple peuvent être de n'importe quel type: atomique, structuré, ou classe. Exemple:
    type tuple (nom     : string,
                adresse : tuple (rue    : string,
                                 numéro : integer,
                                 ville  : string),
                Marie_a : Personne)
    
  2. Le constructeur set : Il permet de construire un ensemble constitué d'un nombre indéfini d'éléments du même type. Par défaut un "set" peut contenir deux fois le même élément. Ce n'est pas le cas pour les "unique set". Dans un "set", l'ordre des éléments est indifférent. Exemple :
    type tuple (nom     : string,
                adresse : tuple (rue    : string,
                                 numéro : integer,
                                 ville  : string),
                Marie_a : Personne,
                enfants : set(Personne))
    
  3. Le constructeur list : Il permet de construire une liste constituée d'un nombre indéfini d'éléments du même type. Cette fois l'ordre des éléments est important.
Un type est la définition d'une structure de données bâtie avec les éléments ci-dessus. Il est donc structuré à l'aide des opérateurs tuple, set et list, et contient des attributs qui sont Si par exemple une classe bitmap est définie pour contenir une image, la référence à cette classe dans un type permettra donc d'associer des données classiques (string, integer) à des données multimédia (image). On peut définir des types dans O2 par la commande create type. Il faut bien distinguer le type de la classe, la différence principale étant qu'un type n'a pas de méthode. Une instance d'un type est un ensemble de valeurs correspondant aux spécifications du type.

(1, 5, 9) est une instance du type set(integer). ("Dupont", 28, "Français"), est une instance du type tuple (nom: string, age: integer, nationalité: string).

Quelques mots sur les sous-types
Un type peut avoir des sous-types, selon la définition suivante:

Un type a est sous-type d'un type b si et seulement si toute instance de a peut devenir instance de b.

Ce qui revient à dire que a contient plus d'information que b.

Selon le constructeur utilisé, on peut avoir les sous-types suivants: Un type est une définition. L'instanciation d'un type donne selon la terminologie O2 une valeur, c'est à dire un ensemble de données structurées.

Il faut bien distinguer type et classe, valeur et objet. Entre le couple (type, valeur) et le couple (classe, objet), la différence tient à la présence ou non de méthodes définissant les opérations applicables à une donnée structurée.

Nous allons donc voir maintenant comment définir des méthodes.

3.3   Méthodes

Une méthode est un module qui implémente une partie du comportement d'un objet. Les méthodes sont définies au niveau d'une classe, et la même méthode s'applique à l'ensemble des objets de cette classe. Il n'est pas possible d'avoir une méthode particulière à un objet.

Il faut bien distinguer la définition d'une méthode et son implémentation. Quand on définit une méthode, on se contente de décrire le type des données en sortie et en entrée, sans rien indiquer sur la façon dont la méthode fonctionne. Le programmeur qui utilise une classe ne connaît donc que l'appel de la méthode, qu'on désigne sous le terme signature.

Le fonctionnement est écrit à part à l'aide du langage O2C, qui est une extension du langage C. De cette façon, on obtient la programmation modulaire qui une des qualités recherchées dans un SGBD OO.

Définition d'une méthode:

Une méthode est définie par Si une méthode est privée, elle ne peut être utilisée que par les autres méthodes de la classe. Une méthode publique peut communiquer avec une entité extérieure.

La signature est la liste des paramètres de la méthode, avec leur type. Ce type peut être un type atomique, un type structuré, ou une classe. Dans ce dernier cas c'est un identificateur d'objet qui sera passé en paramètre. De plus toute méthode reçoit au moins un paramètre implicite: l'identificateur de l'objet à partir duquel la méthode a été appelée. Cet identificateur est désigné par self.

Enfin, une méthode retourne à l'entité appelante une donnée dont le type est spécifié au moment de la définition de la méthode. Cette donnée pourra être un objet ou une valeur.

Exemple d'une méthode de la classe Personne, qui teste si une personne passée en paramètre est parente de l'objet propriétaire:
method public est_parent_de (personne: Personne): boolean;
Dans l'implémentation en O2C, une comparaison sera faite entre la variable personne et la variable self.

3.3.1   Séparation définition/implémentation

L'explication de cette séparation définition/implémentation est le respect du principe d'encapsulation. Le contenu d'un objet, que ce soit la structure des données ou le fonctionnement d'une méthode, doit rester caché à l'environnement extérieur. Il est donc seulement nécessaire de connaître le mode de communication avec un objet, c'est à dire le nom des méthodes, leur signature et le type de l'information retournée.

3.3.2   Envoi de messages

En orienté-objet, on ne dit pas "appeler la méthode chose pour l'objet untel " mais "envoyer le message chose à l'objet untel". La justification de ce jargon est qu'en raison de l'encapsulation et de l'héritage, la procédure appelée quand on déclenche une méthode peut changer d'un objet à l'autre. C'est l'objet qui décide en fait du module à exécuter quand il reçoit un message. On utilise donc l'expression "envoi de message" pour éviter l'analogie trompeuse avec "appel de procédure".

Nous savons maintenant définir une classe.

3.4   Classes

3.4.1   Définition d'une classe

La spécification d'une classe comprend quatre parties:
  1. Le nom de la classe. Par convention, il commence par une majuscule.
  2. La (ou les) classe(s) dont hérite la classe courante. Cet aspect sera abordé plus loin.
  3. Le type de la classe. Tous les objets de la classe auront une information structurée selon ce type.
  4. La liste des méthodes de la classe.
Exemple de création d'une classe:
class Person
type tuple (nom     : string,
            prenom  : string,
            age     : integer,
            photo   : Bitmap,
            enfants : list (Person),
            salaire : real)
method  public maj_salaire , ajout_enfant (enfant: Person)
end;
Ni le type, ni les méthodes ne sont obligatoires.

3.4.2   Propriétés d'une classe

Les attributs et les méthodes sont les propriétés de la classe. Chaque propriété peut être publique ou privée. Les propriétés privées ne sont pas accessibles par l'environnement d'un objet de la classe.

Par défaut une propriété est privée. Dans la classe donnée en exemple ci-dessus, le type est privé. Il est donc impossible d'y accéder excepté dans une méthode de la classe elle-même.

La méthode ajout_enfant est également privée.

3.4.3   Réutilisabilité

O2 essaie autant que possible d'unifier la syntaxe relative aux attributs et aux méthodes. Ainsi, si Joe est une référence vers un objet de la classe Person, l'accès à l'attribut salaire se fait par:
Joe->salaire
et l'appel de la méthode maj_salaire se fait par
Joe->maj_salaire
Pour l'utilisateur extérieur, il est impossible de distinguer, dans ce cas, ce qui est attribut de ce qui est méthode. Dans la réalité, la méthode maj_salaire contient certainement des paramètres et la différence avec un attribut apparaît.

Cette tentative d'unification illustre un des bénéfices attendus de l'encapsulation: l'évolutivité et la réutilisabilité.

Dans l'exemple ci-dessus, supposons qu'on ait d'abord stocké le salaire d'une personne sous forme d'un attribut de type real. Les inconvénients de ce choix apparaissent si il s'avère que la salaire est en fait la somme de plusieurs attributs (salaire de base + primes), affectés d'un coefficient (ancienneté). La correction consiste alors à remplacer l'attribut salaire par une méthode de même nom qui retournera le salaire de la personne en fonction des autres attributs. L'appel n'ayant pas changé, tous les programmes utilisant le salaire d'un individu n'ont pas besoin d'être modifiés.

3.4.4   Compléments sur les classes

Dans O2, toutes les classes héritent implicitement de la classe Object, et donc des méthodes fournies par le système O2 qui sont toutes attachées à la classe Object. Il s'agit notamment des méthodes O2Look qui permettent d'afficher et de manipuler graphiquement des objets.

De plus, toute classe a au moins une méthode init qui permet d'initialiser la valeur d'un objet au moment de sa création. Cette méthode peut être redéfinie.

Une instanciation de la classe est un objet dont le contenu et le comportement sont définis dans la classe.

3.5   Sous-classes

3.5.1   Définition d'une sous-classe

Une sous-classe apporte des informations supplémentaires par rapport à la classe dont elle hérite. Ces informations supplémentaires peuvent être: Dans l'exemple donné précédemment, on n'aurait certainement pas fait figurer un attribut "salaire" dans la classe Personne. L'ensemble des salariés étant un sous-ensemble de l'ensemble des personnes, voici comment on peut rendre compte de cette situation dans O2:
class Person
type tuple (nom     : string,
            prenom  : string,
            age     : integer,
            photo   : Bitmap,
            enfants : list (Person))
method  ajout_enfant (enfant: Person)
end;
class Employé inherit Person
type tuple (poste   : string,
            salaire : real)
method maj_salaire
end;
Toutes les propriétés (attributs et méthodes) de la classe Person existent toujours dans les objets de la classe Employé. On aurait pu ne rajouter que des attributs, ou que des méthodes.

Toute classe dans O2 dérive de la classe de plus haut niveau Object.

Dans l'exemple ci-dessus, on a ajouté des attributs et des méthodes. Mais on peut aussi redéfinir ceux qui existaient déjà dans la super-classe. Cette redéfinition des propriétés est expliquée ci-dessous:

3.5.2   Redéfinition d'un attribut

Si un attribut apparaît dans la définition d'une sous-classe avec le même nom qu'un attribut existant déjà dans la super-classe, ce dernier est redéfini. Seule condition: le type du nouvel attribut doit être un sous-type du type de l'ancien attribut.

3.5.3   Redéfinition d'une méthode

On peut rédéfinir une méthode, la seule condition étant que les signatures soient compatibles

3.5.4   Résolution tardive

La redéfinition d'une méthode donne lieu à la résolution tardive. Supposons qu'une routine soit définie pour agir sur des objets de la classe Person. Elle pourra aussi bien, pendant l'exécution, agir sur des objets de la classe Employé puisque ces derniers ont les mêmes propriétés que la classe Person. On a vu d'ailleurs que les méthodes de la classe Person étaient héritées par la classe Employé.

Si une méthode a été redéfinie sur la classe Employé, ce n'est qu'au moment de l'exécution que l'on pourra déterminer quelle méthode utiliser: celle de la classe Employé ou celle de la classe Person. D'où le terme de résolution tardive.

Quand on "envoie un message" un objet, il s'agit donc d'une opération plus complexe que d'appeler une routine. Il faut La résolution tardive peut mener à une dégradation des performances.

3.6   Objets

Un objet est à sa classe ce qu'une valeur est à son type: la réalisation concrète d'une définition abstraite.

Il faut bien distinguer un objet d'une valeur. Un objet encapsule une valeur, c'est à dire qu'en plus de contenir un ensemble structuré de données, il impose des règles d'accès (les méthodes) limitant les possibilités de manipulation de ces données.

Un objet est donc plus qu'une valeur. De fait un objet comprend les trois éléments suivants: Il faut noter que ce qui distingue entre eux les objets d'une classe, c'est l'identité, qui est unique pour chaque objet, et la valeur. Par contre les méthodes sont communes à l'ensemble des objets d'une classe: il ne peut pas y avoir de méthode propre à un objet.

Pour obtenir la valeur d'un objet, on utilise l'opérateur *. C'est l'opérateur de désencapsulation. Soit par exemple la classe suivante:
class Person
type tuple (nom: string, age: integer).
end;
Si Joe est une variable représentant un objet de cette classe, *Joe est une valeur de type tuple (nom: string, age: integer).

En principe la valeur d'un objet est encapsulée et on n'y accède que dans l'un des deux cas suivants: La création d'un objet se fait par l'opérateur new:
Joe = new Person
Cette instruction revient à appeler la méthode init qui initialise la valeur de l'objet. Par défaut, l'initialisation se fait de la façon suivante: La méthode init peut être redéfinie pour chaque classe de façon à changer ces initialisations par défaut.

3.7   Le schéma

Toutes les définitions de classes constituant une base de données sont regroupées dans une entité appelée schéma. Un schéma est créé par la commande:
create schema nom_schema.
ATTENTION: Dans les version >= 4.6 de O2 il faut exécuter status TEST in base <nom_base> dans la fenêtre O2Shell pour pouvoir modifier le schéma ensuite.

Il peut ensuite y avoir plusieurs bases par schémas. Toutes les bases rattachées à un même schéma auront donc en commun les mêmes classes Une base est créée par la commande:
create base nom_base
Quand on lance une session O2, on définit le schéma et la base dans laquelle on souhaite travailler:
set schema nom_schema
set base nom_base
Toutes les commandes suivantes se feront dans le cadre de la base et du schéma choisi. Les commandes de création de classe viendront notamment augmenter le schéma.

En plus des définitions de classes, un schéma contient les éléments suivants: objets et valeurs nommés, transactions, applications.

Objets et valeurs nommés :
La seule façon d'assurer la persistance des informations dans O2 est de les rattacher à un nom. Un nom est en fait un identifiant permanent géré par le SGBD. Un nom peut faire référence à des objets ou à des valeurs. Exemple:
create name Population: set (Person)
create name pi: real
La gestion de la persistance d'un ensemble d'objets en les rattachant à un nom est la manière courante de stocker des objets dans O2. On peut effectuer les opérations suivantes sur le nom Population:

3.8   O2C

Le langage O2C est une extension du langage C. Les ajouts concernent principalement: Pour les deux dernières, voir la documentation O2. Voici brièvement une description de la gestion des objets en O2C.

Utilisation des types O2 :
La déclaration d'une variable dont le type est défini par O2 doit être préfixée par le mot réservé o2. La syntaxe est
o2 type_spec nom_variable [= valeur_initiale].
Exemples:
o2 set(integer) tierce_gagnant = (1, 12, 4);
o2 Person Joe, Tom;
o2 tuple (nom: string, age: integer);
Manipulation d'objets et de valeurs 
Les types integer, real et char se manipulent comme leurs équivalents int, double et char en C.

Par contre les variables de type string ne sont pas des variables C de type char *. O2 définit un ensemble d'opérateurs sur le type string.

Les variables de type boolean peuvent être employées dans des expressions conditionnelles du C. Elles prennent les valeurs false ou true.

Les variables de type tuple sont équivalentes aux variables de type struct en C.

Les variables qui font référence à un objet sont équivalentes à des pointeurs vers des structures en C.

Pour les variables de type set et de type list, O2 redéfinit un ensemble d'opérateurs.


Précédent Suivant Index