mardi 16 septembre 2008

Programmation événementielle Acte VII)

Modèle observateur dans le Framework .NET

Maintenant que nous avons certaines bases sur le modèle observateur, regardons l'utilisation de ce modèle dans le framework .NET. Pour ceux familier avec la FCL, auront sans doute remarqué que les types IObserver, IObservable, ou ObservableImpl ne sont pas présents dans le Framework. La raison première de leur absence vient du fait que la CLR les rend d'une certaine manière obsolète. Bien que vous puissiez utiliser ces constructions dans une application .NET, l'introduction des délégués (delegate) et des évènements (event), fournit un nouveau moyen très puissant pour l'implémentation du modèle Observateur sans être obligé de lui créer des types dédiés. En fait, comme les délégués et les évènements sont des classes de base de la CLR, les fondations de ce modèle se retrouvent incorporées dans le cœur du Framework .NET. La structure de la FCL en est un parfait exemple, car elle utilise le modèle observateur intensivement.

On a beaucoup écrit sur les délégués et les évènements, on ne va donc pas s'y étendre ici. Il suffit de savoir qu'un délégué est l'équivalent orienté objet (et type-safe) d'un pointeur de fonction. Une instance d'un délégué possède une référence à une ou plusieurs méthodes qu'elles soient statiques ou non.

Les évènements sont des constructions spéciales déclarées dans une classe qui exposent l'état des objets au Run time. Un évènement représente une abstraction des méthodes d'inscription, désinscription et de notification que nous venons d'implémenter dans le modèle Observateur. Les évènements utilisent en arrière plan des délégués qui seront invoqués lorsque un évènement sera levé. Pour de plus amples informations sur les évènements et les délégués reportez-vous à l'article suivant An Introduction to Delegates.

Pour rapprocher le modèle Observateur et la CLR, la classe implémentant l'évènement est le sujet. Plus besoin pour la classe sujet d'implémenter l'interface IObservable et ou de dériver de la classe ObservableImpl, il lui suffit d'implémenter un évènement et rien de plus. La création de l'observateur est légèrement plus sophistiquée, mais cependant plus flexible (nous en rediscuterons plus tard). Plutôt que d'implémenter l'interface IObserver et s'inscrire auprès du sujet, un Observateur doit créer une instance spécifique d'un délégué et inscrire ce délégué auprès de l'évènement du sujet. Il doit, sous peine que l'inscription échoue, utiliser un délégué du type spécifié dans la déclaration de l'évènement. Passons directement à  nos exemples qui sont beaucoup plus simples à implémenter qu'il ne parait au premier abord.

A noter dans l'exemple C# et Visual Basic .NET ci-dessous l'absence de classe de base ou interface utilisées par les classes Stock ou StockDisplay pour supporter le Modèle observateur.

Observateur utilisant des délégués et des évènements (C#)
public class Stock {
 
 //Déclaration du délégué pour l'évènement
 public delegate void AskPriceDelegate(object aPrice);
 //Déclaration de l'évènement utilisant le délégué
 public event AskPriceDelegate AskPriceChanged;
 
 //variable prix
 object _askPrice;
 
 //propriété pour le prix
 public object AskPrice {
 
 set { 
 //Modifie le prix
 _askPrice=value; 
 
 //lève l'évènement
 AskPriceChanged(_askPrice); 
 
 }
 
 }//AskPrice property
 
}//Stock class
 
//Représente l'interface utilisateur
public class StockDisplay {
 
 public void AskPriceChanged(object aPrice) {
 Console.Write("Le nouveau prix est :" + aPrice 
+ "\r\n"); }
 
}//StockDispslay class
 
public class MainClass {
 
 public static void Main(){
 
 StockDisplay stockDisplay=new 
StockDisplay();
 Stock stock=new Stock();
 
 //création d'une nouvelle instance du délégué 
et lie le avec la 
 //méthode askpricechanged de l'observateur
 Stock.AskPriceDelegate aDelegate=new
 
Stock.AskPriceDelegate(stockDisplay.AskPriceChanged);
 
 //Ajout du délégué à l'évènement
 stock.AskPriceChanged+=aDelegate;
 
 //boucle 100 fois et modifie le prix
 for(int looper=0;looper < 100;looper++) 
{
 stock.AskPrice=looper;
 }
 
 //Dés inscrit le délégué de l'évènement
 stock.AskPriceChanged-=aDelegate;
 
 }//Main
 
}//MainClass

Une fois familier avec les délégués et les événements leur force deviendra évidente. L'utilisation des délégués et des évènements, réduit par rapport à l'utilisation des interfaces IObserver, IObservable et de la classe ObservableImpl, la quantité de travail à entreprendre pour implémenter ce modèle. La CLR fournit les bases de la gestion du conteneur, ainsi qu'une convention commune d'appel pour s'inscrire, se désinscrire et notifier l'observateur. De plus, l'un des plus grand bénéfice des délégués est leur possibilité intrinsèque d'invoquer n'importe quelle méthode (pour peut qu'elle se conforme à la même signature). N'importe quelle classe qui utilise ces mécanismes peut alors agir en tant qu'observateur indépendamment de l'implémentation d'interfaces spécialisées.

Bien que l'utilisation des interfaces IObserver et IObservable réduise le couplage entre l'observateur et le sujet, l'utilisation des délégués l'élimine complètement.

Le Modèle évènement (Event Pattern)

Basée sur les évènements et les délégués, la FCL utilise intensivement le modèle Observateur. Les concepteurs de la FCL ont pleinement réalisé la puissance de ce modèle, et l'ont appliquée aussi bien aux classes qui gère l'interface utilisateur, que celles qui sont indépendantes de l'interface utilisateur. Le modèle évènement inventé par les concepteurs du Framework, est une variation du modèle Observateur.

Bien qu'il n'y ait pas obligation, Microsoft recommande que toutes les applications et les Frameworks qui utilisent les évènements et les délégués adoptent ce modèle.

Le premier, et peut-être le plus important, est l'utilisation du nom de l'évènement exposé par le sujet. Il doit être évident par lui-même et indiquer sur quoi il agit. Gardez à l'esprit que cette convention (aussi bien que les autres) est de nature subjective. L'intention première étant de fournir de la clarté à ceux qui utilisent l'évènement.

Retournons à notre exemple et examinons l'impacte de cette convention sur la classe Stock. Un nom convenable d'évènement serait comme préfix le nom du champ qui est modifié dans la classe sujet. Comme le nom de ce champ est _askPrice il est raisonnable de penser que le nom pourrait être AskPriceChanged. Il est évident que ce nom est de loin plus descriptif que celui qui dit StateChangedInStockClass. AskPriceChanged adhère donc à notre première convention

La seconde convention du modèle évènement repose également sur le nom du délégué, mais aussi sur sa signature. Le nom du délégué doit être composé du nom de l'évènement (voir 1er convention) avec le mot Handler ajouté. Le modèle appelle le délégué en lui spécifiant deux paramètres. Le 1er est une référence sur l'objet qui lève l'évènement et le second fournis des informations contextuelles à l'observateur. Le nom de 1er paramètre est tout simplement sender de type System.Object. Le faite que l'on passe un System.Object est du au fait que le délégué peut être lié à n'importe quelle méthode de n'importe quelle classe. Le nom du second paramètre est plus simple que le premier c'est e. Il doit être d'un type System.EventArgs ou de tous types dérivés. Bien que le type de retour dépende essentiellement de vos besoins, la plupart des délégués qui implémentent ce modèle ne retourne rien.

Regardons plus en détails le paramètre e. Ce paramètre permet au sujet de passer des informations à l'observateur. Si aucune information n'est nécessaire, une instance de System.EvenetArgs suffit largement. Par contre si vous avez besoins de passer des informations, une classe dérivant de System.EventArgs implémentant des champs supplémentaires, nécessaires au passage de l'information doit être construite. Le nom cette classe doit être représenté, du nom de l'évènement ajouté de EventArgs.

Dans notre classe Stock, le nom du délégué devient alors AskPriceChangedHandler, et le second paramètre devient AskPriceChangedEventArgs.

Puisque nous devons passer le nouveau prix à l'observateur, nous devons créer une classe AskPriceChangedEventArgs qui dérive de System.EventArgs en fournissant l'implémentation du passage d'informations.

Le dernier élément du modèle évènement est le nom et l'utilisation de la méthode de la classe Sujet qui lève l'évènement. Le nom de cette méthode doit être composé du nom de l'évènement avec le préfix On. Elle doit être de plus marquée comme étant protected. Cette convention s'applique seulement aux classes non sellées.

Appliquons cette dernière convention sur notre classe Stock. La classe n'étant pas sellée (sealed en C#, NotInheritable en VB.NET), on ajoute la méthode OnAskPriceChanged qui lève notre évènement.

L'exemple C# et Visual basic .NET montre une vue complète du modèle évènement appliquée à la classe Stock.

Event Pattern Example (C#)
public class Stock {
 
 public delegate void 
AskPriceChangedHandler(object sender, 
 
AskPriceChangedEventArgs e);
public event AskPriceChangedHandler AskPriceChanged;
 
 object _askPrice;
 
 public object AskPrice {
 
 set { 
_askPrice=value; 
 
//fire the event
OnAskPriceChanged(); 
 }
 
 }//AskPrice property
 
 
  
//method to fire event delegate with proper name
 protected void OnAskPriceChanged() {
 
 AskPriceChanged(this,new 
AskPriceChangedEventArgs(_askPrice));
 
 }//AskPriceChanged
 
 }//Stock class
 
 //specialized event class for the askpricechanged event
 public class AskPriceChangedEventArgs:EventArgs {
 
 //instance variable to store the ask price
 private object _askPrice;
 
 //constructor that sets askprice
 public AskPriceChangedEventArgs(object 
askPrice) { _askPrice=askPrice; }
 
 //public property for the ask price
 public object AskPrice { get { return 
_askPrice; } }
 
 }//AskPriceChangedEventArgs

Conclusion

En regard de ce que nous venons de voir sur le modèle Observateur, il devient évident que ce modèle fournit un mécanisme idéal pour assurer « the crisp boundaries » entre objet d'une application quelque soit leur fonction (Interface utilisateur ou autre). Bien qu'il soit relativement simple de l'implémenter via les fonctions de rappel (utilisation des interfaces IObserver et IObservable), les concepts des délégués et des évènements de la CLR facilite leur implémentation et réduit le couplage entre le sujet et l'observateur. La bonne utilisation de ce modèle assure réellement que l'application puisse évoluer au fil du temps. Ce modèle permet d'apporter des changements sur votre interface graphique aussi bien que sur votre logique métier, sans pour cela que soit aussi difficile que cela ne parait.

Les modèles de conception sont un puissant outil de développement d'application modulable, si utilisés efficacement. Dans cette article on a pu démontrer la solidité de l'approche par modèles, aussi bien que les modèles utilisés dans le Framework .NET. Dans l'article suivant les modèles de fabrique de classe, on expose d'autres modèles de la FCL qui permettent de construire des objets .NET efficacement.

 



--
Alain Lompo
Excelta - Conseils et services informatiques
MCT
MCSD For Microsoft .Net
MVP Windows Systems Server / Biztalk Server
Certifié ITIL et Microsoft Biztalk Server

Aucun commentaire: