TP : Programmation orientée objet et VB.
NET
M. Ngandeu
Nous avons vu dans le cours la théorie concernant la programmation orientée objet, mettons tout cela en pratique
!
Nous allons voir créer notre première classe en Visual Basic .NET, puis comment lui ajouter des attributs et des
méthodes, d'autant plus de paramètres qui détermineront la complexité de votre classe.
Pour finir et vous récompenser de votre assiduité, nous allons mettre en pratique le concept de classes pour vous
montrer l'utilité concrète de ces dernières.
I. Notre première classe
Pour commencer notre entrée dans le monde des classes, je vous propose d'attaquer tout de suite sur un petit
thème. Pourquoi pas Mario ? Nous allons essayer de faire bouger Mario pour commencer.
Logiquement, puisque nous sommes sur la partie traitant de la POO, notre Mario sera… un objet !
1. La classe
Créez donc un nouveau projet, Windows Forms. Ajoutez ensuite un nouvel élément, comme ce que nous avons
fait pour une fenêtre, mais cette fois choisissez Classe, comme le montre la figure suivante.
Nommez tout de suite ce fichier, « Mario » par exemple. Nous voilà donc avec notre classe totalement vide, juste
la déclaration de début et de fin.
2. L'interface
Retournons dans notre fenêtre et ajoutons-y quelques contrôles pour pouvoir commencer (voir figure suivante).
Donc j'ai juste ajouté un petit panel dans lequel j'ai spécifié une taille de 20 x 20, ainsi qu'une couleur de fond
rouge. J'ai ajouté en bas un petit bouton qui va nous permettre de déplacer ce panel. Pour le moment uniquement
vers la droite.
Pour le moment je ne vous demande pas de créer une classe vous-mêmes, nous n'avons pas encore vu comment
faire, nous allons le faire ensemble. Je vous demande juste d'essayer de trouver comment rendre cet objet
adaptable.
Mario doit être capable de se déplacer par « cases ». Pour le moment la taille de la case est définie par la taille du
panel. Donc si mon panel devient de 50 x 50, une case sera égale à 50 x 50.
Notre classe ne pourra pas agir directement sur notre panel, c'est toujours le code qui l'a appelé qui va devoir le
modifier, notre classe va donc devoir nous renvoyer des coordonnées. Les coordonnées de la nouvelle position de
Mario.
Donc si vous avez suivi ma réflexion, nous allons devoir travailler avec des variables de type Point, nous aurions
pu nous envoyer des Int contenant la position en X et en Y, mais la variable Point rassemble le tout. Nous allons
aussi devoir passer à la classe la taille de notre panel, pour qu'elle puisse travailler dynamiquement, quelle que
soit la taille de notre Mario.
3. Notre classe
Dans notre fichier de classe il va falloir tout d'abord définir un constructeur. Rendez-vous dans le fichier [Link]
(la classe) et insérez la déclaration du constructeur (avec New). On se place dans le Class Mario et on inscrit :
Sub New()
End Sub
Voilà notre premier constructeur. Il ne demande aucun argument. Nous ferions tout de même bien de spécifier à la
classe la position originelle de notre Mario et sa taille. Pour cela commençons par déclarer des variables en privé,
car elles ne seront accessibles qu'à partir de la classe.
Juste en dessous de Public Class Mario, déclarez :
Private _CoordonneesActuelles As Point
Private _Taille As Size
Changeons notre constructeur de manière à demander en arguments ces valeurs :
Sub New(ByVal PositionOriginelle As Point, ByVal TailleMario As Size)
_CoordonneesActuelles = New Point(PositionOriginelle)
_Taille = New Size(TailleMario)
End Sub
Et entrons-les dans les variables. Vu que Point et Size sont eux-mêmes des objets, il faut les instancier avec New.
Nous voilà donc avec la taille et les coordonnées de notre Mario.
4. Des méthodes et des attributs
Je rappelle rapidement ce que sont les méthodes et les attributs. Les méthodes sont des fonctions de type privé,
leur nécessité est interne, rien n'est visible depuis l'extérieur. Les attributs sont publics, visibles depuis l'extérieur,
accessibles.
Codons quelques fonctions qui vont nous permettre de déplacer notre Mario. À première vue, rien de
problématique, on va jouer sur la propriété .X de nos coordonnées pour déplacer Mario en horizontal, .Y pour le
vertical.
C'est là que notre notion de « case » et de taille intervient. On va le faire bouger d'une case.
Créons une nouvelle fonction de type Public. Cette fonction sera destinée à faire avancer Mario d'une case.
Appelons-la donc Avance.
Public Sub Avance()
_CoordonneesActuelles.X = _CoordonneesActuelles.X + _PasX()
End Sub
_Pas est une petite fonction qui nous renvoie la taille en longueur de Mario (pour savoir quelle est la valeur de la
case en longueur).
#Region "Fonctions privées"
Private Function _PasX()
Return _Taille.Width
End Function
Private Function _PasY()
Return _Taille.Height
End Function
#End Region
Je renvoie simplement la longueur, et pour le _PasY, c'est la hauteur. Si vous avez suivi, cette méthode publique
va déplacer Mario d'une case sur la droite. Mais ce n'est pas une fonction, on n'a aucun retour, comment est-ce
que notre panel va bien pouvoir se déplacer ? Il va falloir se servir des propriétés.
5. Les propriétés
Vous savez déjà ce que sont les propriétés, vous en assignez tout le temps quand vous modifiez vos contrôles. On
va apprendre à faire de même pour nos objets.
La syntaxe de déclaration d'une propriété est assez particulière. Il va falloir gérer lorsqu'on assigne cette propriété
et lorsqu'on la récupère. Pour commencer, voici la syntaxe :
Public Property Position()
Get
End Get
Set(ByVal value)
End Set
End Property
Vous voyez qu’elle fonctionne comme une fonction sauf qu'à l'intérieur on a deux nouveaux mots-clés :
Set : sera appelée lorsque l'on assignera une valeur à cette propriété ;
Get : sera appelée lorsque l'on demandera une valeur à cette propriété.
Pour le moment l'argument fourni au Set n'a pas de type défini, ce qui est renvoyé non plus. Commençons par les
définir :
Public Property Position() As Point
Get
End Get
Set(ByVal value As Point)
End Set
End Property
On attend donc un Point et on renvoie un Point.
Notre propriété va seulement assigner la valeur à la variable _CoordonneesActuelles et renvoyer sa valeur, mais
on aurait pu effectuer beaucoup d'autre choses en même temps. Assigner plusieurs variables, incrémenter un
compteur, les possibilités sont infinies.
Voici donc la propriété au final :
Public Property Position() As Point
Get
Return _CoordonneesActuelles
End Get
Set(ByVal value As Point)
_CoordonneesActuelles = value
End Set
End Property
On assigne la valeur, on la retourne.
Avec cette nouvelle propriété et la fonction précédente, on peut désormais retourner sur notre code relatif à la
fenêtre et faire bouger Mario simplement en quelques lignes :
Public Class Jeu
'Mario déclaré en global
Dim MonMario As Mario
Private Sub Jeu_Load(ByVal sender As [Link], ByVal e As [Link]) Handles
[Link]
'Un nouveau Mario
MonMario = New Mario(Me.PAN_MARIO.Location, Me.PAN_MARIO.Size)
End Sub
Private Sub BT_AVANCE_Click(ByVal sender As [Link], ByVal e As [Link])
Handles BT_AVANCE.Click
'On le fait avancer
[Link]()
'On recupère la nouvelle position
Me.PAN_MARIO.Location = [Link]
End Sub
End Class
6. Exercices
a) Ajouter les commandes manquantes
b) Ajouter la possibilité d’utiliser les touches du clavier pour bouger l’objet Mario
Pour ce faire, vous pouvez utiliser la méthode Sub Form1_KeyDown() du formulaire
Vous devez au préalable vous mettre à l’écoute des touches au chargement via [Link] = True
Vous pouvez vérifier la touche pressée (par exemple Z) en faisant par exemple [Link] = Keys.Z
II. Concepts de la POO
1. L'héritage
L’héritage donne possibilité de créer une classe à partir d’une autre tout en gardant les mêmes propriétés.
Par exemple,
Cette définition de classe sera identique à celle-ci.
Nous allons devoir apprendre à jongler entre les membres appartenant à la classe fille ou à la classe mère au
travers de préfixes (du même type que Me).
Ce mot est MyBase.
Dans le schéma visible à la figure suivante, on se place du point de vue de la classe fille. Si depuis cette classe fille
on débute une ligne par Me., les membres auxquels nous pourrons accéder seront ceux de la classe fille et de
toutes les classes dont elle hérite. En revanche, en utilisant MyBase. nous accèderons uniquement aux membres
de la classe mère.
Exemple :
Public Class Instrument
Private _Notes() As Integer
Sub New(ByVal Notes() as Integer)
_Notes = Notes
End Sub
End Class
Public Class Guitare
Inherits Instrument 'Hérite d'Instrument
Private _NbCordes As Integer
Sub New(ByVal Notes() As Integer, ByVal NbCordes As Integer)
[Link](Notes) 'On instancie la mère
_NbCordes = NbCordes
End Sub
End Class
2. Les classes abstraites
Une classe abstraite est une classe ne pouvant pas être instanciée, autrement dit on ne peut pas créer d'objet à
partir de ce moule.
Elle va permettre de créer des classes dérivées. En clair, cette classe va seulement servir de base (une classe
mère) pour des classes qui vont en dériver. Comme notre exemple sur les instruments. Une guitare, un piano,
bref des instruments concrets, nous allons les instancier et les utiliser. Cependant la classe Instrument, la classe
mère de toutes les autres, nous aurions pu la définir en classe abstraite.
Exemple :
Public MustInherit Class Instrument
Private _Notes() As Integer
Public Property Notes As Integer()
Set(ByVal value() As Integer)
_Notes = value
End Set
Get
Return _Notes
End Get
End Property
End Class
Public Class Guitare
Inherits Instrument 'Hérite d'Instrument
Private _NbCordes As Integer
Sub New(ByVal Notes() As Integer, ByVal NbCordes As Integer)
[Link] = Notes 'On spécifie la propriété Notes de la
mère
_NbCordes = NbCordes
End Sub
End Class
Vous voyez qu'ici le mot-clé MustInherit spécifié dans la déclaration de la classe (qui signifie en français « doit
hériter », mais qu'il est mieux de traduire par « doit être hérité »), spécifie que cette classe sera abstraite, elle ne
pourra pas être utilisée telle quelle.
Notre classe Guitare est donc ici pour hériter d'Instrument. Une fois la classe Guitare instanciée, on peut très bien
accéder aux membres d'Instrument. La syntaxe [Link](0) est tout à fait correcte (où MaGuitare est
notre objet créé).
Il existe toutefois une seconde utilisation possible pour les classes abstraites ; en tant que bibliothèques de
fonctions.
Plutôt que de placer toutes vos fonctions dans le fichier qui contient la fenêtre, il vous est possible de créer un
fichier de classe, nommé « Fonctions » par exemple, et d'y inscrire des fonctions qui pourront être utilisées à
partir de n'importe où dans votre programme.
Public Class Fonctions
Shared Function Somme(ByVal X As Integer, ByVal y As Integer) As Integer
Return X + y
End Function
Shared Function Difference(ByVal X As Integer, ByVal y As Integer) As Integer
Return X - y
End Function
End Class
Dans notre classe Fonctions, nous avons retiré le mot MustInherit.
Vous vous apercevez que nos fonctions ne sont pas publiques ou privées, mais Shared. Le mot-clé Shared,
signifiant « partagé » en français, indique que cette fonction peut être utilisée sans avoir besoin d'instancier la
classe. Autrement dit, dans le programme on pourra utiliser [Link](1,2) où l'on veut.
Les avantages : la possibilité de regrouper des fonctions utiles dans le même fichier.
Les inconvénients : puisque la classe n'est pas instanciée, il n'est pas possible d'accéder à des membres externes
à la fonction. Des variables globales ne pourront pas être utilisées par exemple.
3. Les événements
Un événement est une fonction appelée lorsque quelque chose se produit. Ce « quelque chose » peut être l'appui
d'un clic sur un bouton, l'insertion de texte dans une textbox, le chargement d'une fenêtre, etc. Vous vous
souvenez du mot-clé Handles ?
C'est ce qui indique l'écoute d'un événement sur un objet. On avait l'habitude de l'utiliser pour des contrôles
visuels, nous allons désormais apprendre à écouter un événement sur notre classe.
Pour commencer, il faut spécifier ce qui va déclencher l'événement à l'intérieur de notre classe.
Dans notre classe nous déclarons un timer en global avec WithEvents, ce qui signifie que nous allons pouvoir
écouter les événements de cet objet. Vous avez déjà utilisé un timer en tant que contrôle (les contrôles étant
d'office avec WithEvents), cette fois il ne sera visible que côté code. Nous l'instancions, puis le lançons dans le
constructeur avec une seconde en interval, Nous utilisons la fonction Start() pour le démarrer plutôt que Enable =
true.
Dans l'événement Tick du timer, nous incrémentons un compteur ; une fois arrivé à 10 nous déclenchons
l'événement.
Ça donne le code suivant dans notre classe :
Private WithEvents _Tim As Timer
Private _Compteur As Integer
Sub New()
_Tim = New Timer
_Tim.Interval = 1000
_Tim.Start()
_Compteur = 0
End Sub
Public Event DixSecondes()
Sub _Tim_Tick() Handles _Tim.Tick
_Compteur += 1
If _Compteur = 10 Then
RaiseEvent DixSecondes()
End If
End Sub
Vous voyez pour commencer la déclaration des variables (compteur et timer). Puis le constructeur initialise,
instancie et démarre tout ça. Vient l'événement Tick du timer, je compte et je déclenche l'événement avec le mot-
clé RaiseEvent.
L'événement déclenché doit être déclaré : Public Event DixSecondes(), en public pour pouvoir « l'écouter » de
l'extérieur.
Allons du côté de notre fenêtre qui va instancier notre objet. La classe déclarée en globale doit être faite avec le
mot-clé WithEvents également :
Dim WithEvents MaClasse1 As MaClasse
Private Sub Form1_Load(ByVal sender As [Link], ByVal e As [Link]) Handles
[Link]
MaClasse1 = New MaClasse1()
End Sub
Sub AttendsLesDixSecondes() Handles [Link]
MsgBox("Dix secondes que l'objet est crée")
End Sub
Puis je l'instancie dans le form.
Pour finir j'ai écouté l'événement avec Sub AttendsLesDixSecondes() Handles [Link].
Lorsque les 10 secondes sont écoulées, je déclenche une MsgBox.
Avec des arguments
Et on peut passer des arguments avec nos événements. Côté classe on déclare l'événement avec :
Public Event DixSecondes(ByVal Message As String)
On voit qu'il attend un argument de type String.
Donc lorsqu’on va l'appeler :
RaiseEvent DixSecondes("Dix secondes que l'objet est crée")
Je lui passe mon argument. Et finalement côté fenêtre, le Handles de mon événement s'effectue ainsi :
Sub AttendsLesDixSecondes(ByVal Message As String) Handles [Link]
MsgBox(Message)
End Sub
4. La surcharge
Même si vous ne savez pas ce que ce mot signifie, je peux vous dire que vous y avez déjà été confrontés.
Souvenez-vous, lorsque vous passez des arguments à une fonction et que l'assistant de Visual Basic vous propose
plusieurs possibilités pour passer ces arguments, comme à la figure suivante.
La surcharge est la possibilité pour une fonction d’être définie de plusieurs façons différentes.
Vous voyez que l'infobulle spécifie « 2 sur 15 ». J'ai choisi la seconde possibilité, sur quinze possibles, de donner
les arguments. C'est cela la surcharge, pour la même fonction, avoir plusieurs « résultats » possibles en fonction
du passage des arguments.
Créons tout de suite un constructeur surchargé pour vous montrer ce que cela implique :
Public MustInherit Class Instrument
Private _Notes() As Integer
Public Property Notes As Integer()
Set(ByVal value() As Integer)
_Notes = value
End Set
Get
Return _Notes
End Get
End Property
End Class
Public Class Guitare Inherits Instrument 'Hérite d'Instrument
Private _NbCordes As Integer
Sub New()
_NbCordes = 0
End Sub
Sub New(ByVal Notes() As Integer)
[Link] = Notes
_NbCordes = 0
End Sub
Sub New(ByVal Notes() As Integer, ByVal NbCordes As Integer)
[Link] = Notes
_NbCordes = NbCordes
End Sub
End Class
J'ai déclaré dans ma classe Guitare trois constructeurs différents, j'ai donc surchargé le constructeur.
Ce qui me donne le droit à l'infobulle visible à la figure suivante lorsque je veux l'instancier.
Deux fonctions avec des arguments de types différents apportent aussi une surcharge :
Sub New(ByVal Notes() As Integer, ByVal NbCordes As Integer)
[Link] = Notes
_NbCordes = NbCordes
End Sub
Sub New(ByVal Notes() As Integer, ByVal NbCordes As String)
[Link] = Notes
_NbCordes = NbCordes
End Sub
Ici le nombre de cordes est une fois un String, une autre fois un Integer, la fonction appelée va dépendre du
type passé lors de l'instanciation.
Surcharger la classe mère
Pour surcharger une méthode de la classe mère, la technique est presque la même. Il va juste falloir ajouter le
mot-clé Overloads devant la méthode de la classe fille. Ce qui nous donne :
Public MustInherit Class Instrument
Private _Notes() As Integer
Public Property Notes As Integer()
Set(ByVal value() As Integer)
_Notes = value
End Set
Get
Return _Notes
End Get
End Property
Sub Joue()
'Ding
End Sub
End Class
Public Class Guitare Inherits Instrument 'Hérite d'Instrument
Private _NbCordes As Integer
Sub New(ByVal Notes() As Integer, ByVal NbCordes As Integer)
[Link] = Notes
_NbCordes = NbCordes
End Sub
Overloads Sub Joue(ByVal Note As Integer)
'Ding
End Sub
End Class
J'ai bien ajouté Overloads devant la méthode Joue de la classe fille, et lors de son appel j'ai le choix entre les
deux possibilités : avec ou sans argument.
Dernière chose : on peut « bypasser » une méthode mère. Autrement dit, créer une méthode de la même
déclaration dans l'enfant et spécifier que c'est cette dernière qui a la priorité sur l'autre. Cela grâce à Overrides.
Public MustInherit Class Instrument
Private _Notes() As Integer
Public Property Notes As Integer()
Set(ByVal value() As Integer)
_Notes = value
End Set
Get
Return _Notes
End Get
End Property
Overridable Sub Joue()
'Ding
End Sub
End Class
Public Class Guitare Inherits Instrument 'Hérite d'Instrument
Private _NbCordes As Integer
Sub New(ByVal Notes() As Integer, ByVal NbCordes As Integer)
[Link] = Notes
_NbCordes = NbCordes
End Sub
Overrides Sub Joue()
[Link]() 'Ding de la mère
'Ding
End Sub
End Class
La méthode Joue de la fille prime sur la mère. Le mot-clé Overrides dans la déclaration de la méthode fille est
nécessaire, tout comme le mot-clé Overridable dans la déclaration de la méthode mère.
Et j'ai pu utiliser [Link]() pour que la méthode mère soit quand même appelée.