mercredi 19 novembre 2008

WCF et les Software Factory templates

Dans leur livre, Software Factories, Jack Greenfield et Keith Short ont discuté le fait que le développement basé sur des modèles dépend de l'esquisse de de languages de modélisation à objectifs généraux en faveur des DSL (Domain Specific Language)

 
Un DSL modélise les concepts rencontrés dans un domaine spécifique. Les DSL doivent être utilisées en conjonction avec le framework de classes correspondant, un ensemble de classes spécifiquement conçus pour couvrir le même domaine. Donc lorsque le DSL est utilisé pour modéliser des voies spécifiques par lesquelles ces classes peuvent être utilisées, il devrait alors être possible de générer le logiciel décrit dans le modèle à partir des classes de framework
 
La combinaison d'un DSL et le framework de classes correspondante constitue le noyau du modèle  software factory. Les modèles de Software Factory servent à la fabrication de plusieurs variété d'un même type de logici
 
Un bon exemple de modèle de Software Factory est le concepteur des formularies Windows dans Visual Studio .Net. Dans ce cas particulier, le concepteur de formulaires Windows est la DSL, la boîte à outils ainsi que l'éditeur des propriétés ainsi que les classes de l'espace de noms System.Windows.Forms constituent le framework de classes. Les utilisateurs du concepteur de formulaires Windows l'utilisent pour modéliser les logiciels qui sont générés à partir de ces classes
 
Les programmeurs ont utilize le concepteur de formulaires Windows et des outils similaires dans d'autres environments de développements pendant de nombreuses années pour developer des interfaces utilisateur. En bref Greenfield et Shrot ont introduit le concept des modèles de Software factory, sans pour autant créer quelque chose de nouveau. Ils sont plutôt en train de formaliser un concept qui s'est déjà révélé efficace, et suggèrent qu'il peut être utilisé pour développer une variété de logiciels au-delà des interfaces utilisateur
 

WCF est un modèle de Software Factory pour la communication entre logiciels. Il consiste ne une DSL appelée le Modèle de Service (Service Model)  et un framework de classes appelé la couche de canaux (Channel Layer). Le modèle de service consiste en des classes faisant partie de l'espace de nom System.ServiceModel et un language de configuration XML. La couche de canaux consistes en des classes disponibles dans l'espace de noms System.ServiceModel.Channel. Les développeurs modélisent comment un morceaux de logiciel peut communiquer en utilisant le modèle de service, ainsi que les composants de communication dont ils ont besoin d'inclure dans leur logiciels qui sont générés à partir de la couche de canaux,

 

En conformité avec leur modèle.Plus tard s'ils ont besoin d'effectuer des changements ou de decrier comment leur logiciel communiqué, ils apportent des alterations à leur modèle, et les modifications ou ajouts à leur logiciel sont générés. S'ils désirent modéliser une forme de communication qui n'est pas déjà supportée par la couche canaux, ils peuvent construire ou acheter un canal adéquat à ajouter à la couche canaux, et continuer à générer leur logiciel comme ils le faisaient habituellement, exactement comme l'utilisateur d'un concepteur de formulaires Windows peut construire ou acheter des contrôles graphiques à ajouter à la boite à outil du concepteur graphique

 

Les termes clefs dans le modèle de service de WCF sont les fameux ABC (Adress, Binding, Contract) soit en français Adresse, Liaison et Contrat. L'adresse définit là ou se trouve le logiciel, la liaison spécifie les protocoles pour communiquer avec le logiciel, et le contrat définit ce qu'il va effectuer. En conséquent le bien trouvé acronyme ABC peut vous aider à vous en rappeler : il faudrait le remplacer par ALC en français. En jargon WCF, une adresse, une liaison et un contrat constituent ce qu'on appelle un point terminal (endpoint en Anglais)

 


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

lundi 6 octobre 2008

File Types supported for solution items

Category Item Type File Extension

General Text file

.txt

Style sheet

.css

XML schema

.xsd

Bitmap file

.bmp

Cursor file

.cur

Visual C# class

.cs

Visual Basic class

.vb

HTML page

.html

XML file

.xml

XSLT file

.xsl

Icon file

.ico

Native resource template

.rct

Test Run Configuration Test run configuration

.testrunconfig

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

Solution Items

Solution Items

In practice, the content you will add most often to a solution is project related. But items

can be added directly to a solution as well. Collectively, the term

solution items refers to

any nonproject file that is attached to a solution. Because we know that solutions can't be

compiled, it stands to reason that files added at the solution level serve no practical

purpose from a compilation perspective. There are various reasons, however, that you may

want to add solution items to your solution. For instance, this is a convenient way to store

documentation that applies to the solution as a whole. Because you can add any type of

file to a solution, this could take the form of documents, notes to other developers, design

specifications, or even source code files from other solutions that may have some impact

or bearing on the work at hand.

By default, Visual Studio supports a few types of solution items that can be created directly

from within the IDE. They are grouped within four categories. Within each category are

various file types that can be generated by Visual Studio. Table 4.1 shows the supported

types.



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

Synchronization Datas

Architecture and Classes for Client and Server Synchronization

Sync Services for ADO.NET 2.0 enables synchronization between a SQL Server Compact 3.5 client database and a server database or any other data source, such as a service that provides stock quotes in XML. For synchronizing two databases, Sync Services supports two-tier and N-tier architectures that use any server database for which an ADO.NET provider is available. For synchronizing between a client database and other types of data sources, Sync Services supports a service-based architecture. This architecture requires more application code than two-tier and N-tier architectures; however, it does not require a developer to take a different approach to synchronization.

The following illustrations show the components that are involved in two-tier, N-tier, and service-based architectures. Each illustration shows a single client, but there are frequently multiple clients that synchronize with a single server. Sync Services uses a hub-and-spoke model. Synchronization is always initiated by the client. All changes from each client are synchronized with the server before the changes are sent from the server to other clients. (These are clients that do not exchange changes directly with one another.)

Sync Services provides snapshot, download-only, upload-only, and bidirectional synchronization:

  • Snapshot and download-only synchronization are typically used to store and update reference data, such as a product list, on a client. Data changes that are made at the server are downloaded to the client database during synchronization. Snapshot synchronization completely refreshes data every time that the client is synchronized. This is appropriate when you do not want to track incremental changes or the server cannot do so. Download-only synchronization downloads only the incremental changes that have occurred since the previous synchronization.

  • Upload-only synchronization is typically used to insert data, such as a sales order, on a client. Inserts and other changes to data that are made in the client database are uploaded to the server during synchronization.

  • Bidirectional synchronization is typically used for data, such as customer contact information, that can be updated at the client and server. Any conflicting changes must be handled during synchronization.

For more information about the types of synchronization, see How to: Specify Snapshot, Download, Upload, and Bidirectional Synchronization. The Sync Services architecture for client and server synchronization is asymmetric: This means that change-tracking is built into the client database, but you must track changes in the server data store if you want incremental changes to be downloaded. For more information about change tracking, see Tracking Changes in the Server Database.

Components in the Architecture Illustrations

The components in the architecture illustrations include the client and server databases and a set of classes from the Sync Services API. The N-tier and service-based architectures also include Web Service and Transport components that you must write.

Two-Tier Architecture

The first illustration shows a two-tier architecture that has a client database and a server database.

Two-tier synchronization topology

Except for the two databases, all items in the illustration correspond to Sync Services classes. These classes are contained in the following DLLs:

  • Microsoft.Synchronization.Data.dll contains Synchronization Agent, Synchronization Tables, and Synchronization Groups.

  • Microsoft. Synchronization.Data.SqlServerCe.dll contains the Client Synchronization Provider.

  • Microsoft. Synchronization.Data.Server.dll contains the Server Synchronization Provider and Synchronization Adapters.

All the DLLs depend on System.dll and System.Data.dll from .NET Framework 2.0 or later versions. Microsoft.Synchronization.Data.SqlServerCe.dll also depends on System.Data.SqlServerCe.dll from SQL Server Compact 3.5. For two-tier applications, all Sync Services DLLs reside on the client. For N-tier applications, Microsoft.Synchronization.Data.dll and Microsoft.Synchronization.Data.Server.dll reside on a separate computer that provides a synchronization service.

N-Tier Architecture

The second illustration shows an N-tier architecture. This requires a proxy, a service, and a transport mechanism to communicate between the client database and the server database. This architecture is more common than a two-tier architecture, because an N-tier architecture does not require a direct connection between the client and server databases.

N-tier synchronization topology

Service-based Architecture

The third illustration shows a service-based architecture. This architecture includes a client database, but does not include a server database or the corresponding Server Synchronization Provider and Synchronization Adapters. To use this kind of architecture, an application must be able to communicate to the Synchronization Agent through a custom proxy and custom service. These must provide the same functionality that the Server Synchronization Provider and Synchronization Adapters usually provide, such as retrieving changes to synchronize.

Service-based synchronization topology

Client Database

The client database for Sync Services applications is SQL Server Compact 3.5 Service Pack 1 (SP1) and later versions. Sync Services provides an infrastructure to track incremental changes in the client database. This infrastructure is enabled the first time any table is synchronized by using a method other than snapshot synchronization. By default, the metadata that Sync Services requires in the client database is stored for 10 days. For more information about metadata retention, see RetentionInDays.

Server Database

The server database can be any database for which an ADO.NET provider is available. If you want to track incremental changes in the server database, you must prepare the database to do this. For more information, see Tracking Changes in the Server Database.

Sync Services Classes

The following classes are represented in the previous illustration: SyncAgent, SqlCeClientSyncProvider, DbServerSyncProvider, SyncTable, SyncGroup, and SyncAdapter. For an example of how to use these classes in an application, see Getting Started: Client and Server Synchronization.

Synchronization Agent

The synchronization agent drives synchronization in the following ways:

  • Loops through each of the tables to be synchronized.

  • Calls the client synchronization provider to retrieve and apply changes at the client database.

  • Calls the server synchronization provider to retrieve and apply changes at the server database.

The synchronization agent also maintains session-level information for the synchronization and provides success messages, errors, and statistics to the application on the client. For more information, see SyncAgent and How to: Specify Snapshot, Download, Upload, and Bidirectional Synchronization.

Client Synchronization Provider

The client synchronization provider communicates with the client and shields the synchronization agent from the specific implementation of the client database. Sync Services includes a provider for the SQL Server Compact 3.5 database. The principal activities of the client synchronization provider are as follows:

  • Stores information about tables on the client that are enabled for synchronization.

  • Retrieves changes that occurred in the client database since the last synchronization.

  • Applies incremental changes to the client database.

  • Detects conflicting changes.

For more information, see SqlCeClientSyncProvider and How to: Specify Snapshot, Download, Upload, and Bidirectional Synchronization.

Server Synchronization Provider

The server synchronization provider communicates with the server and shields the synchronization agent from the specific implementation of the server database. The principal activities of the server synchronization provider are as follows:

  • Stores information about tables on the server that are enabled for synchronization.

  • Enables applications to retrieve changes that occurred in the server database since the last synchronization.

  • Applies incremental changes to the server database.

  • Detects conflicting changes.

For more information, see DbServerSyncProvider and How to: Specify Snapshot, Download, Upload, and Bidirectional Synchronization.

Synchronization Table and Synchronization Group

A synchronization table is defined for each table that is synchronized. It stores settings, such as the direction of synchronization. Each client can request only the tables it needs. This might not include all the tables that the server synchronization provider makes available. For example, there might be 20 tables, 10 of which are configured for bidirectional synchronization in the Server Synchronization Provider. A client might request only 12 of the tables as download-only. Although the server supports upload, the client does not have to make changes or to synchronize all tables. For more information, see SyncTable.

After a synchronization table is defined, it can be added to a synchronization group. A synchronization group is a mechanism to ensure consistent application of changes for a set of tables. If tables are included in a synchronization group, changes to those tables are transferred as a unit and applied at the server in a single transaction. If any change in the group fails, changes for the whole group are retried on the next synchronization. For more information, see SyncGroup and How to: Specify Snapshot, Download, Upload, and Bidirectional Synchronization.

Synchronization Adapter

Modeled after the data adapter in ADO.NET, the synchronization adapter is defined for each table that is synchronized. The synchronization adapter provides the server synchronization provider with the specific commands that are required to interact with the server database, such as the InsertCommand that applies inserts from the client database to the server database. Because synchronization adapters use the ADO.NET DbCommand object, you can use any command structure that is supported by ADO.NET. This includes inline Transact-SQL, stored procedures, views, functions, and so on. The commands only require a single result that defines the structure and data to be transferred and applied. For more information, see SyncAdapter and How to: Specify Snapshot, Download, Upload, and Bidirectional Synchronization.

Proxy, Service, and Transport

Proxy, Service, and Transport are used in N-tier and service-based architectures. In N-tier applications, Microsoft.Synchronization.Data.Server.dll is used but it does not reside on the client. Typically, the DLL resides on a middle tier that has a direct connection to the server database. In this case, a proxy and service are required to communicate between the client and the middle tier:

  • On the client, application code references a proxy for the server synchronization provider (ServerSyncProviderProxy) instead of directly referencing the provider. The proxy communicates with a service on the middle tier.

  • On the middle tier, the service inherits from and exposes the same methods as ServerSyncProvider (the abstract class from which DbServerSyncProvider inherits). The server synchronization provider methods are then executed over a direct connection to the server database. The results are routed through the middle tier and back to the client.

For more information, see How to: Configure N-Tier Synchronization.

In service-based applications, Microsoft.Synchronization.Data.Server.dll is not used on the client. The application code must provide the same functionality that the server synchronization provider and synchronization adapters usually provide:

  • On the client, application code references a proxy for the application code that handles server synchronization provider tasks, such as retrieving changes from the data source. The proxy communicates with a service on the middle tier.

  • On the middle tier, the service inherits from and exposes the same methods as ServerSyncProvider (the abstract class from which DbServerSyncProvider inherits). The methods are then executed by the application code over a direct connection to the server database. The results are routed through the middle tier and back to the client.

Additional Classes in the API

The illustrations in this topic show the major classes in the API. However, there are many classes that are not shown. To obtain information about all the available classes, see Microsoft.Synchronization, Microsoft.Synchronization.Data, Microsoft.Synchronization.Data.SqlServerCe, and Microsoft.Synchronization.Data.Server. The following sections provide introductions to four other important classes that you should be familiar with.

Synchronization Anchor

A synchronization anchor is a reference point in time for a set of tables that are synchronized from the server. Synchronization anchors enable an application to determine which changes should be synchronized during a specified session. During synchronization, the client synchronization provider stores the following reference points in the client database:

Last received anchor

Identifies the last change that was downloaded from the server.

Last sent anchor

Identifies the last change that was uploaded from the client.

On the next synchronization, an application can use these anchors to identify the starting point for synchronizing the next set of changes. For more information, see SyncAnchor and Tracking Changes in the Server Database.

Synchronization Session Statistics

Session statistics are a set of statistics that the Synchronization Agent provides for each synchronization session. The statistics include information about synchronization times, the number of changes processed, and any conflicts or exceptions that occurred. For more information, see SyncStatistics and How to: Work with Events and Program Business Logic.

Synchronization Session Variables

Session variables are variables that are provided for a developer to use as parameters for the select, insert, update, and delete commands executed at the server. The variables can be used in several ways: to provide support for conflict detection and to avoid downloading changes to a client more than one time. For more information, see SyncSession and How to: Use Session Variables.

SQL Server Synchronization Adapter Builder

Modeled after the command builder in ADO.NET, the synchronization adapter builder helps you develop code for the synchronization commands that are executed by the server synchronization provider. The synchronization adapter builder produces SELECT, INSERT, UPDATE, and DELETE statements for SQL Server databases. These statements are based on information that you provide about the tables that are involved in synchronization. For more information, see SqlSyncAdapterBuilder and Tools to Help You Develop Applications (Sync Services).



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

Powerful .Net Framework 3.5 new features I)

Time Zone Additions—There are two new types that help you work with applications
that need to understand multiple time zones. These classes are
System.DateTimeOffset and TimeZoneInfo. The DateTimeOffset structure represents
an exact point in time. The offset indicates how the time differs from UTC
(Universal Coordinated Time). You use this new class when you need precision and
date/time arithmetic.
The TimeZoneInfo class is a welcome enhancement that represents a date and time
in a given time zone. You can use this class to reliably represent the same date and
time in any other time zone. In addition, you can use the class to create custom
time zones if needed.
. Peer-to-Peer Networking Support—The .NET Framework finally has its own
peer-to-peer networking support. This can be found in the System.Net.PeerToPeer
namespace. With it, you can create an application that works without a server and
instead communicates from one client (peer) to another (similar to Microsoft’s
Groove application). Application scenarios supported by this new namespace include
tracking where peers are (online or offline), what they might be doing, interacting
(messaging) with peers, managing peer contacts, discovering new peers, and more.
. Sync Services for ADO.NET—Shipping with Visual Studio 2008 is Microsoft’s
Sync Services. With it you can build an application that works both online and
offline. These types of applications are referred to as occasionally connected applications
(OCA). You use Sync Services (and its related tools) to indicate which data
should be available when a user is offline. When connected, the Sync Services works
to synchronize user changes with database changes.

DataPager control in ASP .NET 3.5

Another new control in 2008 that we’d like to highlight is the DataPager control. This
control allows you to manage the paging of data and the UI associated with that paging.
You can use this control by itself or embed it as part of another control you create. You can
associate other, data-bound controls to a DataPager by using the DataPager’s PagedControlID property (the given control must implement the IPageableItemContainer interface).

You have full control over the customization, layout, and behavior of the DataPager.
Figure 1.17 shows the DataPager Fields editor (accessed from the control’s Tasks window).
Notice that you can set appearance and behavior for all items associated with a given
DataPager layout.

SilverLight

Microsoft’s Silverlight is another exciting client technology for the Web. Silverlight
allows for an even greater user experience delivered through the browser. You use it to
create media-rich, highly interactive experiences. Silverlight requires a browser add-on
(or plug-in). It works with Windows, Mac, and Linux in a wide variety of browsers.
Silverlight does not ship with Visual Studio 2008; however, the Silverlight extensions
for Visual Studio are available as a plug-in to the tool.

Create a richer web interface

AJAX represents the capability to leverage the ubiquitous support for JavaScript in web
browsers to create a more interactive user experience. Client applications built to leverage
AJAX still have a client-server paradigm. However, with AJAX the client can update
portions of a given page without appearing to have posted back to the server (of course, it
typically does). In addition, most AJAX-enabled applications put more processing on the
client for things like toggling sections of a page, working with tabs, auto-completing data
entry, popping up dialogs, and more. The result is a step forward in interactivity for a user.

Enhancing the Web Developer's Productivity with Visual Studio 2008

The vast majority of applications built these days involve some semblance of a web
component—be it a full-blown browser-based, web application; a smart client that works
across the Web; a web service; or otherwise. In fact, the line between a traditional rich
client and a web application is blurring. Technologies such as AJAX (Asynchronous
JavaScript and XML), Web Services, Smart Clients, and XAML (Extensible Application
Markup Language) have ensured this. You can now build rich user experiences as your
needs dictate. Of course, Microsoft has remained suitably focused on expanding Visual
Studio’s capabilities with respect to web development.

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

Programmation événementielle Actes VI)

Le premier argument appelé sender permet à l'abonné de l'événement de connaître précisément l'instance de l'objet qui a émis l'événement. A quoi cela peut-il servir me direz-vous ?

Et bien imaginons un instant que je possède deux instances d'un même objet, Instance1 et Instance2, et que je souhaite m'abonner à l'événement NouveauButPourLOM. Comme dans mon cas le traitement que j'ai à faire dans cet événement côté abonné est identique pour les deux instances, je me dis que cela serait sympa de n'utiliser qu'une seule et même méthode pour traiter les deux événements. Et bien l'argument sender m'a justement me permettre de savoir si l'événement a été émis par Instance1 ou Instance2. A noter que si nous décidions que notre méthode CopieFichier était statique (static), ce premier argument n'aurait pas de sens et ne figurerait pas dans la signature.

Généralement, l'événement émis par une classe à destination d'une autre classe abonnée transporte avec lui un certain nombre d'informations qui pourraient être utiles à l'abonné. On pourrait être tenté de créer autant de paramètres à la signature de notre Delegate/Event que nécessaire. Ce n'est pas l'approche qui a été choisie par les développeurs du framework .NET.

Lorsqu'on souhaite déclarer un évenement qui n'a aucun paramètre particulier à transmettre, on utilise comme deuxième paramètre du Delegate/Event la classe System.EventArgs. Voici ce que la documentation indique sur cette classe :

EventArgs est la classe de base des classes contenant des données d'événement.Cette classe ne contient pas de données d'événement; elle est utilisée par des événements qui ne passent pas d'informations d'état à un gestionnaire d'événements lorsqu'un événement est déclenché. Si le gestionnaire d'événements nécessite des informations d'état, l'application doit dériver une classe à partir de cette classe pour contenir les données.

Et voilà tout est dit, dès qu'on veut passer un certain nombre d'informations à un Delegate/Event, on écrit une classe qui dérive de la classe EventArgs à laquelle on ajoute les informations supplémentaires. Vous avez noté que le nom de ces classes se terminent généralement par EventArgs.

Pour l'événement CopieDemarrage, ce dont on a besoin est simplement la taille du fichier à copier. Ainsi cela permettra d'être notifié de l'imminence du démarrage de la copie et de pouvoir ajuster les valeurs de notre gauge de progression en fonction de la taille du fichier à copier. De plus, il pourrait être utile de pouvoir récupérer les chemins complets des fichiers source et destination. Cela nous donne :

namespace Librairie_CS {

 public class CopieDemarrageEventArgs : System.EventArgs {

  private string fichierSource = string.Empty;
  public string FichierSource {

   get {

    return(this.fichierSource);
   }
  }

  private string fichierDestination = string.Empty;
  public string FichierDestination {

   get {

    return(this.fichierDestination);
   }
  }

  private long tailleFichier = 0;
  public long TailleFichier {

   get {

    return(this.tailleFichier);
   }
  }

  public CopieDemarrageEventArgs(string fichierSource, string fichierDestination, long tailleFichier) {

   this.fichierSource = fichierSource;
   this.fichierDestination = fichierDestination;
   this.tailleFichier = tailleFichier;
  }
 }
}

Pour l'événement CopieFin, ce dont on a besoin est simplement le statut final de la copie. On sait que la copie peut s'achever de trois manières différentes : Succes, Echec ou encore Annulation (par l'utilisateur). C'est un cas idéal de création d'une énumération :

namespace Librairie_CS {

 public enum StatutCopie {

  Aucun,
  Succes,
  Echec,
  Annulation
 }
}

De plus, il peut être utile d'indiquer dans cet événement en combien de temps s'est déroulé l'opération de copie. Cela nous donne finalement ceci:

namespace Librairie_CS {

 public class CopieFinEventArgs : System.EventArgs {

  private StatutCopie statutCopie = StatutCopie.Aucun;
  public StatutCopie StatutCopie {

   get {

    return(this.statutCopie);
   }
  }

  private int duree = 0;
  public int Duree {

   get {

    return(this.duree);
   }
  }

  public CopieFinEventArgs(StatutCopie statutCopie, int duree) {

   this.statutCopie = statutCopie;
   this.duree = duree;
  }
 }
}

Finalement, pour CopieEnCours, il faudrait fournir le nombre d'octets déjà copiés au moment de l'émission de l'événement. Aucune difficulté particulière ici. Par contre, on souhaite également donner la possibilité à l'abonné de pouvoir annuler la copie à tout le moment. L'utilisation de cet événement pour permettre l'annulation de l'opération est alors l'idéal car appelé très régulièrement durant le processus de copie. Voici ce que cela donne :

namespace Librairie_CS {

 public class CopieEnCoursEventArgs : System.EventArgs {
 
  private long position = 0;
  public long Position {

   get {

    return(this.position);
   }
  }

  private bool cancel = false;
  public bool Cancel {

   get {

    return(this.cancel);
   }
   set {

    this.cancel = value;
   }
  }

  public CopieEnCoursEventArgs(long position) {

   this.position = position;
  }
 }
}

Notez que dans ce cas, la propriété Cancel doit être en lecture ET écriture pour donner une chance à l'abonné de pouvoir modifier sa valeur.

A ce niveau là, on a fait le plus dur (et ce n'était pas bien dur, il faut bien l'avouer ;-) ). Il nous reste à modifier le code de la fonction CopieFichier pour déclencher l'émission des événements comme il faut. Cela donne ceci :

public void CopieFichier(string cheminFichierDestination, bool ecrasement) {

 // On vérifie si le fichier de destination n'existe pas déjà
 if (File.Exists(cheminFichierDestination)) {

  // Si demandé, on écrase le fichier de destination
  if (ecrasement) {

   File.Delete(cheminFichierDestination);
  }
  else {

   throw new IOException(string.Format("Le fichier de destination '{0}' existe déjà.", cheminFichierDestination));
  }
 }

 StatutCopie statutCopie = StatutCopie.Aucun;
 int dureeCopie = System.Environment.TickCount;

 try {

  statutCopie = StatutCopie.Succes;

  // On ouvre le fichier de destination en écriture
  using (FileStream fileStreamDestination = new FileStream(cheminFichierDestination, FileMode.Create, FileAccess.Write)) {

   // On ouvre le fichier d'origine en lecture
   using (FileStream fileStreamOriginal = new FileStream(this.cheminFichier, FileMode.Open, FileAccess.Read)) {

    // On informe l'abonné de l'imminence de la copie
    if (CopieDemarrage != null) {

     CopieDemarrage(this, new CopieDemarrageEventArgs(this.cheminFichier, cheminFichierDestination, fileStreamOriginal.Length));
    }

    // On lit le prochain octet
    int octet = fileStreamOriginal.ReadByte();
    while (octet != -1) {

     // On écrit l'octet
     fileStreamDestination.WriteByte((byte)octet);

     // On informe l'abonné de la progression de la copie
     if (CopieEnCours != null) {

      CopieEnCoursEventArgs copieEnCoursEventArgs = new CopieEnCoursEventArgs(fileStreamOriginal.Position);
      CopieEnCours(this, copieEnCoursEventArgs);

      // L'utilisateur a-t'il demandé l'annulation de la copie ?
      
if (copieEnCoursEventArgs.Cancel) {

       statutCopie = StatutCopie.Annulation;
       break;
      }
     }

     octet = fileStreamOriginal.ReadByte();
    }

    fileStreamOriginal.Close();
   }
   fileStreamDestination.Close();
  }
 }
 catch {

  // On renvoit telle quelle l'exception qui a lieu,
  
// l'objectif étant simplement de pouvoir indiquer
  
// correctement le statut de la copie
  statutCopie = StatutCopie.Echec;
  throw;
 }
 finally {

  dureeCopie = System.Environment.TickCount - dureeCopie;

  // On informe l'abonné de la fin de la copie
  if (CopieFin != null) {

   CopieFin(this, new CopieFinEventArgs(statutCopie, dureeCopie));
  }
 }
}

Ceci étant fait, il ne nous reste plus qu'à compiler l'ensemble de ce code dans une assembly (Librairie_CS.dll).

Enfin, pour illustrer l'utilisation de ces événements depuis une IHM, nous allons créer un projet de type Application Windows et déposer deux Label, deux TextBox, deux Button et une ProgressBar :

 

Puis voici le code client :

namespace ClientWindows_CS {

 public class Form1 : System.Windows.Forms.Form {

  ...
  

  private Librairie_CS.GestionFichiers gestionFichiers = null;
  private float taille = 0;
  private bool cancel = false;

  private void buttonCopie_Click(object sender, System.EventArgs e) {
  
   gestionFichiers = new Librairie_CS.GestionFichiers(textBoxFichierSource.Text);

   gestionFichiers.CopieDemarrage += new Librairie_CS.CopieDemarrageEventHandler(gestionFichiers_CopieDemarrage);
   gestionFichiers.CopieEnCours += new Librairie_CS.CopieEnCoursEventHandler(gestionFichiers_CopieEnCours);
   gestionFichiers.CopieFin += new Librairie_CS.CopieFinEventHandler(gestionFichiers_CopieFin);

   gestionFichiers.CopieFichier(textBoxFichierDestination.Text, true);
  }

  private void buttonAnnuler_Click(object sender, System.EventArgs e) {

   cancel = true;
   buttonAnnuler.Enabled = false;
   Application.DoEvents();
  }

  private void gestionFichiers_CopieDemarrage(object sender, Librairie_CS.CopieDemarrageEventArgs e) {

   cancel = false;
   taille = Convert.ToSingle(e.TailleFichier);
   progressBar1.Minimum = 0;
   progressBar1.Maximum = 100;
   progressBar1.Value = 0;
   buttonAnnuler.Enabled = true;

   Application.DoEvents();
  }

  private void gestionFichiers_CopieEnCours(object sender, Librairie_CS.CopieEnCoursEventArgs e) {

   e.Cancel = cancel;
   progressBar1.Value = Convert.ToInt32(Convert.ToSingle(e.Position) / taille * 100D);

   Application.DoEvents();
  }

  private void gestionFichiers_CopieFin(object sender, Librairie_CS.CopieFinEventArgs e) {

   progressBar1.Value = 0;
   buttonAnnuler.Enabled = false;
   MessageBox.Show(this, string.Format("Statut : {0} - La durée du traitement a été de {1}", e.StatutCopie.ToString(), (new TimeSpan(0, 0, 0, 0, e.Duree)).ToString()), "Copie finie");
  }

}

Les modèles de conception

Il est courant lors du développement d'un projet, d'utiliser des Modèles de conceptions (Design Patterns), pour adresser certains problèmes relatifs à la modélisation et à l'architecture de l'application. Cependant, la définition de ces modèles de conceptions, est souvent difficile à communiquer avec précision. Un bref retour aux origines et à l'histoire du concept s'impose.

L'origine informatique des modèles de conception est attribuée aux travaux de Christopher Alexander. En tant qu'architecte en bâtiment, Alexander a remarqué l'apparition de problèmes communs relatifs à un contexte donné. D'après Alexander, un modèle de conception possède 3 éléments essentiels ; Problème/solution/Conséquence ; qui permettent à un architecte de rapidement adresser les problèmes d'une manière connue et acceptée. Publié il y a 35 ans pour la première fois, A Pattern Language: Towns, Buildings, Construction (Alexander et al, Oxford University Press, 1977), il introduit plus de 250 Modèles de conception Architecturaux et fournit les bases pour l'introduction du concept dans le développement de logiciels.

C'est en 1995, que l'analogie entre les modèles de conception et la construction d'applications se répand largement dans l'industrie du logiciel. Les quatre auteurs Gamma, Helm, Johnson, et Vlissides (connus comme le gang des quatre, ou GoF), ont rapproché dans leurs travaux, Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley Pub Co, 1995), les modèles de conception d'Alexander et l'émergence croissante du développement orienté objet.

Basé sur leur expérience collective des frameworks objets existant, le GoF a fournit 23 Modèles de conception qui adressent les problèmes et solutions rencontrés lors de la modélisation d'applications. Après la publication, ce concept n'a cessé d'augmenter en popularité en résolvant de nombreux problèmes rencontrés dans le domaine des logiciels. Due à cette popularité, la notion d'anti-modèles (anti-Pattern) a émergé, et il est communément acquis que les solutions sans modèles sont un frein à l'évolution d'un projet de grande envergure.

 

Pourquoi les modèles de conception ?

Bien qu'il n'y ait rien de magique dans les modèles de conception, ce sont des outils extrêmement puissants pour développeurs ou architectes qui s'embarquent dans le développement de projet. Les modèles de conception garantissent que les problèmes soient adressés par des solutions reconnues et acceptées de tous.

La force fondamentale des modèles reste dans le fait, que la plupart des problèmes ont vraisemblablement été rencontré et résolu par un ensemble d'individus et par la communauté des développeurs. En d'autres termes, les modèles fournissent un mécanisme de partage de solutions qui fonctionnent entre les développeurs et les organisations. Peu importe l'origine des modèles, c'est un formidable levier de l'expérience collective. Ils garantissent, l'implémentation rapide d'un code robuste, et la réduction des erreurs lors de la conception ou de l'implémentation. De plus, les modèles de conceptions offrent une sémantique commune entre chaque membre d'une équipe. Ceux qui ont été impliqués dans un projet un temps soit peu important, savent, qu'avoir un jeu commun de modèles, de syntaxes et de principes de développement, est critique pour l'achèvement du projet. Les modèles de conception, s'ils sont utilisés judicieusement, peuvent faire gagner un temps précieux.

Les modèles du Framework .NET

Les modèles de conception ne sont pas inféodés à un langage ou à une plateforme de développement particulier (bien que le GoF ait limité leurs exemples à du C++ ou du SmallTalk) ; l'arrivée du Framework .NET de Microsoft en est l'illustration parfaite. Elle permet désormais à des développeurs utilisant à l'origine un langage non orienté objet comme Microsoft Visual Basic, de développer selon les principes Objets. Pendant le développement des Librairies de classes du Framework (FCL), Microsoft a appliqué plusieurs modèles introduits par le GoF en 1994. Vu l'ampleur des fonctionnalités exposées par le Framework .NET, Microsoft a également introduit de nouveaux modèles.

Dans cette série d'articles, nous examinerons en détail plusieurs modèles disponibles dans la FCL. La structure générale et les bénéfices de chaque modèle seront étudiés, suivie d'un examen de leur implémentation spécifique dans la FCL. Bien que la plupart des modèles que nous examinerons trouvent leurs origines dans le GoF, le Framework .NET offre un nombre de fonctionnalités innovantes, pour lesquelles il y a très peu ou pas du tout de guide disponible actuellement. Les modèles utilisés pour ces nouvelles fonctionnalités seront également examinés. Nous débutons ici avec le modèle Observateur.

Le modèle Observateur

L'un des principes primordiaux du développement orienté objet, est la bonne répartition des tâches dans une application donnée. Chaque objet du système doit se focaliser sur sa propre implémentation dans le domaine qu'il couvre et rien de plus. Pour faire cours, un objet doit faire une seule chose et la faire bien. Cette approche assure qu'un couplage faible existe entre les objets, permettant une meilleure réutilisation, et un système plus maintenable.

Une des zones où la bonne répartition des tâches est des plus importantes, est l'interaction entre l'interface utilisateur, et la logique métier. Au cours du développement d'une application, il est souvent courant que l'interface utilisateur soit amenée à changer rapidement, sans pour cela impacter le reste de l'application. L'inverse est vrai aussi. Il est probable que les besoins métiers changent sans que cela en affecte l'interface utilisateur. Chacun ayant une expérience dans le développement, sait, que dans la plupart des cas, il y aura des modifications à apporter dans ces deux zones. Sans la séparation de l'IU avec le reste de l'application, la modification d'une portion de l'interface peut impacter l'intégralité de l'application.

Le besoin de séparer l'interface utilisateur et la logique métier, est un problème récurrent dans le développement d'applications. De ce faite, les frameworks orienté objet développés depuis l'apparition des interfaces utilisateur graphique (GUI) ont été construit dans ce sens. Il n'est donc pas surprenant que la plupart ait adopté un modèle similaire pour fournir cette fonctionnalité. Ce modèle est connu sous le nom d'Observateur et permet de faire la distinction précise entre chaque objet du système. Comme la plupart des modèles, son utilisation s'étend au delà des limites de ses intentions primaires et il est assez courant de voir cette solution utilisée dans des segments de l'application qui ne soient pas liés à l'interface utilisateur.

Model logique

Bien qu'il existe un certain nombre de variations du modèle observateur, la base du modèle est constituée de deux acteurs primaires l'observateur et le sujet (Pour ceux familier de Smaltalk et du modèle MVC, reconnaîtront dans ces deux termes, respectivement View et Model).

Dans l'interface utilisateur, l'observateur est l'objet responsable d'afficher les données, alors que le sujet, représente la couche métier.

Comme vous pouvez le constater dans la Figure 1 une association logique existe entre l'observateur et le sujet. Lorsqu'un changement survient dans l'objet sujet (ex : Modification d'une variable d'instance), l'observateur observe et met à jour son affichage en conséquence.

Figure 1. Relation entre l'Observateur et le Sujet

Par exemple, supposons que nous ayons une application simple, qui suive quotidiennement le prix d'une action cotée en bourse. Nous avons d'un coté une classe Stock qui représente différentes actions du NASDAQ. Cette classe contient une variable d'instance, qui représente la fluctuation du prix courrant (la manière dont il change n'est pas importante ici). De l'autre coté, nous avons la classe SockDisplay qui affiche cette information à l'utilisateur, en écrivant sur la sortie standard (Console). Dans cette application, une instance de la classe Stock agit en tant que Sujet et une instance de la classe StockDisplay agit en tant que Observateur. Si le prix change, la variable d'instance change et comme StockDisplayobserveStock, alors l'affichage change.

Avec ce processus on s'assure qu'une frontière existe entre les classes Stock et StockDisplay. Supposons maintenant que les besoins de l'application change, et qu'un formulaire d'affichage soit nécessaire. Il suffit simplement alors de construire une nouvelle classe StockForm qui agira en tant qu'observateur. Pas besoin ici de modifier la classe Stock. D'ailleurs elle n'est même pas au courant qu'une modification a été faite. De plus, si on a besoin d'utiliser une autre source pour retrouver le prix (Par exemple un service Web plutôt qu'une Base de données), la classe StockDisplay ne change pas elle continue à observer ignorant tous changements.


Modèle physique

Comme dans la plupart des solutions, c'est dans les détails que les difficultés surviennent. Il n'y a pas d'exception avec le modèle Observateur. En effet, bien qu'il soit fait état dans le modèle logique que l'observateur observe le sujet, lorsqu'on passe à l'implémentation cette logique ne calque pas tout à fait à la réalité. Plus exactement, l'observateur doit s'inscrire au près du sujet, en précisant qu'il va observer. Lorsqu'un changement survient, le sujet notifie l'observateur. Lorsque l'observateur ne souhaite plus observer il se désinscrit du sujet. Ces étapes sont connues respectivement comme inscription, notification, et désinscription.

La plupart des framework implémente ces mécanismes via des fonctions de rappel (callbacks). Le diagramme UML des Figures 2, 3, et 4 montre l'utilisation de cette approche.

Pour ceux qui ne seraient pas familier avec ce type de diagramme, les rectangles représentent les objets tandis que les flèches représentent les appels de méthodes.

Figure 2. Enregistrement de l'Observateur

Dans la Figure 2, l'observateur invoque la méthode Register du sujet, ce passant lui-même en tant qu'argument (pour le rappel). Une fois que le sujet reçoit cette référence, il la stocke pour pouvoir notifier l'observateur lorsqu'un changement survient. Plutôt que de stocker la référence à l'observateur directement dans une variable d'instance, la plupart des implémentations du modèle observateur, délèguent cette mission à un objet distinct, le Conteneur (Container). Nous verrons plus tard les bénéfices qu'apporte l'utilisation de ce conteneur. L'action suivante, est le stockage de la référence de l'observateur à l'aide de la méthode Add du conteneur.

Figure 3. Notification à l'Observateur

Dans la Figure 3 on peut noter, que quand un changement apparaît (AskPriceChanged), le sujet retrouve tous les observateurs en invoquant la méthode GetObservers du conteneur. Ensuite, le sujet notifie les observateurs qu'un changement est intervenu, en appelant la méthode Notify de chaque Observateur.

Figure 4. Désenregistrement de l'Observateur

Figure 4 la séquence de désenregistrement est exécutée lorsque l'observateur n'a plus besoin d'observer le sujet. Un appel à la méthode UnRegister est faite qui délègue le travail au conteneur via la méthode Remove.

Revenons à notre application, et examinons les impacts de ce processus d'inscription et désinscription. Lors du démarrage de l'application, une instance de la classe StockDiplay s'inscrit auprès d'une instance de la classe Stock, ce passant elle-même en tant qu'argument à la méthode Register. L'instance de la classe Stock maintient une référence d'une instance de la classe StockDisplay (dans le conteneur). Lorsque le prix change, Stock notifie StockDisplay du changement, en appelant la méthode Notify. Lorsque l'application se termine, le processus inverse doit s'effectuer. StockDisplay invoque la méthode UnRegister de Stock et la relation entre les deux se termine ainsi. Examinons maintenant les bénéfices d'utilisation du Conteneur, plutôt que d'une variable d'instance pour le stockage d'une référence à l'observateur. Imaginons que nous ayons besoin en plus de StockDisplay, d'un graphique en temps réel de la fluctuation des prix. Pour cela, nous allons créer une nouvelle classe StochGraph qui affiche le prix sur l'axe des x et les heures sur l'axe des y. Lorsque l'application démarre elle inscrit les instances des classes StockDisplay et StockGraph avec une seule instance de la classe Stock. Puisque le sujet stocke les observateurs dans un conteneur, lorsque le prix change, l'instance de stock peut notifier simplement les deux observateurs ou un nombre infini d'observateur en opposition à un nombre fini si on devait passer par des variables d'instances. On ne sait jamais à l'avance combien d'observateurs vont venir s'inscrire. En utilisant un conteneur on ajoute de la flexibilité à notre solution.

Bien que cela ne soit pas obligatoire, la plupart des frameworks fournissent un jeu d'interfaces que doivent implémenter les observateurs et les sujets. Dans nos exemples C# et VB.NET ci-dessus, l'interface IObserver expose une méthode publique Notify. Cette interfacedoit être implémentée par toutes les classes qui ont l'intention d'agir en tant qu'observateur. L'interface IObservable qui expose les méthodes Register et UnRegister, doit quand à elle être implémentée par toutes les classes qui souhaitent agir en tant que sujet. Ces interfaces prennent généralement la forme de classe abstraite virtuelle ou true interfaces si le langage que vous utilisez gère cette dernière option.

L'utilisation de ces interfaces IObserver et IObservable, introduit un couplage faible entre l'observateur et le sujet et permettent des opérations indépendantes de l'implémentation.

Interfaces IObserver et IObservable (C#)

//Toutes les classes observateurs doivent implémenter ces interfaces
public interface IObserver {
 
 void Notify(object anObject);
 
}//IObserver
 
public interface IObservable {
 
 void Register(IObserver anObserver);
 void UnRegister(IObserver anObserver);
 
}//IObservable

Retournons à notre exemple d'application. Nous savons que la classe Stock agit en tant que sujet, elle doit donc implémenter l'interface IObservable, alors que la classe StockDisplay doit implémenter l'interface IObserver.

Puisque toutes les opérations sont définies par l'interface, plutôt que par une classe spécifique, la classe Stock n'est pas liée à la classe StockDisplay et vice versa. Cela nous permet de changer rapidement l'implémentation de l'observateur ou du sujet sans impacter le reste de l'application (Remplacement ou ajout d'un observateur différent à StockDisplay).

Il n'est pas rare de voir également une classe de base commune dont les sujets dériveront. L'utilisation de cette classe de base réduit l'effort requis pour implémenter le modèle observateur. C'est elle qui implémente l'interface IObservable et fournit l'infrastructure requise pour le stockage des instances des observateurs. L'exemple en C# et VB.NET ci-dessous de la classe ObservableImpl esquisse ce que pourrait être cette classe de base. On remarquera dans les méthodes Register et UnRegister qu'elle délègue le stockage de l'observateur à une table de hachage (HashTable). Bien évidement n'importe quel conteneur peut convenir (Par soucis de simplicité, nous utilisons ici une table de hachage comme conteneur). A noter également la méthode NotifyObservers qui est utilisée pour notifier tous les observateurs stockés dans la table de hachage. Lorsque cette méthode est appelée, elle parcourt la table de hachage et invoque la méthode Notify de chaque observateur.

Classe ObservableImpl (C#)
//Classe helper qui implémente les interfaces IObservable
protected Hashtable _observerContainer=new Hashtable();
public class ObservableImpl:IObservable {
 
 // Conteneur pour stocker les instances des observateurs
 protected Hashtable _observerContainer=new Hashtable();
 
 //inscrit l'observateur
 public void Register(IObserver anObserver){
 _observerContainer.Add(anObserver,anObserver); 
 
 }//Register
 
 //des-inscrit l'observateur
 public void UnRegister(IObserver anObserver){
 _observerContainer.Remove(anObserver); 
 }//UnRegister
 
 //Méthode commune pour notifier tous les observateurs
 public void NotifyObservers(object anObject) { 
 
 //Parcours les observateurs et invoque leur 
méthode Notify
 foreach(IObserver anObserver in 
_observerContainer.Keys) { 
 
 anObserver.Notify(anObject); 
 
 }//foreach
 
 }//NotifyObservers
 
}//ObservableImpl

Plutôt que de fournir sa propre implémentation de IObservable, la classe Stock bénéficie de cette infrastructure en dérivant de la classe ObservableImpl. Puisque la classe ObservableImpl implémente l'interface IObservable, aucune modification n'est requise dans la classe StockDisplay. Cette approche simplifie réellement l'implémentation du modèle Observateur, permettant à de multiples sujets de réutiliser la même fonctionnalité tout en maintenant un couplage faible entre les classes impliquées.

L'exemple C#  d'observateur ci-dessous montre l'utilisation des interfaces IObservable et IObserver et de la classe ObservableImpl.

Cet exemple utilise également MainClass  pour associer un Observateur à un sujet et modifier la propriété AskPrice d'une instance de la classe Stock. Cette propriété est responsable d'invoquer la méthode NotifyObservers de la classe de base.

.
Exemple d'Observateur (C#)
//Classe stock
public class Stock:ObservableImpl {
 
 // Variable d'instance pour le prix
 object _askPrice;
 
 //Propriété
 public object AskPrice {
 
 set { _askPrice=value;
 
base.NotifyObservers(_askPrice);
 
}//set
 
 }//PropriétéAskPrice
 
}//Stock
 
//Représente l'interface utilisateur
public class StockDisplay:IObserver {
 
 public void Notify(object anObject){ 
 Console.WriteLine("le nouveau prix est:" 
+ anObject); 
 }//Notify
 
}//StockDisplay
 
public class MainClass{
 
 public static void Main() {
 
 //Création d'instances
 StockDisplay stockDisplay=new 
StockDisplay();
 Stock stock=new Stock();
 
 //Inscription de la grille
 stock.Register(stockDisplay);
 
 //Boucle 100 fois pour modifier le prix
 for(int looper=0;looper < 100;looper++) 
{
 stock.AskPrice=looper;
 }
 
 //Désinscription
 stock.UnRegister(stockDisplay);
 
 }//Main
 
}//MainClass


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