Aller au contenu principal

Les modificateurs d'accès

Notions théoriques

Les 4 modificateurs d'accès principaux

En C#, chaque membre (champ, propriété, méthode, classe) a un modificateur d'accès qui contrôle qui peut y accéder.

ModificateurAccessible depuis
publicPartout — aucune restriction
privateDans la classe uniquement
protectedDans la classe et ses sous-classes
internalDans le même projet (assembly)
class Compte
{
public string Titulaire { get; } // accessible partout
private decimal _solde; // accessible seulement dans Compte
protected string _devise = "EUR"; // accessible dans Compte et sous-classes

public decimal Solde => _solde;
private void AuditLog(string msg) { }
public void Deposer(decimal m) { _solde += m; AuditLog("dépôt"); }
}

public — interface de la classe

Les membres public forment l'interface de la classe : ce que le monde extérieur peut utiliser. Réfléchissez bien à ce que vous rendez public — il est difficile de retirer un membre public sans casser le code existant.

class Produit
{
public string Nom { get; set; } = "";
public decimal Prix { get; set; }

public decimal PrixTTC(double tva = 0.20)
=> Prix * (decimal)(1 + tva);
}

var p = new Produit { Nom = "Clavier", Prix = 49.99m };
Console.WriteLine(p.PrixTTC());

private — détails d'implémentation

Les membres private sont des détails internes cachés. On peut les modifier sans affecter le code qui utilise la classe.

class Panier
{
private readonly List<string> _articles = [];
private decimal _total = 0;

public int NombreArticles => _articles.Count;
public decimal Total => _total;

public void AjouterArticle(string nom, decimal prix)
{
_articles.Add(nom);
_total += prix;
JournaliserAjout(nom);
}

private void JournaliserAjout(string nom)
=> Console.WriteLine($"[LOG] Ajouté : {nom}");
}

protected — héritage contrôlé

protected est utilisé pour les membres que les sous-classes peuvent utiliser mais que le monde extérieur ne peut pas voir :

class Forme
{
protected double _largeur;
protected double _hauteur;

public Forme(double largeur, double hauteur)
{
_largeur = largeur;
_hauteur = hauteur;
}

public virtual double Aire() => 0;
}

class Rectangle : Forme
{
public Rectangle(double l, double h) : base(l, h) { }

public override double Aire() => _largeur * _hauteur; // accès à _largeur OK
}

internal — visibilité limitée au projet

internal est accessible dans le même projet (assembly) mais pas depuis les projets qui utilisent cette bibliothèque. Utile pour les classes d'implémentation interne.

internal class ServiceInterne // utilisable dans le projet, pas depuis l'extérieur
{
public void FaireQuelqueChose() { }
}

Valeur par défaut

Sans modificateur explicite :

  • Les membres d'une classe sont private par défaut
  • Les classes de niveau supérieur sont internal par défaut

Principe du moindre privilège

Toujours choisir la visibilité la plus restrictive possible

Commencez par private. Passez à protected seulement si une sous-classe en a besoin. Passez à public seulement si l'utilisateur de la classe doit y accéder. Cette discipline limite les risques d'usage incorrect et facilite les évolutions futures.

Exemple pratique

class CompteBancaire
{
private decimal _solde;
private readonly List<string> _historique = [];

public string Titulaire { get; }
public decimal Solde => _solde;

public CompteBancaire(string titulaire, decimal soldeInitial = 0)
{
Titulaire = titulaire;
_solde = soldeInitial;
EnregistrerOperation($"Ouverture du compte avec {soldeInitial:F2} €");
}

public void Deposer(decimal montant)
{
ValiderMontant(montant);
_solde += montant;
EnregistrerOperation($"Dépôt : +{montant:F2} €");
}

public bool Retirer(decimal montant)
{
if (montant <= 0 || montant > _solde) return false;
_solde -= montant;
EnregistrerOperation($"Retrait : -{montant:F2} €");
return true;
}

public void AfficherHistorique()
{
Console.WriteLine($"Historique de {Titulaire} :");
foreach (string op in _historique)
Console.WriteLine($" {op}");
}

private void ValiderMontant(decimal montant)
{
if (montant <= 0)
throw new ArgumentException("Le montant doit être positif.");
}

private void EnregistrerOperation(string description)
=> _historique.Add($"[{DateTime.Now:HH:mm:ss}] {description}");
}

var compte = new CompteBancaire("Alice", 500m);
compte.Deposer(200m);
compte.Retirer(100m);
compte.AfficherHistorique();
// compte._solde = 9999; // ERREUR : private !
Console.WriteLine($"Solde : {compte.Solde:F2} €");

Test de mémorisation/compréhension


Quelle visibilité permet l'accès depuis n'importe où ?


Quelle visibilité est réservée à la classe elle-même uniquement ?


Quelle visibilité convient pour un membre que la classe et ses sous-classes peuvent utiliser, mais pas le code externe ?


Quel est le modificateur d'accès par défaut d'un membre de classe non déclaré ?


Quel principe recommande de toujours choisir la visibilité la plus restrictive possible ?


TP pour réfléchir et résoudre des problèmes

Vous allez créer un système de bibliothèque avec encapsulation stricte.

Étape 1 — Concevoir la classe Livre avec la bonne visibilité


Bonne pratique - private set pour la modification contrôlée

public bool EstDisponible { get; private set; } expose la propriété en lecture depuis l'extérieur mais ne permet la modification que depuis l'intérieur de la classe. C'est préférable à exposer un champ public qui peut être modifié n'importe où.

Étape 2 — Méthodes publiques contrôlant l'état


Bonne pratique - L'état interne ne se modifie que via des méthodes

_nbEmprunts est private : on ne peut pas l'incrémenter depuis l'extérieur. La seule façon de l'augmenter est d'appeler Emprunter(), qui applique aussi les règles métier. C'est l'essence de l'encapsulation : l'objet contrôle son propre état.

Étape 3 — Tester les accès


Bonne pratique - La preuve de l'encapsulation

Si l'IDE ou le compilateur rejette livre._nbEmprunts = 0 avec "inaccessible en raison de son niveau de protection", c'est exactement le comportement attendu. L'encapsulation fonctionne : l'état interne est protégé et ne peut évoluer qu'à travers les méthodes publiques de la classe.

📌 Une solution