POURQUOI CE BLOG, POUR QUI ?


POURQUOI CE BLOG, POUR QUI ?
Ce Blog s'adresse à tous ceux qui sont passionnés par les sciences informatiques , Professionnels,Etudiants,Amateurs ...
Les sujets exposés dans la suite se rapporteront essentiellement sur l'analyse informatique,la programmation,le développement ainsi que à l'architecture IT.
QUI SUIS JE ?
Je suis Kangulungu Lubilanji, Consultant-Freelance sur les technologies .NET,C#,ASP.NET ... Contactez moi pour plus d'informations.

Design pattern : Introduction,1.Le Pattern Abstract Factory

Introduction
Un design pattern ou pattern de conception consiste en un schéma à objets qui forme une solution à un problème connu et fréquent. Le schéma à objets est constitué par un ensemble d'objets décrits par des classes et des relations liant les objets.
Les patterns répondent à  des problèmes de conception de logiciels dans le cadre de la programmation par objets. Ce sont des solutions connues et éprouvées dont la conception provient de l'expérience de programmeurs. Il n'y a pas d'aspect théorique dans les patterns, notamment pas de formalisation (à la différence des algorithmes).

A.Introduction aux patterns de construction
A.1. Présentation
Les patterns de construction ont pour vocation d'abstraire les mécanismes de création d'objets. Un Système utilisant ces patterns devient indépendant de la façon dont les objets sont crées et, en particulier, des mécanismes d'instanciation des classes concrètes.

Ces patterns encapsulent l'utilisation des classes concrètes et favorisent ainsi l'utilisation des interface dans les relations entre objets, augmentant les capacités d'abstraction dans la conception globale du système.

A.2. Problématique
Dans la plupart des langages à objets, la création d'objets se fait grâce au mécanisme d'instanciation se fait par appel de l'opérateur new paramétré par classe.
Dans cette optique il est difficile de paramétrer le mécanisme de création d'objets, la classe transmise en paramètre à l’opérateur new ne pouvant être substitué par une variable. L'utilisation d'instruction conditionnelles dans le  code du client est souvent pratiqué avec l’inconvénient que chaque changement dans la hiérarchie des classes à instancier demande des modifications dans le  code des clients.


1.Le Pattern Abstract Factory
Description: 
Le but du pattern Abstract Factory est la création d'objets regroupés en famille sans devoir connaître les classes concrètes destinées à la création de ces objets.
Exemple:
Lorsqu'un Client à la possibilité d'instancier des produits (a,b,c,..) directement via des sous-classes concrètes d'un ensemble (architecture) composée de famille(F1a, F1b, F2a,...) de produit, si, par la suite de nouvelles familles (F3a,..) de produit doivent être prises en compte, les modifications à apporter à l'objet Client peuvent être assez conséquentes.
But: 
Rendre la classe Client indépendante de l'ensemble (architecture) des familles(F1a, F1b, F2a,...) et des produits.
Solution
- On introduit une Interface FabriqueAbstraite contenant les signatures des méthodes pour définir chaque produit, dont le type de retour de ces méthodes est constitué par les types des classes abstraites de produit.
-Une sous classe d'implantation FabriqueAbstraite est introduit pour chaque famille de produit FabriqueConcretFamilleX ,FabriqueConcretFamilleZ,.. Une telle sous-classe implante les opérations de de création du produit appropriée pour la famille à laquelle elle est associée.
-L'objet Client prend alors pour paramètre une instance répondant à l'interface FabriqueAbstraite c'est à dire  une instance de FabriqueConcretFamilleX ou FabriqueConcretFamilleZ,.. (car il y a un mécanisme d'héritage)
Avantages:
- L'objet Client n'a pas besoin de connaître les sous-classes concrètes et reste ainsi indépendant des familles de produit.
Diagramme de classes:
Participants:
- FabriqueAbstraite est une interface spécifiant les signatures des méthodes créant les différents types de produit.
- FabriqueConcretFamille1FabriqueConcretFamille2 sont les classes concrètes implantant les méthodes créant les produits pour chaque famille de produit. Connaissant la famille et le produit, elles sont capables de créer une instance du produit pour cette famille.
- TypeAAbstraitTypeBAbstrait sont les classes abstraites des produits indépendamment de leur famille. Les familles sont introduites dans leurs sous-classes concrètes.
- Client
Collaborations:
La classe Client utilise une instance de l'une des fabriques concrètes pour créer ses produits au travers de l'interface FabriqueAbstraite.
Domaines d'utilisation:
- Un système utilisant des produits a besoin d'être indépendant de la façon dont ces produits sont créés et regroupés.
- Un système est paramétré par plusieurs familles de produits qui peuvent évoluer. 

La Programmation OO: Chapitre 20. Design patterns

Chapitre 20. Design patterns
Les desing patterns sont des recettes de conception OO qui à l'origine furent réunies dans le célèbre livre du gang des quatre. Ils sont depuis devenus presque aussi courants que les langages de programmation et UML. Leur connaissance complète toute formation approfondie en OO et permet une meilleure compréhension des mécanismes de ce style de programmation. Ce chapitre en décrit quelques-uns à l'aide d'exemples concrets.

Introduction aux design patterns, dans toute discipline, quelle qu'elle soit, l'expert est celui qui à développé un flair suffisant pour identifier rapidement les problèmes types et qui sait comment les resoudres facilement et vite. En 1995, le GOF (Gang Of Four), a transposé la pratique des design patterns architecturaux dans l'univers du logiciel.
Si le recours à ces patterns ne semble devoir intervenir q'une fois le problème rencontré et pas avant, il n'en reste pas moins vrai que leur maîtrise permet de réorienter, et cela dès le début, la conception du code en leur donnant une place prépondérante. Cette réorientation ne peut être que bénéfique puisqu'elle reprend la longue expérience des nombreux développeurs antérieurs. Certains vont même jusqu'à parler de développement par les « patterns » ou à partir d'eux, se limitant à une simple combinaison de ceux-ci.
Le livre du GOF contient vingt-trois design patterns, dans ce livre chaque pattern est présenté par son nom, associé au problème qui motive son recours,assorti de la solution proposée ainsi que des limites de sa mise en oeuvre.
Toujours dans ce même livre, les patterns sont organisés en trois catégories : 

« créationnels », c'est-à-dire plutôt centrés sur la problématique de la construction d'objets grâce à l'amplification ou au court-circuit du constructeur ; 

« structurels », c'est-à-dire plutôt centrés sur les schémas associatifs des classes, afin, par exemple, de contrebalancer l'héritage et l'association ; 

« comportementaux », c'est-à-dire décrivant plutôt des mécanismes astucieux à mettre en oeuvre pour l'exécution des codes  ;

Le pattern singleton 
Dans la vie réelle, de nombreuses classes n'ont parfois comme raison  d'être que la création d'un et un seul objet.Une première possibilité serait d'en faire des classes statiques, comme il  en existe d'ailleurs pas mal dans la programmation OO. Dans un  soucis de cohérence, le GOF propose la mise au point d'une classe, dite classe « singleton », qui ne peut donner naissance qu'à une seule instance et garantir l'accès unique qu'à cette instance. Supposons que notre programme de flipper ne doive fonctionner qu'avec une seule boule. Le  code qui suit vous permet de réaliser cela grâce à une classe SingletonBoule, qui ne permettra qu'à une seule boule de se balader dans notre flipper.


class SingletonBoule : Boule {
        private static SingletonBoule b = null;  // notez le static
        private static Object ob = new Object();

        private SingletonBoule(int x, int y, int r) : base(x,y,r) {  // le constructeur doit être privé
          
        }
        public  static SingletonBoule getBoule(int x, int y, int r) {
            if (b == null)
            {
                b = new SingletonBoule(x, y, r);
            }
            return b;
        }
    }
    class Boule {
        int x, y, r;
        public Boule(int x, int y, int r) {
            this.x = x;
            this.y = y;
            this.r = r;
        }
    }

Le pattern Adaptateur, supposons que dans le code original du flipper, nous utilisons l'interface ObstacleDuFlipper dont une des méthodes abstraites dessineToi(Graphics g), à redéfinir dans les classes précises d'obstacles, est responsable de l'apparition  et de l'apparence de cet obstacle. Supposons qu'un autre développeur nous propose de fournir pour l'obstacle Champignon une classe de sa fabrication, dénommée ChampignonJoli, dont l'énorme avantage est de proposer une méthode sophistiquée pour dessiner magnifiquement ce type d'obstacle et nommée public void apparaitBeauChampignon(Graphics g, Rectangle r). Grâce aux deux diagrammes UML ci-dessous l'un misant sur l'héritage l'autre sur la composition - vous comprendrez aisément comment, sans modifier d'aucune manière le code d'origine, celui-ci pourra exploiter cette méthode. La manière dont la méthode dessineToi doit être redéfinie est indiquée pour les deux versions dans une note accolée à la classe Champignon. La classe qui sert d'intermédiaire entre l'interface de départ - celle que le flipper est contraint et forcé d'utiliser - et la classe contenant la méthode que nous souhaitons récupérer est Adaptateur. Il s'agit ici de la classe Champignon. Cet exemple montre comment, dans de nombreux cas, l'héritage et la composition peuvent apparaître comme alternative pour un  même problème.
Par héritage :
Extrait de code des classes et interface :
  interface ObstacleDuFlipper {
         void dessineToi(Graphics g);
    }
    class Champignon :ChampignonJoli, ObstacleDuFlipper {

        public void dessineToi(Graphics g)
        {
            Rectangle r  = new Rectangle ();
            apparaitBeauChampignon(g, r);
        }
    }
    class ChampignonJoli 
    {
        public void apparaitBeauChampignon(Graphics g, Rectangle r)
        { 
            //code
        }
    }
Par composition :
Extrait de code des classes et interface :
 interface ObstacleDuFlipper {
         void dessineToi(Graphics g);
    }
    class Champignon  {
        ChampignonJoli cj;
        public Champignon() { 
            cj = new ChampignonJoli();
        }
        public void dessineToi(Graphics g)
        {
            Rectangle r = new Rectangle ();
            cj.apparaitBeauChampignon(g, r);
        }
    }
    class ChampignonJoli 
    {
        public void apparaitBeauChampignon(Graphics g, Rectangle r)
        { 
            //code
        }
    }

Les Patterns patron de méthode,proxy, observer et memento, l'idée de pattern « patron de méthode » est de fournir un squelette de code dans lequel, bien qu'utilisée, une méthode agit sans son code. Il suffit alors au programmeur désirant exploiter ce squelette d'implémenter le corps de la méthode. Par exemple par redéfinition d'une méthode provenant d'une interface,afin de bénéficier de son squelette. Celui-ci sera complété et adapté à ce corps. Il se caractérise donc par une partie fixe, proposé par un premier développeur et par un partie variable (que fixe un  deuxième développeur). Cette seconde partie se résume au corps de la méthode à définir. Elle est variable car elle dépend de la définition du corps.

Si, dans le flipper, les obstacles sont installés dans un vecteur, et qu'il nous viens l'envie de les réordonner dans ce même vecteur, la seule information qui fait défaut est le critère selon les obstacles doivent se comparer et se réordonner. Toute la pratique de comparaison peut être mise en oeuvre, à l'exception de ce critère de comparaison qui constitue ici, en fait, le corps manquant de la méthode.

Pour le pattern « proxy », la présence du stub côté client, appelé à jouer le rôle de serveur, en est l'utilisation la plus connue. Le proxy se substitue à un objet manquant ou distant, afin de ne pas modifier radicalement le code de son interlocuteur, tout en apportant quelques fonctionnalités additionnelles indispensables au fonctionnement de l'objet distant. Un proxy  serait le bienvenu si parmi les obstacles du flipper, l'un d'entre eux prend énormément de temps à se dessiner car il doit chercher sur le web une image particulière. Comme indiqué dans la figure ci-dessous, et de manière très semblable au  pattern « adaptateur », il est possible de remplacer cet obstacle par un proxy qui, tout en déclenchant la méthode appropriée sur l'obstacle de départ, ajoute quelques fonctionnalités qui permettent de patienter, par exemple un message signalant que la recherche ou le téléchargement sont en cours.

 Le but pattern « observer » est de maintenir deux objets synchronisés, bien qu'ils ne possèdent pas entre eux de lien explicite.

Enfin le pattern « memento » vous permet de revenir à la case départ si un ensemble de manipulation d'objet n'aboutissent pas et qu'il devient important de restaurer les objets modifiés dans leurs états d'origine. Il suffit soit de le stocker au départ des manipulations sur le disque dur par une des méthodes décrites dans le chapitre 19, soit de les cloner et de conserver ces copies pendant toutes la durée des manipulations. En général, la classe de l'objet à mémoriser intègre un mécanisme de génération d'un « memento », qui ne conserve que les attributs susceptibles de modifications et qu'il est important de pouvoir restituer.

Le pattern flyweight, prenons le cas d'une molécule qui comme chacun sait, est un réseau d'atomes. Chaque atome possède des informations qui lui sont propres, reprises dans la classe : son symbole, son poids atomique, sa valence, sa charge éventuelle, etc. Une première solution rapide pour réaliser la classe Molecule aurait pu se limiter à une relation de composition entre la classe Molecule et la classe Atome. Cependant, lorsqu'on sait qu'une même molécule organique peut contenir jusqu'à des millions d'instances du même atome de carbone, il est stupide, et surtout extrêmement coûteux en mémoire, de répliquer pour chacune de ces instances toutes les informations comme le poids, la valence ou le symbole. Le remède à cela, application directe du pattern « flyweight » dont le but essentiel est de réduire l'espace de stockage des objets, consiste, comme indiqué ci-dessous à regrouper toutes ces informations dans une classe à part. L'économie de mémoire peut très vite s'avérer considérable. Nous retrouvons ce même besoin d'économie lorsque les cellules du système immunitaire se clonent. Il n'est sans doute pas nécessaire de stocker dans chaque clone l'entièreté de l'information. Ainsi, le même patrimoine génétique se retrouve pour l'essentiel dans toutes les cellules, ce qui autorise à ne le stocker qu'une fois en mémoire, toutes les cellule pouvant alors y faire référence.
Les patterns builder et prototype, un type d'objet dont la construction s'avère délicate dans le réacteur chimique sont les molécules. De nouvelles molécules apparaissent et disparaissent au cours de la simulation, résultant des réactions impliquant celles s'y trouvant au départ. En général, le programme lit les molécules de départ à partir d'un fichier ou d'une fenêtre initiale, dans lesquels celles-ci sont décrites à l'aide de leur symbole : CH4 ou NH3. Il faut alors pouvoir transcrire ces formules en un graphe canonisé, car c'est sous cette forme qu'elles seront ensuite, car elle exige une lecture et un traitement attentif des expressions définissant les molécules initiales.

Lorsqu'une réaction se produit, de nouvelles molécules apparaissent. Là encore, la génération d'une nouvelle molécule exige de nombreuses manipulations des molécules de départ.

La solution qui vient à l'esprit consiste à prévoir et à installer tous ces traitements dans le ou les constructeurs de la classe Molecule. La solution préconisée par le GOF est tout autre. Dès que la genèse de nouveaux objets exige une séquence de traitements compliqués (par exemple ; lecture et parsing d'un chaîne de caractères), il propose de séparer cette séquence de la construction de la molécule à proprement parler et de l'installer dans une classe à part, ici la classe MolecularBuilder (voir code ci-dessous), dont une  ou plusieurs méthodes renverrons une nouvelle instance de la classe Molecule, aboutissement de ces nombreux traitements. Le constructeur de la classe Molecule se limite à une création très élémentaire de l'objet, précédant ou concluant tous ces traitements.
 public class MoleculeBuilder { 
        // code
        public static Molecule build(string s) {
            Molecule m = new Molecule();
            // de nombreuses instructions de manipulation de « m » afin de construire la nouvelle molécule
            return m;
        }
    }
    public class Molecule { 
        //code
    }
... Et quelque part dans le main, on crée une nouvellemolécule : 
     m = MoleculeBuilder .build(s)
Lorsqu'une nouvelle molécule apparaît à l'issue d'une réaction comme celle-ci : 
AB + CD --> AC + BD,
cette nouvelle venue ne se construit pas à partir de rien, mais récupère les atomes des molécules de départ, avant la réaction. Ainsi, dans la réaction qui précède, la molécule AC est en partie une combinaison des molécules AB et CD. La manière la plus simple de générer le nouvelle venue à partir de ces deux clones. Cette démarche nous conduit tout droit au pattern « prototype » donc c'est la raison d'être : construire un nouvel objet à partir du clone d'un objet existant. Lorsqu'on clone une molécule, c'est en fait son graphe que l'on clone, en parcourant tous les noeuds atomiques de celui-ci. Le clonage d'un noeud atomique est donc défini récursivement et entraîne le clonage de tous les noeuds atomiques avec lesquels ce premier noeuds est connecté.

Le pattern façade, comme parfaitement illustré par les diagrammes de classe et de séquence de la figure qui suit, ce pattern dissimule un ensemble d'objets sous une interface unique, la seule avec laquelle l'utilisateur de tous ces objets interagira. C'est cette interface unifiée qui joue le rôle de « facade ». Elle permet une interface simplifiée avec cet ensemble d'objets ainsi qu'un point de contact unique. L'interlocuteur n'a nullement besoin de connaître la façon dons tous les objets agissent pour satisfaire les services proposés par l'interface. C'est la version logicielle de l'arbre qui cache la forêt. 

Le pattern command, supposons que l'on souhaite enrichir l'interface du flipper avec quelques menus et quelques boutons. Chaque « item » du menu et chaque bouton se voit associer une fonctionnalité donnée, comme c'est le cas dans le code Java classique qui suit :
public void actionPerformed(ActionEvent evt){
  Object obj = evt.getSource( );
   if (obj == mOpen){ // item de menu "mOpen"
      ....
   }
 if (obj == mSave){ // item de menu "mSave"
      ....
   }
if (obj == mStarting){ // item de menu "mSave"
      ....
   }
  ....
}
Les boutons et menus auront été préalablement créés et associés à une interface ActionListener comme suit pour le seul bouton blanc :

unBoutonBlanc = new Button("blanc") ;
unBoutonBlanc.addActionListener(this) ;

Le corps de la méthode actionPerformed reprend toutes les fonctionnalités associées à ces éléments de l'interface. Cette façon de procéder est loin d'être la meilleurs, et ce pour deux raisons principales. D'abord la présence de cette séquence de test (on aurait pu tout aussi bien utiliser un  « switch » à la place) dont le principal défaut est le manque de stabilité si l'idée nous viens d'ajouter quoi que ce soit dans l'interface. Dès qu'une suite de tests conditionnels ou un switch apparaît, il est toujours bon de vous demander, s'il n'y aurait pas lieu de remplacer le tout par un structure d'héritage et un mécanisme de polymorphisme. Si c'est le cas, vous accroîtrez en tout état de cause la stabilité du code face à des ajouts ou à des retraits des conditions antérieures dans le test.

La deuxième raison est que la méthode actionPerformed() est encombrée avec toutes les fonctionnalités associées à chacun des éléments de l'interface, ce qui la rend lourde et multiforme, c'est à dire peu en phase avec la simplicité et la modularité derrière lesquelles courent les développeurs OO.

L'alternative est l'application du pattern « command », dans lequel chaque tâche de l'interface se définit dans une classe à part qui implémente une interface commune contenant la méthode abstraite et polymorphe execute().
Plus de problème de stabilité et modularité assuré, comme le code Java qui suit et qui illustre cette méthode le démontre de façon convaincante. Nous limitons ce code à la fonctionnalité d'un des boutons et d'un des menus
//Tout d'abord définitions des fonctionnalités de chaque élément de l'interface
//dans une classe à part implémentant l'interface Command
public class BoutonGrisCommand extends Button implements Command{
  Frame f ;

  public BoutonGrisCommand (String label, Frame f){
        super (label):
        this.f = f;
   }
  public void execute(){
       // code
   }
}
public class FileOpenCommand extends MenuItem implements Command{
  public  FileOpenCommand  (String label){
        super (label):
   }

  public void execute(){
       // code
   }
}
// Création des nouveaux objets boutons et menus à partir de ces classes
unOpen = new FileOpenCommand ("Open") ;
unBoutonGris = new BoutonGrisCommand ("gris", this);
// Ajout de l'interface ActionListerner, comme classiquement fait
unOpen .addActionListener(this) ;
unBoutonGris .addActionListener(this) ;
// Nouvelle méthode « actionPerformed ».. C'est la que ce Pattern prend toute sa signification
// Les tests ont disparus et la méthode plus légère
public void actionPerformed(ActionEvent evt){
  Object obj = evt.getSource( );
   if (obj instanceof Command){
      ((Command)obj).execute();
   }
....
}

Le pattern décorateur, dans l'exemple du programme du flipper, à l'état actuel le flipper contient un ensemble prédéfini d'obstacles.Ces catégories sont fixées une fois pour toutes et se singularisent par l'effet de l'obstacle sur la balle. Or imaginons que nous désirons, lors de l'installation des obstacles dans un flipper, apporter plus de souplesse au comportement de ceux-ci. Alors qu'il est obligatoire que tous les obstacles de type champignon fassent rebondir la balle, on souhaiterait que certains, en plus, incrémente le score, que d'autres changent de couleur, que d'autres encore puissent faire les deux, un troisième comportement étant même envisageable, à la demande : que l'obstacle champignon puissent se caractériser, à la carte, par un ensemble de comportement différent, tous issus d'un nombre fini de possibilités de base.

Une première solution extrêmement lourde et naïve est d'imaginer autant de classe (et donc toutes les combinaisons d'héritage qui le permettent) qu'il y aurait de types d'obstacles différents. Une classe serait de type « Point-Couleur », une autre « Couleur-son », une troisième « Point-Son-Couleur »,etc. On pressent aisément la prolifération de toutes ces classes avec l'accroissement du nombre de fonctionnalités de base possibles. Une solution bien plus élégante est fournie par le pattern « décorateur », dont le diagramme de classe suit.
Le pattern décorateur permet d'enchaîner de manière très souple une succession de fonctionnalités à un même objet, en court-circuitant pour ce faire toutes les combinaisons possibles d'héritage.

Le pattern composite, celui-ci est idéal pour créer des structures complexes dans lesquelles, des éléments se trouvent  imbriqués les uns dans les autres ou, comme dans un mille-feuille, empilé les unes sur les autres. De manière générale, un « composite » est un groupe d'objets dans lequel certains objets peuvent en contenir d'autres. Certains objets seront donc plutôt de type groupe (mais pouvant contenir d'autres groupes), comme nos molécules, alors que d'autres seront isolés et singuliers, tout comme nos atomes.

Non seulement un groupe contiendra soit un ensemble d'objets individués, soit d'autres groupes mais, plus important encore, tant les groupes que les objets individués présenterons partiellement un comportement commun. Le diagramme de classe ci-suivant rend bien compte de ce pattern.

Dans ce diagramme, on considère que les molécules, en plus d'être un ensemble d'atomes, peuvent être considérées ici comme un ensemble de molécules. Les trois méthodes installées uniquement dans la superclasse doivent être redéfinies dans les deux sous-classes mais existent bel est bien dans ces deux là dans des versions différentes. Un cas très parlant d'application de ce pattern est  l'oeuvre de certaines structures arborescentes (comme le langage XML), dans lequel chaque noeud de l'arbre pointe vers un ensemble de noeuds jusqu’à atteindre les feuilles de l'arbre.

Le pattern chain of responsability
La responsable a, entre autres responsabilités, celle de répondre à une demande adressée par le client. A son niveau, la méthode gereDemande est abstraite et se trouve redéfinie dans un  ensemble de sous-classes qui, toutes, ont la possibilité soit de gérer cette demande soit, si elles en sont incapables, de déléguer cette gestion à une autre des sous-classes. C'est la raison pour laquelle la superclasse se réfère elle-même par un lien d'agrégation, afin de pouvoir « refiler le travail » à une autre des sous-classes. Chaque sous-classes peut se refiler la demande, jusqu'à aboutir à celle d'entre elles dont la méthode gereDemande est capable de satisfaire le client.

Dans notre pattern chimique composite, si nous demandons à une molécule, composée d'autre molécules, de nous donner sa masse, elle ne peut qu'additionner la sienne à celles qu'elle demande à toute celles dont elle est composée, comme dans le code qui suit : 
public double donneMasse(){
         double masse = 0;
         for (int i = 0 ; i < lesComposants.size() ; i++){
                masse += ((ComposantChimique)lesComposants.elementAt(i)).donneMasse();
         }
     return masse ;
}

Les patterns strategy et state  le pattern « strategy » est une application directe du polymorphisme. Dans ce pattern, différentes sous-classes implémente de manière une même fonctionalité stratégique. Le client n'a pas à se préoccuper de connaître les différentes implémentations ; celles-ci seront finalement choisie au moment de l'exécution du code en fonction du type précis de l'objet interlocuteur implémentant la version de la stratégie souhaité. Ainsi, dans la figure suivante, lorsque le réacteur demande à une réaction de s'exécuter, cette réaction s'effectuera différemment et selon mode propre, en fonction de sa nature définitive.

La manière dont les molécules réagissent et se comportent en général dépend donc de l'état dans lequel elles se trouvent à un moment donné. Créer autant de sous-classes de molécules qu'il y a d'états possible n'est pas une solution au problème, puisqu'une molécule peut changer d'état au cours du temps alors qu'un objet ne peut changer de classe au cours d'une simulation. La solution proposé par le GOF, comme le montre le diagramme de classe de la figure suivante, est d'associer la molécule à une classe EtatMoleculaire qui, elle, possèdera autant de sous-classes qu'il y a d'états possibles, et d'installer à même ces classes et sous-classes tout ce qui permet de modifier le comportement des molécule selon leur état. Il faut donc déléguer toutes les opérations qui dépendent de l'état de la molécule à son objet état courant. Les transitions entre états peuvent se produire dans une classe extérieure Reacteur ou  se faire à même les classes d'état. Ce pattern accopagne comme il se doit toute mise en oeurvre d'un digramme UML d'état-transition.




La Programmation OO: Chapitre 19. Persistance d'objets

Chapitre 19. Persistance d'objets
Dans ce chapitre, nous nous intéressons aux différentes manières de sauvegarde, durant l'exécution du programme, les objets sur le disque dur.

Le disque dur, tout fichier est stocké physiquement comme un ensemble d'octets enchaînés par une liste liée. Ces octets sont codés magnétiquement sur le disque. Ce dernier est doté d'une immense capacité, mais d'un accès très lent par rapport à la mémoire RAM. A chaque accès, il faut d'abord retrouver l'emplacement du bloc, le cylindre, le secteur, etc., puis commencer à lire bit par bit et, quand il s'agit de l'entièreté du fichier, bloc par bloc. C'est un processus long et pénible, présentant le désavantage de ralentir les applications qui, au cour de leur déroulement, doivent extraire de l'information ou en déposer sur le disque dur.

Quatre manières d'assurer la persistance des objets, il y a, aujourd'hui quatre manières d'assurer la persistance des objets, qui par sophistication croissante, sont la simple écriture et lecture des valeurs d'attributs à sauvegarder sur un fichier, la sérialisation des objets, l'interaction avec une base de données relationnelle, et l'utilisation de bases de données orienté objets. Nous pourrions également distinguer en fonction de leur respect de la nature « objet » des données à sauvegarder, leur facilité d'emploi, leur diffusion.  

Simple sauvegarde sur un fichier
Utilisation des « streams », C# permet l'utilisation d'un « stream » (traduit littérale : « flux »), et de manière assez semblable, d'écrire et de lire des octets sur un fichier. Un « stream » est un tube à octet, qui connecte le programme en train de s'exécuter et tout type de périphérique, disque et autres (réseau, imprimantes...). Cette abstraction permet de traiter tous les périphériques : comme un seul, en phase avec la manière dont le processeur se connecte aux différents périphériques : un bus unique, que la présence de « pilotes » permet ensuite de ramifier et de spécialiser en fonction du périphérique.

Comme à l'état primitif, le « stream » ne fait circuler que des octets, cette classe est souvent sujette à des spécifications par le jeu de l'héritage, ou à des emboîtements par lesquels on spécialise, par un second « stream », connecté à même le premier, un mode d'écriture ou de lecture sur le périphérique. On parle alors de « flux filtré ». Par exemple, les données à lire peuvent être des nombres, des caractères, des images, du son. Selon le cas, la manière d'associer les octets et de les transmettre à l'application est différente.

FileInputStream fin = new FileInputStream("fichier.dat");
DataInputStream din = new DataInputStream(fin);
double s = din.readDouble;

La méthode readDouble n'existe que dans la classe DataInputStream. Un autre type de filtrage très fréquent consiste à adjoindre, soit à la lecture, soit à l'écriture, un mécanisme de temporisation (en anglais « buffering »), qui permet, par exemple, d'écrire les octets dans une zone RAM locale, avant de transférer d'un seul coup tous ces octets sur le disque dur (pour minimiser le nombre d'accès disques, très lents).

L'héritage entre les « streams » est une caractériqtique commune aux langages avec, comme toujours, au grand dam des programmeurs, quelques nuances de syntaxe ou de pratique, ici et là, les différenciant. La spécialisation de la lecture ou de l'écriture se fait souvent en fonction du type de périphérique avec lequel on communique, ou en fonction du type très particulier d'information à échanger. On conçoit aisément la raison d'être de l'héritage. Dans tous les cas, on lit ou on écrit, mais il est nécessaire de redéfinir le mode de lecture ou le mode d'écriture en fonction du support ou des données à transmettre.

static void Main(string[] args)
        {
            Predateur[] lesLions = new Predateur[3];
            lesLions[0] = new Predateur();
            lesLions[1] = new Predateur();
            lesLions[2] = new Predateur();
/* obtient la connexion au fichier en mode lecture */ 
            FileStream fso = new FileStream("leFichierPredateur.txt", FileMode.Open);
/* flux filtré pour utiliser "ReadLine()" */ 
            StreamReader monEntree = new StreamReader(fso);
            for (int i = 0; i < lesLions.Length; i++)
            {
                lesLions[i].litLesDonnees(monEntree);
            }
            monEntree.Close(); /* fermeture indispensable  du tube */ 
/* obtient la connexion au fichier en mode écriture */
            FileStream fsr = new FileStream("leFichierPredateur.txt", FileMode.Create);
/* flux filtré pour utiliser "WriteLine()" */ 
            StreamWriter maSortie = new StreamWriter(fsr);
            for (int i = 0; i < lesLions.Length; i++)
            {
                lesLions[i].sauveDonnees(maSortie);
            }
            maSortie.Close(); /* fermeture indispensable  du tube */ 
            Console.ReadLine();
        }
La différence entre la lecture et l'écriture se se fait dans les arguments du constructeur du FileStream, et ne requiert pas de classes séparées. Ces arguments suffisent à spécifier le mode d'accès ou l'emploi d'un mécanisme de temporisation. Par exemple, le FileMode peut être OpenOrCreate, indiquant que le fichier doit être s'il existe, ou créé s'il n'existe pas. D'autres informations peuvent être spécifiées à la construction, comme FileAccess.Read quand seule la lecture est autorisée, ou FileAccess.ReadWrite, qui est l'option par défaut.

Sauvegarder les objets sans les dénaturer : la sérialisation, c'est un mode de sauvegarde qui permet de reproduire sur le disque dur une copie des objets et de leur structure d'interconnexion, c'est à dire de faire, à un instant donné, une copie miroir du tissu relationnel des objets qui se trouve  dans la RAM. La lecture d'un des objets du réseau suffirait, pareillement, à la reproduction dans la RAM du réseau dans son entièreté. Ce mécanisme en C# porte le nom de « sérialisation »

[Serializable]
    class Predateur {....}

La présence de [Serializable] devant la classe indique que la classe peut l'être.

 public void sauveDonnees(BinaryFormatter maSortie, Stream sw)
        {
            maSortie.Serialize(sw, this);
        }
        public void litLesDonnees(BinaryFormatter monEntree , Stream sr)
        {
            Predateur unPredateur;
            unPredateur = (Predateur)monEntree.Deserialize(sr);
        }
On retrouve un stream pour la lecture comme pour l'écriture, mais on recourt à l'utilisation de la classe BinaryFormatter pour le formatage des objets sauvés ou lus. La lecture et l'écriture se font au moyen des méthodes Serialize et Deserialize et, il est nécessaire de « caster » l'objet lu dans la classe d'accueil car la méthode Deserialize()  retourne n'importe quel type d'objet .
En C# et .Net, il  est également possible de sérialiser très facilement des objets dans le format XML, soit en substituant simplement le BinaryFormatter par un SoapFormatter, soit par l'utilisation de la classe XMLSerialize et des méthodes Serialize et Deserialize recevant des « stream » en argument.

Les bases de données relationnelles, les bases de données relationnelles sont, aujourd'hui, le moyen le plus répandu pour stocker l'information de manière permanente. Les raisons du quasi-monopole de ce mode de stockage sont nombreuses. Une première en est qu'à l'instar de l'approche OO, les données sont concentrées dans des structures de type « objet ». Les informations à stocker le sont en tant que valeurs d'attribut, regroupées dans un structure appelée  « table », très voisine de nos classes. Chaque instance de cette table, obtenue en fixant les valeurs d'attribut pour un cas donné, et qui donnerait naissance à un objet dans le cas de la classe, donne ici naissance à un enregistrement.
Une deuxième raison est l'existence d'un mode d'organisation de ces tables, mode dit relationnel, et dont la qualité première est d'éviter d'avoir à reproduire plusieurs fois la même information. On imagine très aisément tous les problèmes liés à la duplication des informations : mises à jour plus pénibles et erreurs d'encodage plus probables. Chaque table se doit de coder un concept donné, et il est nécessaire de séparer l'encodage des informations dans des tables distinctes, mais conservant entre elles des relations qui permettent, à partir de l'une d'entre elles, de retrouver les informations de l'autre.
Une troisième raison est la qualité importante de solutions techniques disponibles aujourd'hui, pour gérer de façon automatique des problèmes aussi critiques que les  « backups automatisés », les accès concurrentiels, les accès sécurisés et fiabilisés, les accès de type transactionnel (quand une étape défaillante dans une succession d'opérations rend caduques toutes les opérations effectuées jusque-là), le stockage réparti sur plusieurs ordinateur, etc.

SQL, une dernière raison est l'existence d'un langage d'interrogation de ces bases de données, langage standardisé nommé SQL(Structured Query Language). Toute application informatique qui doit s'interfacer avec une base de données relationnelle le fera en  « parlant » ce langage. C'est ce standard, additionné à toutes les solutions techniques proposées par des environnements logiciels, bien installés aujourd'hui, comme Oracle, MS SQL SERVER, Access, additionné, enfin à toutes les couches logicielles de visualisation et d'analyse de données s'exécutant à partir de ces bases, qui rendent les bases de données relationnelles incontournables lors de l'étude des solutions de persistance des objets.
Sql est un  langage d'interaction avec les bases de données qui sont utilisables, généralement, dans des programmes informatiques qui interagissent avec ces bases. Ces instructions permettent la création des tables  et des relations, la consultation, l'insertion, et la modification des enregistrements, la définition des permission au niveau des utilisateurs.

Select pour lire les enregistrements : 
SELECT [ALL] | [DISTINCT] 
<liste des noms de colonnes> | *
FROM <liste des tables>
[WHERE <conditions logique>]

Insert pour insérer des nouveaux enregistrements :  
INSERT INTO Nom_de_la_table
(colonne1,colonne2,colonne3,...)
VALUES (valeur1,valeur2,valeur3,...)

Update pour mettre à jour ces enregistrements : 
UPDATE Nom_de_la_table
SET Colonne = Valeur
[WHERE qualification]

Delete pour effacer ces enregistrements : 
DELETE FROM Nom_de_la_table
WHERE qualification

Clé primaire, la clé primaire d'une table est l'attribut qui permet d'accéder, de manière unique, et sans aucune équivoque à un des enregistrement de la table. La valeur de cet attribut doit être distincte pour chaque enregistrement, une valeur entière étant ce qu'il y a de plus courant.

Référent versus clè, les bases de données relationnelles fonctionnent et s'utilisent indépendamment de tout mode de stockage, là où les objets ne s'utilisent et ne fonctionnent que sur un mode stocké. Un enregistrement est indicé par un nombre unique plutôt qu'un emplacement unique. C'est la valeur d'une variable fondamentale (la clè) et non pas un système d'adressage qui permet l'accès à l'objet et les connexions entre les objets. C'est la toute la différence.

Relations entre tables et association entre classes, les tables peuvent être tout autan en relation que les classes le sont, le passage de l'OO vers le relationnel se trouve facilité par cette ressemblance structurelle. Quelques requêtes suffisent à mener à bien le va-et-viens des objets entre le programme et la base de données, quelles que soient les liaisons qui existes entre objets.

Relation 1-n, supposons une nouvelle classe, Tanière, habitat de notre prédateur et admettons, de surcroît, que chacun des prédateurs peut se loger dans plusieurs de ses tanières.
Dans l'approche OO, le prédateur interagit avec ses tanières en possédant un vecteur de référents vers celles-ci, la valeur de chaque référent étant l'adresse d'un objet tanière, avec lequel le prédateur peut interagir. Chacun des prédateurs a donc une connaissance directe de ses tanières. Il possède l'adresse de chacune d'entre elles. Dans l'approche base de données relationnelle, il y a lieu, tout comme en OO, de tenir séparée ces deux tables, puisqu'elles concernent, en effet deux réalités très distinctes.

Cependant, il n'est plus ici question d'un adressage physique explicite, mais simplement d'une manière, pour chaque enregistrement de la première table, de découvrir avec quels enregistrements de la seconde table, il entre en relation. Cela se fait, tout simplement, en rajoutant à la seconde table un clé dite « étrangère » dont les valeurs doivent être partagées avec celles prises par la clé primaire de la première table.
La relation s'établit alors comme indiqué dans le schéma ci-dessus, entre la clé primaire de la première table et la clé étrangère de la seconde table. La tanière saura de quel prédateur elle est la tanière, par l'entremise de cette clé étrangère, dont la valeur correspondra à une des valeurs de la clé primaire des prédateurs. Il existe une façon de forcer le partage des valeurs entre les deux clés, en imposant l'intégrité référentielle dans la relation entre ces deux tables. Les tanières ne peuvent être alors les tanières que des seuls prédateurs existants.

Pour ce qui est des classes, nous nous trouvons en pleine logique de programmation : l'ordinateur exécute des instructions, et deux classes doivent se parler, dès lors que la première déclenche sur l'autre une séquence d'instruction. Pour ce faire, elle n'a d'autre moyen, plus direct, que de connaître l'adresse des données que ces instructions doivent manipuler.

Pour ce qui est des tables, nous sommes en pleine logique de stockage de données, l'important étant la facilité d'utilisation, d'écriture et de lecture de ces données. Les relations sont parfaitement justifiées lors de l'écriture ou de la mise à jour des données, car elles permettent d'éviter des redondances malvenues, souvent sources d'erreur lors de l'écriture ou de cette mise à jour. Ces relations existent, alors, sans aucun besoin d'emplacement physique. Elle existent à titre « conceptuel »,peu importe le lieu de stockage des tables.

En conséquence de quoi, on se rend compte que, dès qu'un diagramme de classe intègre une relation de type 1-n entre deux classes, il est indispensable de penser une traduction dans le mode relationnel. Cette traduction, idéalement, passe par l'addition, en plus des attributs issus des deux classes, d'une clé primaire dans une des tables et d'une clé étrangère dans l'autre, ainsi que par une assignation de valeur pour clés, qui traduisent les relations existant entre les objets.

Les instructions SQL peuvent se complexifier quelque peu, car elles devrons prendre en considération l'existence des relations. Par exemple, si l'on désire accéder à la dimension de la seconde tanière du premier prédateur, la requête SQL le permettant sera la suivante (INNER JOIN indiquant la jointure entre les deux tables) : 
SELECT Taniere.dimension FROM Predateur INNER JOIN Taniere ON
Predateur.idPredateur = Taniere.IdPredateur WHERE Predateur.idPredateur = 1 and idTaniere = 2µ

Alors que son équivalent OO se limiterait à quelque chose comme : Predateur[0].getMesTanieres()[1].getDiemension(). 

Relation n-n
Dans le diagramme de classe, l'association qui existe entre le prédateur et, disons, les points d'eau est de type n-n. En effet, plusieurs prédateurs peuvent s'abreuver à un même point d'eau et un prédateur quelconque peut s'abreuver à plusieurs point d'eau.
Il n'est pas possible de reproduire cela, de la manière la plus simple, par une relation équivalente, n-n, entre les deux tables.C'est parfaitement prohibé étant donné le mécanisme de jointure qui sous-tend les relation 1-n, et de cela contribuerait à dégrader considérablement les relations. La seule table du côté du n comprend une clé à associer à la clé primaire de la table du côté du 1. Une seule clé étrangère est nécessaire, car chaque enregistrement de cette table n'est associé qu'à un seul enregistrement de l'autre table, quitte à ce que cela soit plusieurs fois le même (pour que l'on ait bien  « 1 » d'un coté et « n » de l'autre.

Or si nous voulions une relations de type n-n, il faudrait permettre plusieurs clés étrangères dans la table du côté du n, pour que chaque « eau » puisse référer plusieurs prédateurs. C'est parfaitement impensable, car nous ne saurions combien en mettre et comment se ferait la jointure entre la clé primaire et ces multiples clés étrangères.

En conclusion, les relations n-n ne sont pas admises dans les bases de données relationnelles, alors qu'il peut en exister, sans le moindre problème, en OO.On conçoit bien qu'un premier objet puisse pointer vers de multiples objets, et que ces derniers puissent faire de même. Cette relation n-n nous permet de comprendre davantage encore les deux mécanismes très différents mis en oeuvre pour relier les tables et les classes entre elles : lien conceptuel d'un coté, lien physique de l'autre. Le seul moyen de s'en sortir dans l'univers relationnel est de procéder, comme montré dans la figure ci-après, à l'ajout d'une table intermédiaire, une table qui associe les points d'eau aux prédateurs.

Cette table additionnelle, sans équivalent dans la version OO, complexifie encore la traduction du monde objet vers le relationnel, inévitable pour pouvoir sauvegarder les objets et leur relations dans une base de données. Il faudra via les requêtes SQL, créer, accéder et modifier cette table d'association, alors qu'elle ne paraît être la contrepartie sémantique d'aucune des classes en présence.

Linq, La bibliothèque Linq (Langage-INtegrated Query), sous les feux de la rampe informatique à l'heure actuelle, est sans conteste l'innovation majeure de la nouvelle version de la plate-forme de développement de microsoft .Net.3. On peut aller jusqu'à penser que l'essentiel des autres innovations présentes dans cette troisième version, telles les expressions lambda, les  « méthodes d'extension », les variables anonymes typées implicitement  « var » et la manipulation des  « query », sont autant d'étapes majeures pour enfin aboutir à la réalisation de Linq. Si la bibliothèque Linq est à ce point discutée dans le monde informatique, c'est parce qu'elle est présentée par Microsoft comme la solution à cet épineux problème encore irrésolu de la mise en correspondance entre le monde du stockage des données (tel le relationnel) et de l'orienté objet, auquel vient s'ajouter depuis quelques années,avec l'invasion irrépressible des application web, la nécessaire prise en charge de l'information stockée et représentée sous forme XML. Linq veut aboutir une fois pour toute à la réalisation et la concrétisation parfaite de l'équation DATA=OBJECT, c'est-à-dire présenter une syntaxe unifiée faisant abstraction de la manière définitive dont sont stockées les données en mémoire.

Une même information, et trois manières de la présenter : l'objet, le relationnel et XML. Il est en effet grand temps de parvenir à homogénéiser un mode d'accès et de traitement de cette informations qui soit indépendant de la manière de la représenter. Linq se présente comme un candidat possible à cette homogénéisation : une même requête, dans un langage voisin du nôtre, agissant dans ces trois univers d'information.

De surcroît, Linq s'intègre parfaitement aux langages de programmation .Net tels C#, il en est une évolution. Toute requête Linq se trouve compilée au même titre que n'importe quelle autre instruction. Fini donc les requêtes SQL qui ne posent problème qu'à l'exécution, c'est à dire quand, en effet, elles interagissent directement avec la base de données relationnelle. Linq permet de prévenir par la compilation des erreurs d'interrogation de bases de données (par exemple des attributs mal orthographiés) qui, auparavant, ne se seraient produites que pendant la phase d'exécution. De plus, l'homogénéisation permettra à une même  expression d'interroger indifféremment des système d'information objet, XML et relationnels et d'en mélanger les résultats. Le programmeur est enfin libre de ses mouvements OO. Il n'a plus besoin de maîtriser autant de langages qu'il y a de manière de stocker l'information : SQL, XML... 

Exemple de Linq agissant sur une collection d'objets


static void Main(string[] args)
        {
            string[] names = {"Burke","Tom","Frank","Pierre","Kat","Bob","Karl","Bob" };
            var test = from s in names
                       where s.Length == 5
                       orderby s
                       select s.ToUpper ();

            foreach (var item in test) { Console.WriteLine(item); }
            Console.ReadLine();
        }
Cette requête select n'ira pas sans vous rappeler sa version SQL, mais dans une version qui serait inversée par rapport à l'original. L'inversion de la requête est justifiée par l'utilisation des aides à la programmation. C'est parce qu'il découvre que « s » est de type string, que l'environnement de développement pourra vous faciliter la programmation des opérateurs qui suivent. Le typage anonyme y est réalisé par la présence du mot-clé var. Cette inférence sur le typage permet au compilateur de déduire le type de variables sans que le développeur ait besoin de le lui signaler explicitement. Comme cette étape se passe à la compilation, les risques d'erreur restent limités tout en autorisant une syntaxe plus libre. La clause Where agit comme une condition de filtrage du résultat. Ces clauses (par exemple plusieurs Where à la suite l'un de l'autre) peuvent se concaténer, car Linq permet en effet d'enchaîner l'opérateur comme : 

"List.Where(clause where).Select(clause select).OrderBy(critère de tri)".

L'opérateur Select effectue une projection de la liste de départ sur une nouvelle liste obtenue à l'issue de son action. C'est à lui que l'on doit la création d'un nouveau typage en ligne comme résultat de cette projection.
Les opérateurs de tri classique que son OrderBy et ThenBy permettent de trier les éléments d'un résultat pour peu que ceux-ci implémente l'interface IComparable<T>, comme dans le code qui suit : 

class Program
    {
        static void Main(string[] args)
        {
            List <Etudiant> lesEtudiants = new List<Etudiant>();
            lesEtudiants.Add(new Etudiant("Kangulungu","Pite", 33));
            lesEtudiants.Add(new Etudiant("Hubert","Karl", 21));
            lesEtudiants.Add(new Etudiant("Kurt","Nora", 20));
            lesEtudiants.Add(new Etudiant("Marcca","Jane", 43));
            lesEtudiants.Add(new Etudiant("Dupond", "Dupond", 76));

            var test = from s in lesEtudiants
                      where s.Age > 25
                      orderby s.Nom
                      select new { s.Nom, s.Prenom };
            foreach (var item in test) Console.WriteLine(item.Prenom);
            Console.ReadLine();
        }
    }
    class Etudiant {

        public Etudiant(string Nom, string Prenom, int Age)
        {
            this.Nom = Nom;
            this.Prenom = Prenom;
            this.Age = Age;
        }
        public int Age
        {
            get;
            set;
        }
        public string Nom
        {
            get;
            set;
        }
        public string Prenom
        {
            get;
            set;
        }
    }
Deuxième exemple de Linq agissant sur une base de données relationnelle

 [Table]
    public class Predateur
    {
        [Column(IsPrimaryKey = true)] public int idPredateur;
        [Column] public int vitx;
        [Column] public int vity;
        [Column] public int energie;
    }
    class SomeDataContext : DataContext
    {
        public SomeDataContext(IDbConnection connection) : base(connection) { }
    }
    class Program
    {
        static void Main(string[] args)
        {

            DbProviderFactory dbpf = DbProviderFactories.GetFactory("System.Data.SqlClient");
            DbConnection connexion = dbpf.CreateConnection();
            connexion.ConnectionString = "Data Source=(local);";
            connexion.ConnectionString += "Initial Catalog=ecosysteme;";
            connexion.ConnectionString += "Integrated Security=true;";

            SomeDataContext dataContext = new SomeDataContext(connexion);
            Table<Predateur> lesPredateur = dataContext.GetTable<Predateur>();

            IQueryable<int> query = from p in lesPredateur
                                    where p.energie > 10
                                    orderby p.idPredateur descending
                                    select p.idPredateur;

            foreach (int id in query) Console.WriteLine(id);

            Console.ReadLine();
        }
    }
On découvre qu'un même opérateur de projection select, toujours très inspiré de SQL mais inversé, composé d'un même filtre de type where et/ou d'un mécanisme de tri orderby, peut s'appliquer indifféremment sur un tableau d'objets entreposés dans la mémoire RAM et sur une base de données relationnelle. Une troisième illustration aurait pu être possible, en appliquant, une fois encore, ce même type de requête à même l'information stockée en XML, comme dans le schéma ci-dessous
Si , par mégarde, il y a erreur dans la requête select - attribut mal orthographié par exemple -, le code produira une erreur à la compilation. Cela est évidemment possible grâce à la déclaration de la table|classe Predateur et des attributs|Column, qui font le lien avec la base de données relationnelle. La traduction en une requête SQL se fait à la volée, après que le compilateur ait vérifié l'exactitude de la syntaxe. Elle s'écrirait en requêtes XQuery en présence d'un fichier XML. Un objet de classe DataContext représente le lien entre la base de données et les classes entités qui lui sont assignées. Il maintient la correspondance entre les objets du code et les enregistrements de la base de données. Il est responsable de la génération du code SQL qui sera soumis à la base de données.
L'addition ou le retrait d'enregistrements dans la base de données peut s'effectuer à partir du DataContext de la manière suivante :
dataContext.Predateur.InsertOnSubmit(p) ; // p serait un nouvel objet prédateur
dataContext.SubmitChanges() ; // exécution de l'insertion


dataContext.Predateur.DeleteOnSubmit(p) ; // p serait l'objet à supprimer
dataContext.SubmitChanges() ; // exécution de la suppression