Le modèle objet de Gambas

1. Les objets & classes

Un objet en Gambas est une structure de données qui fournit des propriétés, des variables, des méthodes et des évènements.

L'accès aux objets se fait "par référence", c.a.d. en utilisant un pointeur, ou en utilisant une variable dont la valeur est l'adresse mémoire de la structure de données de l'objet.

Vous pouvez voir l'adresse d'un objet en utilisant l'instruction PRINT :

DIM aStr AS NEW String[]

PRINT aStr

(String[] 0x80dccf8)

La structure de données de l'objet est décrite par une classe.

1.1. Les classes

Donc chaque objet Gambas a une classe qui décrit toutes ses propriétés, méthodes et évènements publics. Cette classe est aussi un objet Gambas, dont la classe est la classe nommée Class.

Une classe statique est une classe dont tous les membres sont statiques (voir ci-dessous). En Gambas, une classe statique est aussi nommée module.

Une classe statique ne peut pas être instanciée : elle créerait un objet sans variable dynamique, ce qui est inutile.

Exemple

La classe System est une classe statique : toutes ses méthodes sont statiques, et vous ne pouvez pas créer un objet dont la classe serait System.

Une classe virtual est une pseudo-classe cachée que vous ne pouvez pas manipuler explicitement.

1.2. Les propriétés, méthodes et variables.

Les propriétés, les méthodes permettent de manipuler les structures de données.

Une Propriété, une Méthode ou une Variable peut être statique:
  • Une variable statique sera partagée par toutes les instances de la même classe.

  • Une propriété ou méthode statique ne peut modifier que les variables statiques.

Une méthode ou une variable peut être soit publique soit privée. Une propriété est toujours publique.

Les symboles privés ne peuvent être utilisés que depuis l'intérieur d'une classe.

Les symboles publics peuvent être utilisés partout, à condition que vous ayez une référence pointant sur l'objet.

1.3. Références

Il n'y a pas de garbage collector en Gambas. Aussi chaque objet a un compteur de références qui est incrémenté chaque fois que l'objet est référencé par n'importe quelle variable, tableau, collection ou autre objet, et décrémenté quand il est libéré.

Ce compteur de références est mis à zéro lors de la création de l'objet, et quand il revient à zéro après la libération d’une référence, l'objet est libéré.

1.4. Les objets invalides

Un objet peut devenir invalide, lorsque, par exemple, il est relié à un objet interne non géré par Gambas et qui a été détruit.

Essayer d'utiliser un objet invalide provoque une erreur.

1.5. Les méthodes spéciales

Les méthodes spéciales sont des méthodes déclarées dans les classes, dont le nom commence avec un caractère _, et qui est appelée par l'interpréteur dans les situations suivantes :
  • Quand un objet est créé.

  • Quand un objet est libéré.

  • Quand la classe de l'objet est chargée.

  • Quand la classe de l'objet est déchargée.

  • En utilisant l'objet comme si c’était un tableau.

  • En énumérant l'objet.

  • En énumérant un objet comme si c’était une fonction.

  • En essayant d'utiliser une méthode ou une propriété inconnue.

Voir /wiki/api/cat/special pour plus d'informations.

2. Événements & Observateurs

2.1. Les événements

Les événements sont des signaux envoyés par un objet quand quelque chose se passe sur lui.

Si un objet lève des événements, il tiendra une référence sur son observateur par défaut, ou son objet parent.

Cet observateur par défaut est un autre objet qui implémente des gestionnaires d'événements. Un gestionnaire d'événement est juste une méthode publique appelée à chaque fois que l’événement survient.

Par défaut, l'observateur est l'objet courant où le nouvel objet instancié est déclaré.

Pour générer les événements, un objet doit avoir un nom d'événement. Ce nom d'événement est assigné à l'instanciation de l'objet, quand l'instruction NEW et le mot-clé AS sont utilisés. Il est le préfixe de toutes les gestionnaires d'événement.

Exemple

Ceci crée un contrôle Button qui lèvera des événements.

DIM hButton AS Button

hButton = NEW Button(ME) AS "ButtonEventName"

Public Sub ButtonEventName_Click()

  Print "This is the 'Click' event handler for the previous button."

End

Si aucun nom d'événement n'est indiqué, alors l'objet ne lèvera aucun 'événement.

2.2. Objet parent par défaut (ou observateur par défaut)

Certains objets peuvent avoir un objet parent par défaut si vous ne le définissez pas en spécifiant un nom d'évènement avec la syntaxe As "xxx".

Pour le moment, les seuls objets ayant un objet parent par défaut sont les formulaires, qui s'utilisent eux-mêmes comme objet parent par défaut.

Ceci est conçu pour mimer le comportement de Visual Basic&trade ; et aider les anciens développeurs VB.

Si vous créez un Formulaire et spécifiez son nom d'évènement avec la syntaxe As "xxx", ce comportement par défaut sera surchargé.

2.3. Verrouillage des objets.

Un objet peut être verrouillé de telle sorte qu'il suspende l’émission de ses événements, et peut être débloqué afin qu'il les lève de nouveau. Reportez-vous aux méthodes Object.Lock et Object.Unlock.

Certains événements peuvent être annulés par le gestionnaire d'événement, en utilisant l'instruction STOP EVENT. L'effet de cette annulation dépend de l’événement.

Un objet est automatiquement verrouillé pendant l’exécution de son constructeur : il ne peut ni envoyer, ni recevoir d’événement.

2.4. Les observateurs

Les observateurs sont des objets qui vous permettent d’intercepter les événements levés par d’autres objets. Ils les "observent".

Vous pouvez intercepter les événements juste avant qu’ils soient levés, ou juste après.

Pour chaque événement intercepté, l’observateur provoquera un événement du même nom avec les mêmes arguments. Ces évènements sont levés en plus de l'évènement originel géré par l'objet parent.

Si vous utilisez STOP EVENT dans un observateur gestionnaire d’évènement, l’évènement originel est annulé.

Exemple

PRIVATE $hButton as Button
PRIVATE $hObserver AS Observer

PUBLIC SUB Form_Load()

  $hButton = NEW Button(ME) AS "Button"
  $hObserver = NEW Observer(hButton) AS "Observer"

END

PUBLIC SUB Observer_Click()

  DEBUG "Le bouton a été cliqué. J’annule l’évènement !"
  STOP EVENT

END

PUBLIC SUB Button_Click()

  DEBUG "Vous ne devriez pas me voir."

END

3. Héritage

L'héritage est une manière pour une classe de devenir une version spécialisée d'une autre classe.

3.1. Qu'est-ce qui est hérité ?

La classe hérite de tous les méthodes, propriétés, constantes et évènements de son parent.

Vous devez utiliser le mot-clé ME pour accéder aux éléments hérités depuis l'intérieur de la classe.

3.2. Quelles classes peuvent être une classe parent ?

Vous pouvez hériter de n'importe quelle classe, y compris une classe native.

Par exemple, vous pouvez créer une classe personnalisée MaListBox qui hérite de ListBox, mais qui permet d'associer une étiquette (un tag) à chaque objet de la liste.

Notez que vous ne pouvez pas utiliser INHERITS dans un fichier de classe de formulaire, parce que les formulaires héritent déjà de la classe Form.

La profondeur de l'arbre d'héritage ne peut pas être plus supérieure à 16. C'est une constante codée en dur dans l'interpréteur Gambas.

3.3. Le dispatching virtuel

Lorsqu'on appelle une méthode ou lorsqu'on accède à une propriété d'une référence d'un objet, Gambas utilise toujours le dispatching virtuel.

Cela signifie que la classe réelle de l'objet est toujours utilisée, et non pas le type de la variable qui référence l'objet - Comme c'était malencontreusement le cas pour Gambas 1.0.

3.4. Héritage et constructeur

Attention ! Contrairement à tous les langages objets que je connais, chaque classe dans la hiérarchie d'héritage consomme les paramètres passés au constructeur.

Supposons que nous ayons l'arbre d'héritage suivant :

MaListeBox ---hérite de--> ListBox ---hérite de---> Control

  • Control._new() n'existe pas.

  • ListBox._new() prend un paramètre : le contrôle parent.

  • MyListBox._new() prend un paramètre : un nom - C'est juste un exemple.

Donc NEW MaListeBox prendra deux paramètres.

  • Le premier sera envoyé à ListBox._new().

  • Le second à MaListeBox._new().

Attention : le ListBox._new() sera appelé en premier, afin d'assurer que le contrôle ListBox existe quand vous êtes dans MaListeBox._new().

Ensuite vous créez un contrôle MaListeBox de cette manière :

hMaListeBox = NEW MaListeBox(hContainer, "Nom")

De sorte que l'ordre des arguments est le suivant :

  • Les arguments obligatoires sont consommés en premier, puis les arguments optionnels s’ils sont disponibles.

  • les arguments des plus anciennes classes sont spécifiés en premier !

En Gambas 2.0, l'ordre des arguments est inversé !

Par exemple, si vous avez l’héritage suivant :

MyForm --> Form --> Window

Avec le constructeur de MyForm suivant :

Sub _new(FirstArg As String, SecondArg as Integer, Optional ThirdArg As Integer)

Note : le constructeur de Form ne prend pas d’argument, et le constructeur de Window prend un argument parent optionnel.

La signature du constructeur final sera :

New MyForm(FirstArg As String, SecondArg As Integer, Optional Parent As Control, Optional ThirdArg As Integer)

D’une manière plus générale, l’ordre des arguments pour un héritage à trois niveaux est :

  • Arguments obligatoires du constructeur grand-parent.

  • Arguments obligatoires du constructeur parent.

  • arguments obligatoires du constructeur final.

  • Arguments optionnels du constructeur grand-parent.

  • Arguments optionnels du constructeur parent.

  • Arguments optionnels du constructeur final.

3.5. Surcharge des symboles

Quand un symbole est surchargé, la signature du symbole dans la classe fille doit être la même que celle du symbole dans la classe parent.

Les règles sont :
  • Un symbole dynamique doit être surchargé par un symbole dynamique, un symbole statique par un symbole statique.

  • Une méthode doit être surchargée par une méthode avec exactement la même signature (mêmes types de donnée des arguments, même type de donnée de la valeur retournée, s'il y en a une).

  • Une propriété en lecture/écriture doit être surchargée par une propriété en lecture/écriture du même type de donnée.

  • Une propriété en lecture seule doit être surchargée par une propriété en lecture seule avec le même type de donnée.

  • Une constante peut être surchargée par une constante du même type de donnée.

4. Les composants

Les composants Gambas sont des bibliothèques partagées externes écrites en C/C++ ou en Gambas, qui ajoutent de nouvelles fonctions ou de nouvelles classes à l'interpréteur.

Les Classes sont regroupées selon le composant d'où elles proviennent.

4.1. Le composant interne par défaut

L'interpréteur inclut un composant interne nommé gb qui définit toutes les classes standard du langage.

Ce composant est toujours chargé par défaut, et peut être considéré comme faisant partie du langage.

4.2. Les tables de symboles

Chaque composant a sa propre table de symboles de classes, de sorte que les noms de classes n’entrent pas en conflit.

4.3. La table de symbole globale

Pour que les composants puissent fonctionner ensemble, il y a une table de symboles globale, où toutes les classes exportées par les composants et les classes exportées par le projet actuel sont rangées.

S’il y a un conflit de nom dans cette table de symbole globale, la dernière classe chargée surcharge la classe du même nom chargée précédemment, en utilisant l'héritage. En d’autres termes, la classe ‘surchargeante’ étend celle qui est surchargée.

Cette dernière caractéristique peut être utilisée pour :
  • Etendre une classe déjà déclarée en ajoutant de nouvelles méthodes ou propriétés à cette dernière. Par exemple, la classe Application gb.qt4 ré-implémente la classe Application gb.

  • Surcharger les méthodes d'une classe déjà déclarée. Par exemple, le composant gb.form.dialog remplace la plupart des méthodes statique de la classe Dialog.

4.4. La table de symboles du projet

Votre projet a son propre symbole privé, comme n'importe quel composant, et peut exporter vers la table de symboles globale n'importe laquelle de ses classes en utilisant le mot-clé EXPORT,

Les classes du projet sont chargées après l'ensemble des composants. Ainsi vos classes exportées peuvent surcharger des classes exportées déclarées dans un composant.

Voir aussi