La Chaîne de responsabilité est un design pattern assez courant en programmation Orientée Objet.

Il découple la source de la requête du receveur en autorisant un ou plusieurs objets à prendre en compte la requête. Un receveur peut passer la requête au receveur suivant jusqu’à ce qu’un objet dans la chaîne soit capable de la traiter.

La chaîne de responsabilité consiste en 3 composant : l’Interface du receveur, au moins un receveur concret et l’application cliente. Afin de mieux présenter les choses, prenons un exemple :

  • Un auteur écrit un billet de blog et le soumet à un éditeur,
  • Si l’article fait moins de 1000 caractères, l’éditeur s’occupe du document,
  • Si l’article fait moins de 600 caractères, l’éditeur le refuse,
  • Si l’article fait plus de 1000 caractères, l’éditeur passe l’article au responsable de l’édition.

Dans ce cas nous avons une requête correspondant à l’article et un éditeur sera le premier à prendre en compte la requête.

Avant de faire la chaîne commençons par créer la classe du résultat du traitement de la requête. Deux informations sont nécessaires : le résultat et qui a traité la demande.

public class ReviewResult
{
    public bool Approved { get; set; }
    public string Reviewer { get; set; }
}

Puis la classe article correspondant à la requête.

public class Article
{
    public string Content { get; set; }
    public long Id { get; set; }
}

Interface receveur

Cette interface définit le contrat de traitement de la requête. Dans notre exemple cela correspond à un éditeur.

public interface IEditor
{
    ReviewResult ReviewArticle(Article article);
}

Cette interface indique que nous attendons un article à traiter et que nous rendrons le résultat dans un objet ReviewResult.

Classe concrète du receveur

La classe Editor va implémenter IEditor. Nous allons de plus lui passer dans le constructeur le successeur pour le traitement qui implémentera lui aussi IEditor :

public class Editor : IEditor
{
    public IEditor Successor { get; private set; }

    public Editor(IEditor sucessor)
    {
        this.Successor = sucessor;
    }

    public ReviewResult ReviewArticle(Article article)
    {
        ReviewResult result = new ReviewResult
        {
            Reviewer = "Editor"
        };

        if (!string.IsNullOrWhiteSpace(article.Content))
        {
            if (article.Content.Length > 1000)
            {
                return Successor.ReviewArticle(article);
            }

            if (article.Content.Length > 600)
            {
                result.Approved = true;
            }
        }

        return result;
    }
}

Editor vérifie le nombre de caractère de l’article. S’il fait plus de 1000 il passe l’article à son successeur. S’il fait 600 ou plus, il approuve l’article. Sinon l’article est rejeté.

Il est maintenant temps d’implémenter le responsable de l’édition. Celui termine la chaîne. Il est tout à fait possible d’avoir plusieurs éléments pour la chaîne mais pour l’exemple nous nous limiterons à 2.

public class ExecutiveEditor : IEditor
{
    public ReviewResult ReviewArticle(Article article)
    {
        ReviewResult result = new ReviewResult
        {
            Reviewer = "Executive Editor" 
        };

        result.Approved = 
            !string.IsNullOrWhiteSpace(article.Content) &&
            article.Content.Length > 1000;

        return result;
    }
}

Application Cliente

Il est temps de tout mettre ensemble dans l’application cliente.

Pour commencer créons une liste de 3 articles :

  • un de 500 caractères,
  • un de 850 caractères,
  • un de 1500 caractères.
List<Article> articles = new List<Article>
{
    new Article() { Id = 1, Content = new string('*', 500)},
    new Article() { Id = 2, Content = new string('*', 850)},
    new Article() { Id = 3, Content = new string('*', 1500)
};

Puis nous créons l’éditeur qui est le premier receveur de la chaîne. Nous passons comme successeur une nouvelle instance de responsable d’édition.

IEditor editor = new Editor(new ExecutiveEditor());

La dernière étape est d’itérer sur tous les documents et de les passer à l’éditeur puis d’afficher chaque résultat :

articles.ForEach(x => 
{
    var result = editor.ReviewDocumment(x);
    Console.WriteLine(result.Approved ?
        "Document {0} approuvé par {1}" :
        "Document {0} rejeté par {1}",
        x.Id,
        result.Reviewer);
}

Comme vous pouvez le voir ce design pattern est très pratique pour définir le flux de traitement. Ici c’est une version simplifié mais il est possible d’aller encore plus loin en créant un RequestManager qui s’occupera de définir les successeurs de la chaîne.