Le pattern Observer est l'un des patrons de conception les plus utilisés en programmation orientée objet, particulièrement dans le développement d'applications .NET modernes. En C#, ce pattern prend une dimension particulière grâce au système d'événements natif du langage, offrant une implémentation élégante et puissante des mécanismes de notification.
Comprendre le Pattern Observer en C#
Le pattern Observer établit une relation un-à-plusieurs entre objets, permettant de notifier automatiquement tous les objets dépendants (observateurs) lorsqu'un changement d'état survient dans l'objet observé (sujet).
Concepts fondamentaux
- Publisher (Subject) : L'objet contenant l'état à observer
- Subscriber (Observer) : Les objets souhaitant être notifiés des changements
- Event : Le mécanisme de notification en C#
- Delegate : Le type définissant la signature des méthodes de callback
Implémentation basique avec événements C#
public class PriceChangeEventArgs : EventArgs
{
public decimal OldPrice { get; set; }
public decimal NewPrice { get; set; }
}
public class Product
{
private decimal _price;
// Déclaration de l'événement
public event EventHandler PriceChanged;
public decimal Price
{
get => _price;
set
{
if (_price != value)
{
var oldPrice = _price;
_price = value;
// Déclencher l'événement
OnPriceChanged(oldPrice, value);
}
}
}
protected virtual void OnPriceChanged(decimal oldPrice, decimal newPrice)
{
PriceChanged?.Invoke(this, new PriceChangeEventArgs
{
OldPrice = oldPrice,
NewPrice = newPrice
});
}
}
Utilisation avancée avec les événements personnalisés
// Définition d'un delegate personnalisé
public delegate void PriceChangeDelegate(decimal oldPrice, decimal newPrice);
public class ProductAdvanced
{
private decimal _price;
// Utilisation du delegate personnalisé
public event PriceChangeDelegate PriceChanged;
public void UpdatePrice(decimal newPrice)
{
var oldPrice = _price;
_price = newPrice;
// Pattern de vérification null moderne
PriceChanged?.Invoke(oldPrice, newPrice);
}
}
Bonnes pratiques et recommandations
- Toujours vérifier si l'événement a des abonnés avant de le déclencher (null check)
- Utiliser des EventArgs personnalisés pour transporter les données
- Implémenter une méthode protected virtual pour déclencher l'événement
- Considérer l'utilisation de weak events pour éviter les fuites mémoire
Gestion des erreurs et performances
public class SafeProduct
{
private decimal _price;
public event EventHandler PriceChanged;
protected virtual void OnPriceChanged(PriceChangeEventArgs e)
{
var handler = PriceChanged;
if (handler != null)
{
try
{
handler(this, e);
}
catch (Exception ex)
{
// Loguer l'erreur mais ne pas la propager
Debug.WriteLine($"Error in price change handler: {ex.Message}");
}
}
}
}
Tests unitaires
[Fact]
public void PriceChange_ShouldTriggerEvent()
{
// Arrange
var product = new Product();
var eventWasRaised = false;
decimal capturedOldPrice = 0;
decimal capturedNewPrice = 0;
product.PriceChanged += (sender, e) =>
{
eventWasRaised = true;
capturedOldPrice = e.OldPrice;
capturedNewPrice = e.NewPrice;
};
// Act
product.Price = 100m;
// Assert
Assert.True(eventWasRaised);
Assert.Equal(0m, capturedOldPrice);
Assert.Equal(100m, capturedNewPrice);
}
Cas d'usage réels
Le pattern Observer avec événements est particulièrement utile dans les scénarios suivants :
- Interfaces utilisateur réactives (MVVM)
- Systèmes de logging et monitoring
- Mise à jour en temps réel de données
- Communication entre microservices
Exemple d'implémentation dans un contexte MVVM
public class ProductViewModel : INotifyPropertyChanged
{
private string _name;
private decimal _price;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
}
public decimal Price
{
get => _price;
set
{
if (_price != value)
{
_price = value;
OnPropertyChanged(nameof(Price));
OnPropertyChanged(nameof(FormattedPrice));
}
}
}
public string FormattedPrice => $"€{Price:N2}";
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Considérations de performance
Pour optimiser les performances lors de l'utilisation d'événements :
- Éviter de déclencher des événements inutilement
- Utiliser des delegates asynchrones pour les opérations longues
- Implémenter un mécanisme de throttling si nécessaire
- Gérer correctement le désabonnement des événements
Conclusion
Le pattern Observer, combiné avec le système d'événements C#, offre une solution puissante et flexible pour gérer les notifications dans les applications .NET. En suivant les bonnes pratiques et en comprenant les implications en termes de performance et de gestion de la mémoire, vous pouvez créer des systèmes robustes et maintenables.
Points clés à retenir :
- Utilisez les EventArgs personnalisés pour une meilleure encapsulation
- Gérez correctement les null checks et les exceptions
- Pensez à la performance et à la gestion mémoire
- Testez rigoureusement vos implémentations