Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Fathi Bellahcene

.Net m'a tuer!
[C#] Design Pattern: Chaine de responsabilité

 

Cette semaine, on a eu avec un collègue à migrer du code…enfin, lui écrivait du code et moi je faisait des commentaires sur le code Sourire.

A un moment donnée, on (il) a été confronté a un problème classique de la POO: On avait un fichier de log alimenté par un traitement (Nant) et on devait en temps réel lire cette log (depuis un autre process C#); interpréter son contenu et remonter les informations. En gros: début de l’étape X, Fin de l’étape X, Etape X réussi, Etape X en Erreur, Etape de Chargement de donnée...

Pour résoudre cette problématique, on (il) a commencé a écrire le traitement qui lit le fichier et remonte ligne par ligne le fichier de log, puis fait passer cette ligne dans un ensemble de traitements chargé d’analyser si elle correspond à un ou plusieurs état à remonter (une voir plusieurs expressions régulières par type d’état ): une ligne peut signifier début de traitement + Erreur + Traitement de chargement des données,….

Et là, on s’est rendu compte que le code devenait trop complexe, on a donc chercher à le redesigner et le pattern qui nous est venu de suite en tête c’est le pattern Chaine de responsabilité (CoR).

Si on regarde la définition Wikipédia:

En génie logiciel, le patron de conception Chaîne de responsabilité permet à un nombre quelconque de classes d'essayer de répondre à une requête sans connaître les possibilités des autres classes sur cette requête. Cela permet de diminuer le couplage entre objets. Le seul lien commun entre ces objets étant cette requête qui passe d'un objet à l'autre jusqu'à ce que l'un des objets puisse répondre. Ce pattern permet aussi de séparer les différentes étapes d'un traitement.

image

 

On se rend compte que l’on est exactement dans la problématique visé par ce pattern:

le patron de conception Chaîne de responsabilité permet à un nombre quelconque de classes [Les classes qui vont tenter de voir si la ligne de log correspond a un état précis] d'essayer de répondre à une requête [Le traitement de ma ligne de log] sans connaître les possibilités [Mes classes ne sont en aucun cas liée et ne savent absolument pas comment les autres fonctionnent] des autres classes sur cette requête.

Dans notre cas, l’avantage de ce pattern par rapport à d’autres comme le Strategy ou Pipeline, c’est qu’on a la possibilité d’injecter autant de Handler qu’on le souhaite et que le traitement de la requette par ces differents Handlers est complètement découplé (ce qui n’est pas forcément le cas dans un pipeline).

On doit donc finir par avoir du code qui doit ressembler à ca:

le Handler (LogHandler) ainsi qu’un enum (flags) pour connaitre les types associés à la ligne de log :

 

   1: public abstract class LogHandler
   2: {
   3:  
   4:     protected string _regExp;
   5:     protected LogHandler _successor;
   6:  
   7:     public abstract TaskStatus HandleRequest(string log);
   8:  
   9:     public void SetSuccessor(LogHandler logHandler)
  10:     {
  11:         _successor = logHandler;
  12:     }
  13: }
  14:  
  15: [Flags]
  16: public enum TaskStatus
  17: {
  18:     None,
  19:     Started,
  20:     Finished,
  21:     Error,    
  22: }

Dans cette classe on a une regExp stockée dans une string. Un objet successor de type LogHandler et une méthode SetHandler (on pourrait très bien avoir une propriété avec juste un set).

les Handlers concrets qui override chacun la méthode HandleRequest en faisant bien sûr appel au successor (qui dans cet exemple est finalement un precedor), dans cet exemple ils font plus ou moins la même chose :

   1: public class TErrorHandler : LogHandler
   2:  {
   3:      public TErrorHandler()
   4:      {
   5:          _regExp = "Ma RegExp Error";
   6:      }
   7:  
   8:      public override TaskStatus HandleRequest(string log)
   9:      {
  10:          var status = TaskStatus.None;
  11:  
  12:          if(_successor!=null)
  13:              status = _successor.HandleRequest(log);
  14:  
  15:          Regex myRegex = new Regex(_regExp);
  16:          if(myRegex.IsMatch(log))
  17:          {
  18:              status |= TaskStatus.Error;
  19:          }
  20:          return status;
  21:      }
  22:  }
  23:  
  24:  public class TFinishedHandler : LogHandler
  25:  {
  26:      public TFinishedHandler()
  27:      {
  28:          _regExp = "Ma RegExp Finished";
  29:      }
  30:  
  31:      
  32:      public override TaskStatus HandleRequest(string log)
  33:      {
  34:          var status = TaskStatus.None;
  35:  
  36:          if(_successor!=null)
  37:              status = _successor.HandleRequest(log);
  38:  
  39:          Regex myRegex = new Regex(_regExp);
  40:          if(myRegex.IsMatch(log) && log.StartsWith("S") )
  41:          {
  42:              status |= TaskStatus.Finished;
  43:          }
  44:          return status;
  45:      }
  46:  }
  47:  
  48:  public class TStartedHandler : LogHandler
  49:  {
  50:      public TStartedHandler()
  51:      {
  52:          _regExp = "Ma RegExp Started";
  53:      }
  54:  
  55:      public override TaskStatus HandleRequest(string log)
  56:      {
  57:          var status = TaskStatus.None;
  58:  
  59:          if (_successor != null)
  60:              status = _successor.HandleRequest(log);
  61:  
  62:          Regex myRegex = new Regex(_regExp);
  63:          if (myRegex.IsMatch(log) && log.StartsWith("S"))
  64:          {
  65:              status |= TaskStatus.Started;
  66:          }
  67:          return status;
  68:      }
  69:  }

et la classe Client :

   1: public class Client
   2:   {
   3:  
   4:       private LogHandler _chainOfResponsibility;
   5:       public void LoadHandlers()
   6:       {
   7:           var startedHandler = new TStartedHandler();
   8:           var errorHandler = new TErrorHandler();
   9:           var finishedHandler = new TFinishedHandler();
  10:  
  11:           startedHandler.SetSuccessor(errorHandler);
  12:           errorHandler.SetSuccessor(finishedHandler);
  13:  
  14:           _chainOfResponsibility = startedHandler;
  15:  
  16:       }
  17:  
  18:       public void Request(string log)
  19:       {
  20:           var status = _chainOfResponsibility.HandleRequest(log);
  21:           if (status != TaskStatus.None)
  22:           {
  23:               SendMessageToSystem(status);
  24:           }
  25:       }
  26:  
  27:  
  28:       private void SendMessageToSystem(TaskStatus status)
  29:       {
  30:           throw new NotImplementedException();
  31:       }
  32:  
  33:   }

On a :

  • une méthode loadHandlers chargée de construire notre chaine de traitement. On construit et associe nos différents handlers avec comme point d’entrée l’objet _chainOfResponsibility.
  • une méthode Request: qui lance notre chaine  de traitements, récupère les status et envoi un message si on doit le faire.

Alors, bien sûr, le traitement présenté ici est simplifiée le but étant bien évidement de présenter le pattern CoR.

On obtient du code bien découplé, simple a lire (chaque handler contient du code spécifique à ce qu’il tente d’intercepter) facile à modifier…bref un code SOLID!

 

L’exemple est dispo ici

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :
Posted: samedi 7 juillet 2012 22:11 par fathi

Commentaires

JeremyJeanson a dit :

Et sinon le framework .ne dispose de Workflow Foundation qui intègre tout ça de base ;)

(sans avoir besoin de coder quoi que ce soit ne plus)

# juillet 9, 2012 11:19

fathi a dit :

hello,

d'accord avec toi si on avait a recoder la partie qui génère la log...sauf que cette partie n'est pas à migrer dans notre cas. On doit juste recoder la partie "parsing" du log.

# juillet 9, 2012 20:57
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Intégration Yammer et SharePoint Online (Office 365), étape 1 … par Le blog de Patrick [MVP SharePoint] le 06-12-2013, 17:37

- [Dynamics CRM] Ajouter les dossiers de CRM au dossier Favoris d’Outlook par Christine Dubois le 06-10-2013, 15:50

- Visual Studio 2013 par Etienne Margraff le 06-04-2013, 10:26

- Configurer la collation SQL Server pour SharePoint par Blog de Jérémy Jeanson le 06-03-2013, 19:48

- Etendre le Team Web Access de TFS 2012 – Step 1: Création du plugin par Philippe Didiergeorges Aka Philess le 06-03-2013, 07:30

- Livre Blanc : Développer des applications NUI par Fathi Bellahcene le 06-01-2013, 11:35

- [Dynamics CRM 2011] Copier une vue d'entité par Christine Dubois le 05-29-2013, 13:20

- [Conf’SharePoint 2013] Mes présentations… par Le blog de Patrick [MVP SharePoint] le 05-28-2013, 09:04

- [wpdev] Storage bug in MediaLibrary.SavePicture par Kévin Gosse le 05-26-2013, 19:08

- VMMap en mode instrumentation sur système 64bit : attention à la plateforme cible du build .NET par CoqBlog le 05-25-2013, 22:25