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

- ProcDump 6.0 : support du filtrage sur messages d'exceptions .NET, des filtres multiples et du ciblage par nom de service par CoqBlog le 05-20-2013, 14:50

- Votez pour le TOP 10 des influenceurs SharePoint francophones ! par Le blog de Patrick [MVP SharePoint] le 05-20-2013, 12:59

- [Conf’SharePoint] Dernier rappel ! :-) par Le blog de Patrick [MVP SharePoint] le 05-20-2013, 09:09

- [ #SharePoint 2013 ] les modèles de sites standards… par Le blog de Patrick [MVP SharePoint] le 05-20-2013, 09:03

- 10 erreurs de compréhension concernant SharePoint… par Le blog de Patrick [MVP SharePoint] le 05-20-2013, 08:27

- Conf’SharePoint : 10 bonnes raisons pour ne pas la rater par Le petit blog de Pierre / Pierre's little blog le 05-14-2013, 02:24

- [Event] Soirée de lancement Agile .NET France à Lyon par Blog Agile/ALM de Vincent THAVONEKHAM le 05-13-2013, 01:29

- .NET / Debug : inspection de la mémoire d'applications .NET (dump ou processus live) : première livraison d'une librairie .NET par Microsoft par CoqBlog le 05-11-2013, 22:21

- SharePoint : Incompatibilité avec Internet Explorer 10 (IE10) par Blog Technique de Romelard Fabrice le 05-08-2013, 16:29

- AutoSPInstaller pour SharePoint 2013 maintenant disponible en “RTM” par Julien Chable le 05-06-2013, 23:30