Les classes en C#
Nous avons vu qu'en dehors des types de base, tout est classe. Une classe est constituée d'une partie données - les attributs - et d'une partie méthodes.
Les
membres d'une classe
Des attributs,
privés.
Un attribut d'une classe se déclare avec un spécificateur d'accès qui précise son niveau de visibilité. Dans un premier temps les deux seuls spécificateurs d'accès que nous verrons sont public et private.
class Produit
{
private String Libelle;
private int Prix;
private int QtStock = 0;
...
}
Il est possible d'attribuer des valeurs par défaut aux champs de donnée.
Les attributs sont déclarés privés, c'est à dire inaccessibles en
dehors de la classe produit.
Ceci est une règle fondamentale de la programmation objet - en dehors de quelques exceptions notamment une très forte imbrication de deux classes - . Ceci est la condition première de la réutilisation des objets; imaginons par exemple une classe Equipe constituée d'objets Joueur, implémenté sous forme d'un tableau de Joueurs; si un utilisateurs de la classe équipe accède directement au tableau de joueurs cela interdit tout changement dans la structure des donnée - remplacer le tableau par une liste, un fichier- ou cela entraîne la réécriture de tous les modules utilisant des équipes.
Mais le principe d'encapsulation ne consiste pas seulement dans la déclaration privée des donnée mais dans le masquage de la structure des données, nous aurons l'occasion de revenir sur ce point.
Des méthodes
Le rôle d'une méthode est d'effectuer un traitement en utilisant, en général, les données. Les méthodes peuvent être publiques ou privées. L'ensemble des méthodes publiques représente l'interface de la classe, sa partie "utilisable" à l'extérieur.
On peut les classer en trois groupes selon leurs fonctionnalités:
Les fonctions
de création.
Ces fonctions qui permettent de créer des objets seront étudiées un peu plus loin.
Les méthodes
accesseurs/modificateurs
Elles permettent :
- soit de donner l'état de l'objet sans le modifier.
public String GetLibelle()
{
return this.Libelle;
}
public int GetPrix()
{
return this.Prix;
}
public int GetStock()
{
return this.QtStock;
}
Remarque: nous avons utilisé le mot réservé this afin de faire référence à l'objet courant, cette écriture (non indispensable) améliore la lisibilité.
On parle d'accesseur (méthode d'accès).
remarque: on utilise en général le préfixe Get.
- soit de modifier l'état de l'objet
public void SetStock(int s)
{
this.QtStock = s;
}
public void SetPrix(int a)
{
this.Prix = a ;
}
On parle alors de modificateur.
remarque: on utilise en général le préfixe Set.
Les méthodes agissant sur le comportement ou l'état de l'objet.
Ce sont toutes les autres méthodes.
exemple :
public int ValStock()
{
return this.Prix * this.QteStock;
}
public void Destocker(int sortie)
{
this.QteStock -= sortie;
}
Création
des objets.
Notion
de référence.
Pour créer un objet, nous devons créer une référence sur l'objet:
Produit p;
ensuite, nous utilisons l'opérateur new qui va allouer - dans le tas - une place pour l'objet:
p = new Produit();
L'appel de l'opérateur new va créer une instance d'un produit, c'est à dire un objet de la classe Produit, mais p n'est pas cet objet mais une référence sur cet objet.
On pouvait faire directement:
Produit p = new Produit();
Après cette instanciation, l'objet est accessible et nous pouvons écrire:
p.setStock ( 100 );
On peut assimiler ce mécanisme à l'allocation dynamique en C:
Produit * p;
p = new Produit;
p->Affiche();
mais en C, on déclare explicitement un pointeur
ou similitude encore plus proche, la création d'objets en Visual Basic
dim rs as Recordset
set rs = new Recordset("...")
Il faudra sans cesse avoir à l'esprit qu'en dehors des types de base, nous ne
manipulons jamais un objet mais une référence sur l'objet. Ainsi il faudra
être vigilant lorsque l'on affecte une référence à une autre référence:
Produit unPoduit = new Produit();
Produit unAutreProduit = unProduit;
Maintenant les deux références réfèrent le même objet !
Notion de
constructeur.
C# propose des méthodes particulières qui ont comme rôle d'initialiser les attributs, ce sont les constructeurs. Un constructeur est appelé automatiquement au moment au moment de la création de l'objet à l'aide de l'opérateur new.
Les constructeurs ont une signature particulière.
Exemple:
public Produit( String l, int p, int q)
{
this.Libelle
= l;
this.Prix
= p;
this.QtStock
= q;
}
Chaque objet de la classe Produit sera instancié en utilisant le constructeur
Produit p = new Produit("marteau",12,100);
On peut écrire plusieurs constructeurs pour une classe:
public Produit()
//constructeur sans paramètre ou constructeur par défaut
{
this.Libelle
= "";
this.Prix
= 0;
this.QtStock
= 0;
}
Le mécanisme permettant de définir plusieurs méthodes ayant le même nom (mais pas la même signature) s'appelle une surcharge. Le mécanisme de surcharge s'inscrit dans un concept plus général, celui de polymorphisme.
Produit p = new Produit( ); // appel du constructeur par défaut.
Produit p1 = new Produit("scie",50, 10); //appel du constructeur à trois arguments
Le langage appellera le constructeur correspondant aux arguments passés au moment de l'appel.
Si aucun constructeur n'est défini explicitement, C# va générer automatiquement
un constructeur par défaut qui va initialiser chaque champ selon leur type.
Il est fortement conseiller de toujours fournir au moins un constructeur à chaque
classe.
Destruction
des objets.
Nous avons dit que l'instanciation d'objet créait sur le tas une zone correspondant au type de l'objet. En C/C++, lorsque l'objet n'était plus référencé, il fallait explicitement libérer cet espace à l'aide de l'opérateur delete. En C# nous n'aurons plus cette contrainte, ceci sera fait automatiquement:
void uneFonction()
{
Produit p = new Produit();
...
}
La durée de vie de p est la fonction, donc à la fin de l'appel de la fonction uneFonction p est supprimé, le Produit n'est plus référencé, le langage va libérer l'espace anciennement référencé par p.
Champs
et méthodes statiques.
Nous avons vu que les champs de données appartenaient de fait à chaque objet d'une classe; chaque objet est ainsi constitué de données dont les valeurs dépendent de l'objet concerné.
Il parfois nécessaire de déclarer des attributs qui ont une "portée classe", leurs valeurs seront les même pour toutes les instances de la classe. Ceci est possible en déclarant le champ ou la méthode static.
public class Produit
{
...
public static int GetNbProduit()
// méthode à portée classe
{
return Produit.nbProduits;
}
Noter l'attribut Produit.nbProduits , l'attribut n'est pas préfixé
par this (une instance) mais par le nom de la classe.
private static int NbProduits = 0; // champ à portée classe
L''appel est un peu différent, c'est le nom de la classe qui préfixe la méthode:
Produit.getNbProduits();
<Nom de la classe>.<Méthode statique>
<Nom de la classe>.<Champ statique>
Remarques:
Passage
de paramètre dans les méthodes.
Le passage
de paramètre par défaut est le passage par valeur.
Ceci ne gènera pas si les paramètres sont des références sur des objets puisque ces références permettront d'atteindre les objets eux-mêmes.
Exemple :
void UneFonction( Produit p)
{
p.SetStock(200);
}
modifiera le champ stock du paramètre réel appelant.
Par contre si des paramètres sont des valeurs de type Donnée/résultats il faudra préciser explicitement un passage par référence.
Exemple:
void Permute( int nb1,nb2){...}
ne pourra modifier les paramètres réels.
Par contre :
void Permute(ref int nb1, ref int nb2){...} le pourra :
L'appel de la méthode
reproduit le type de passage de paramètre :
Permute(ref a, ref b);
Travail à faire :
.