• Aucun résultat trouvé

Programmation orientée objet Classe et structure

Tout code exécutable doit être écrit à l'intérieur d'une procédure ou d'une fonction, c’est connu. Ce qu'il faut encore dire, c'est que toute procédure ou fonction doit être écrite à l'intérieur d'un module, d'une structure ou d'une classe. Les exemples de programmation à l'intérieur d'un module sont déjà nombreux. En ce qui concerne la structure, elle a été étudiée en tant que moyen d'organisation des données et il est nécessaire de compléter l'information à son sujet. Une classe peut être définie en dehors de toute autre structure de programmation, auquel cas elle est obligatoirement

Public, ou définie comme sous classe à l'intérieur d'une autre classe, où le programmeur peut lui donner la portée qu'il souhaite. Elle peut également être définie à l'intérieur d'un module où elle peut être Public, Friend ou Private, mais dans la mesure où un module n'est pas accessible par instanciation ou héritage dans une application cliente, les portées Public et Friend sont tout à fait équivalentes. Une classe peut aussi être définie à l'intérieur d'une structure. Deux attributs peuvent être associés à la déclaration de portée de la classe pour préciser son comportement en matière d'héritage : MustInherit et NotInheritable.

MustInherit Indique que la classe sert de classe de base dans une relation d'héritage et elle ne peut être instanciée. En général, dans une telle classe, seules les déclarations des méthodes sont définies. Le contenu doit être rédigé dans les classes dérivées.

NotInheritable Indique que la classe est la dernière de la hiérarchie. Aucune autre classe ne peut en dériver.

Public Class MaClasse

' Inherits UneClasseMere ' Implements UneInterface ' Implements UneAutreInterface

Public Function MonCinq() As Integer Return 5 End Function End Class Module MonModule Sub Main() Dim MC As MaClasse MC = New MaClasse Console.WriteLine(MC.MonCinq) Console.ReadLine() End Sub End Module

La classe MaClasse écrite en dehors de toute autre structure est bien Public et elle expose une méthode, la fonction

MonCinq. Les lignes en commentaires ne sont évidemment pas utiles ici, sauf pour présenter l'endroit où il faut, le cas échéant, déclarer une classe mère et des interfaces. L'utilisation du mot clé Inherits précède la classe dont hérite celle en cours. Il ne peut y avoir qu'une seule ligne de ce type dans une classe. Le mot clé Implements précède le nom d'une interface souhaitée. Il peut y en avoir plusieurs par classe. Une interface ressemble un peu à une classe mais elle contient uniquement des descriptions de propriétés et de méthodes, sans code à l'intérieur, ni même de fin de code (End Sub, EndFunction). Implémenter une interface revient à s'engager à en écrire le contenu pour toutes les méthodes et propriétés. L'usage de l'interface est étudié plus loin.

L'application cliente de MaClasse est le module MonModule. Dans sa procédure Main, la déclaration Dim MC As MaClasse attribue une variable pour la manipulation de l'objet. L'affectation de cette variable est réalisée par l'appel du constructeur de la classe qui réalise l'instanciation de l'objet, c'est-à-dire sa création en mémoire. Il faut remarquer au passage que le code du constructeur New de la classe n'a pas été écrit. Dans ce cas, le compilateur en crée un d'office ou plus exactement, il en recherche un en remontant la généalogie des classes jusqu’à la classe Object s’il le faut.

L'exemple suivant illustre la création d'une sous classe et celle d'une classe à l'intérieur d'un module, ainsi que leur instanciation.

Public NotInheritable Class MaClasse ' Nul ne pourra en hériter

Private Shared UneValeur As Integer ' Une variable de classe

Public Function MaFonction1() As Integer ' Une méthode

UneValeur = 5

Return UneValeur ' Retourne la valeur de la variable de classe

End Function

Public Function MaFonction2() As Integer ' Une autre méthode

Dim C As UneSousClasse

C = New UneSousClasse ' Instanciation d'une sous classe

Return C.UneFonction / 2 ' Retourne la valeur reçue d'une méthode

End Function ' de la sous classe après l'avoir divisée

Public Class UneSousClasse ' Définition de la sous classe UneSousClasse

Public Function UneFonction() As Integer UneValeur = 10

Return UneValeur * 2 ' La méthode retourne un entier

End Function

End Class

End Class

Module MonModule

Public Class ClasseModule ' Définition d'une classe dans le module

Public Function UneAutreFonction() As String

Return "MomMessage" ' La méthode retourne une chaîne

End Function

End Class

Sub Main()

Dim MC As MaClasse ' Une variable pour MaClasse

Dim MSC As MaClasse.UneSousClasse ' Une variable pour accéder à la sous classe

Dim MCM As ClasseModule ' Une variable pour ClasseModule

MC = New MaClasse ' Création d'un objet MaClasse

MSC = New MaClasse.UneSousClasse ' Création d'un objet UneSousClasse

MCM = New ClasseModule ' Création d'un objet ClasseModule

Console.WriteLine(MC.MaFonction1) ' Affiche 5

Console.WriteLine(MC.MaFonction2) ' Affiche 10

Console.WriteLine(MSC.UneFonction) ' Affiche 20

Console.WriteLine(MCM.UneAutreFonction) ' Affiche MonMessage

Console.ReadLine() End Sub

End Module

Tout fonctionne aussi bien, dans les exemples précédents quand le mot clé Class est remplacé par le mot clé

Structure. Cependant, il y a des différences et notamment celles-ci :

Un type Structure est de type valeur alors qu'une classe est de type référence. Un type Structure doit déclarer au moins une variable membre ou un événement.

Un type Structure peut déclarer un constructeur, mais alors il doit obligatoirement être paramétré. Un type Structure ne peut pas servir de base pour un autre qui en hériterait.

Un type Structure ne peut pas spécifier une classe dont il hériterait. Un type Structure ne peut pas déclarer un destructeur.

Le premier exemple :

Public Structure MaStructure

' Implements UneInterface ' L'implémentation d'interfaces est permise

Dim UneVariable As Integer ' Une structure doit contenir au moins une

Public Function MonCinq() As Integer ' variable membre.

Return 5 End Function End Structure Module MonModule Sub Main() Dim MS As MaStructure

' MS = New MaStructure ' Instanciation superflue. Toutefois, les

Console.WriteLine(MS.MonCinq) ' membres de type référence doivent être

Console.ReadLine() ' initialisés

End Sub End Module

Le deuxième exemple :

Public Structure MaStructure

Private Shared UneValeur As Integer ' Ne compte pas comme variable membre

Private XMS As Integer ' Voici une variable membre

Public Function MaFonction() As Integer ' Une fonction membre qui utilise la fonction

Dim C As UneSousStructure ' membre d'une sous structure

' C = New UneSousStructure ' Instanciation superflue.

Return C.UneFonction / 2 End Function

Public Structure UneSousStructure ' Définition de la sous structure et de ses

Dim XMSS As Integer ' membres, dont une fonction

Public Function UneFonction() As Integer UneValeur = 10

Return UneValeur * 2 End Function

End Structure

Public Class ClasseStructure ' Définition d'une classe dans la structure

Public Function UneFonction() As Integer ' et d'une méthode UneFonction

Dim X As Integer = 30 Return X End Function End Class End Structure Module MonModule

Public Structure StructureModule ' Définition d'une structure dans le module

Dim XSM As Integer ' et de ses membres, dont une fonction

Public Function UneAutreFonction() As String Return "MomMessage" End Function End Structure Sub Main() Dim MS As MaStructure Dim MSS As MaStructure.UneSousStructure Dim MSM As StructureModule Dim MCS As MaStructure.ClasseStructure

MCS = New MaStructure.ClasseStructure ' Une classe est un objet de type référence

Console.WriteLine(MS.MaFonction) ' Affiche 10

Console.WriteLine(MSS.UneFonction) ' Affiche 20

Console.WriteLine(MSM.UneAutreFonction) ' Affiche MonMessage

Console.WriteLine(MCS.UneFonction) ' Affiche 30

Console.ReadLine() End Sub

Un dernier exemple pour illustrer encore les similitudes existant entre les Class et les Structure, mais aussi l'une de leurs différences, ainsi que l'emploi d'un constructeur personnalisé.

Public Structure MaStructure ' Définition d'une structure MaStructure et

Public XMS As Integer ' de ses membres, soit une variable de type

Public Tab1() As Integer ' entier, un tableau d'entiers, une variable

Public Classe As ClasseEnStructure ' de type objet ClasseEnStructure

Public Sub New(ByVal NbEntiers As Integer) ' La structure déclare un constructeur dont

Tab1 = New Integer(NbEntiers -1) {} ' le rôle est de créer le tableau pour un

Classe = New ClasseEnStructure ' nombre d'entiers passé en paramètre et

End Sub ' de créer l'objet pour la variable Classe

Public Class ClasseEnStructure

Public Tab2 = New Integer(5) {} ' La classe expose un tableau pour 6 entiers

End Class End Structure

Public Class MaClasse ' Définition d'une classe MaClasse et

Public XMC As Integer ' de ses membres, soit une variable de type

Public Tab1() As Integer ' entier, un tableau d'entiers, une variable

Public Classe As ClasseEnClasse ' de type objet ClasseEnClasse

Public Sub New(ByVal NbEntiers As Integer) ' La structure déclare un constructeur dont

Tab1 = New Integer(NbEntiers -1) {} ' le rôle est de créer le tableau pour un

Classe = New ClasseEnClasse ' nombre d'entiers passé en paramètre et

End Sub ' de créer l'objet pour la variable Classe

Public Class ClasseEnClasse

Public Tab2 = New Integer(5) {} ' La classe expose un tableau pour 6 entiers

End Class End Class

Jusqu'ici, il n'y a pas de différences importantes entre les approches Structure et Class.

Module MonModule Sub Main()

Dim MS, MS2 As MaStructure ' Déclaration des variables pour MaStructure

Dim MC, MC2 As MaClasse ' Déclaration des variables pour MaClasse

MS = New MaStructure(5) ' Création d'une MaStructure en mémoire

MS.XMS = 123 ' Affectation d'un membre de MaStructure

MS.Tab1(1) = 10 ' Affectation d'un autre membre

MC = New MaClasse(5) ' Création d'une MaClasse en mémoire

MC.XMC = 456 ' Affectation d'un membre de MaClasse

MC.Tab1(1) = 100 ' Affectation d'un autre membre

MS2 = MS ' Affectation d'une autre MaStructure par MS

MS2.XMS = 321 ' Modification des valeurs de cette variable

MS2.Tab1(1) = 101 '

MC2 = MC ' Affectation d'une autre MaClasse par MC

MC2.XMC = 654 ' Modification des valeurs de cette variable

MC2.Tab1(1) = 1001 ' Console.WriteLine(MS.XMS) ' Affiche 123 Console.WriteLine(MS2.XMS) ' Affiche 321 Console.WriteLine(MS.Tab1(1)) ' Affiche 101 Console.WriteLine(MS2.Tab1(1)) ' Affiche 101 Console.WriteLine(MC.XMC) ' Affiche 654 Console.WriteLine(MC2.XMC) ' Affiche 654 Console.WriteLine(MC.Tab1(1)) ' Affiche 1001

Une différence !

Console.WriteLine(MC2.Tab1(1)) ' Affiche 1001 End Sub End Module

La seule différence constatée dans l'exemple précédent réside apparemment dans le fait que la variable de type valeur est bien traitée comme telle dans la structure et non dans la classe. En réalité, c'est la structure elle-même qui est de type valeur et lors de l'affectation de MS à MS2, toutes les valeurs de la première sont copiées dans la deuxième. Dans le cas de l'objet de type MaClasse, seule la référence de MC est copiée dans MC2 lors de l'affectation, et c'est bien normal puisqu'une classe est de type référence.

Dès lors, et en première approche, il est étonnant que les tableaux de la structure ne soient pas simplement copiés eux aussi, qu'ils soient traités comme dans la classe. Il faut se souvenir qu'un tableau est de type référence et que seule la référence est conservée dans la variable. Ainsi, tout s'explique.

Donc, dans la structure comme dans la classe, seule la référence d'un membre de type référence est conservée dans la variable correspondante et dans le cas de la structure, cette référence est bien copiée par l'affectation, tandis que dans le cas de la classe, seule la référence de l'instance de la classe elle-même est copiée.

Le choix entre Class et Structure est simple dès lors qu’est abordée la programmation orientée objet. Comme la structure ne peut hériter d'une classe et qu'aucune classe ou autre structure ne peut en dériver, il est évident qu'elle n'intègre pas le concept d'héritage de la POO, ni donc celui du polymorphisme. La structure est donc à rejeter de la POO, sauf pour organiser des lots de données sous la forme de types complexes définis par le programmeur.

La classe intègre tous les concepts de la POO, elle est maintenant le principal objet de ces pages.

Gestion de la mémoire

La déclaration d’un objet de type valeur provoque la réservation en mémoire de l’emplacement nécessaire pour stocker la ou les valeur(s) de cet objet. L’initialisation par défaut de la variable le représentant, est la valeur nulle selon le type. L'opération d'affectation entre objets de ce type copie toutes les valeurs de l'objet. Les valeurs de ce type sont stockées dans la pile ou stack. Les données de la pile sont dépilées et perdues quand l'application quitte la portée qui leur a été allouée. L'espace occupé dans la pile est celui nécessaire au stockage des valeurs des variables.

La déclaration d’un objet de type référence provoque la réservation en mémoire de l’emplacement nécessaire pour stocker l’adresse où sera stocké cet objet. L’initialisation par défaut de la variable le représentant, qui est donc un pointeur, est Nothing. Ce n'est qu'à la création effective de l'objet que cette variable reçoit l'adresse de l'objet.

Une variable de type référence doit être initialisée par des valeurs ou par l’usage de l’opérateur New pour permettre au compilateur d’évaluer et de réserver l’espace nécessaire en mémoire. Quand cette variable représente l'instance d'une classe, New, qui est le constructeur de toute classe, est le seul opérateur habilité à l'initialiser. L'opération d'affectation entre objets de ce type ne copie que l'adresse mémoire de l'objet, sa référence. Les références des objets sont stockées dans la pile et leurs valeurs dans le tas ou heap. Comme pour les objets de type valeur, les références dans la pile sont dépilées et perdues quand l'application quitte la portée qui leur a été allouée. Cependant, ce processus ne libère pas l'espace occupé dans le tas par les objets. Cette libération de la mémoire est réalisée automatiquement par le ramasse- miettes ou garbage collector. L'affectation de la valeur Nothing à une référence n'influence en rien l'occupation de la mémoire, aussi bien côté pile que côté tas. L'espace occupé dans la pile est celui nécessaire au stockage d'une adresse, soit quatre ou huit octets selon le système, et celui occupé dans le tas est l'espace nécessaire pour les données des objets.

Les codes, comme ceux des méthodes par exemple, sont stockés en d'autres endroits de la mémoire et n'affectent ni la pile, ni le tas.

Quelque part dans la pile Quelque part dans le tas

Opérations Etiquettes Adresses

supposées Valeurs Adresses supposées Valeurs … Dim XN As Integer XN 14000000H 0 XN = 5 XN 14000000H 5

Dim MC As MaClasse MC 13FFFFFCH Nothing

MC = New MaClasse MC 13FFFFFCH 10000000H 10000000H Début d'un objet

MaClasse Dim MC2 As MaClasse MC2 13FFFFF8H Nothing

MC2 = MC MC2 13FFFFF8H 10000000H

Dim T() As Integer T 13FFFFF4H Nothing

T = New Integer(5) {} T 13FFFFF4H 10001000H 10001000H Espace pour

6 entiers

T(1) = 7 T 13FFFFF4H 10001000H 10001004H 7