Template Method : exploitez l’héritage à son plein potentiel.


Patron de méthode (Template Method) : patron de conception consistant à structurer un algorithme en une série de petites étapes, afin de pouvoir les modifier indépendamment du reste.


Problème

Félicitations ! Après des mois de travail acharnés, vous avez enfin réussi. Je n’ai même pas les mots pour exprimer à quel point je suis fier de vous. Vous avez reprogrammé le Monopoly avec succès.

Merci, c’était mon rêve depuis toujours. Le seul truc qu’il me reste à faire, c’est d’implémenter quelques variantes du jeu. Ça devrait être facile vu qu’elles se ressemblent toute.

Bonne idée, mais attention. Le problème des variantes, c’est qu’elles sont similaires, mais pas identiques. Un manque de vigilance et c’est la porte ouverte à toutes les duplications de code les plus odieuses.

Tu sais que t’es un vrai fan quand t’as même le tablier Monopoly. Image de u/eoin27.
Pas de soucis, j’ai juste à rajouter quelques paramètres pour rendre le code plus générique.

Bof. Les paramètres sont pertinents pour changer les valeurs clefs d’un programme, comme le nombre de joueurs ou leur argent de base.

Mais ici, j’ai peur que ce ne soit pas suffisant, car la plupart des variantes du Monopoly vont surtout changer les règles du jeu, comme les actions des joueurs ou les conditions de défaites.

Ça va être compliqué de contrôler ça avec de simples paramètres.

C’est un problème architectural très classique, et malheureusement, la plupart des développeurs ne se cassent pas la tête et choisissent soit de :

  • Créer un nouveau jeu de règles à part, ce qui donne lieu à énormément de duplication.
  • Passer des flags en paramètres pour choisir dynamiquement sa variante, ce qui viole le principe ouvert fermé (OCP), car ça vous force à modifier tout l’algorithme le jour où vous voulez introduire une nouvelle variante.
Et c’est quoi la meilleure issue du coup ?

Aucune. Dans les deux cas, votre code sera absolument dégueulasse, impossible à maintenir, et génèrera une dette technique équivalente à l’achat d’une maison sur les collines de Monaco.


Solution

Le cœur du problème vient du fait que vous avez créé les règles du Monopoly comme un tout, alors qu’il s’agit plutôt de petites règles assemblées de manière cohérente.

La plupart des variantes ne vont pas chambouler la manière de jouer, mais simplement modifier certaines de ses petites règles pour donner au jeu une nouvelle face.

Le meilleur moyen de s’en sortir serait donc d’avoir un module contenant l’ensemble des règles, et autour, des petits modules (un par variante) qui vont les modifier une par une.

C’est justement le principe des patrons de méthode.

Il s’agit d’une forme d’inversion des contrôles qui consiste à avoir une classe pour assembler les différentes étapes d’un programme (Monopoly), et des sous-classes pour les implémenter (variantes).

De toutes les choses que l’on peut faire avec l’héritage, c’est surement une des plus puissantes, car elle permet :

  • D’encapsuler votre code : la responsabilité des sous-types se limite aux comportements individuels des étapes, sans toucher à la structure.
  • D’uniformiser vos modules : tous les sous-types fonctionnent de la même manière, ce qui les rend plus cohérents et compréhensibles.
  • De limiter la duplication de code : chaque sous-type se contente de changer ce qu’il a à changer, sans réimplémenter du code existant.
  • De séparer les responsabilités (SRP) : tous les sous-types sont indépendants les uns des autres.
  • De respecter l’OCP : la structure est fermée à la modification et tous les changements se passent à l’extérieur de celle-ci, dans les sous-types. Ça permet notamment d’inverser le flux de contrôle du programme au même titre que l’injection de dépendance.

Prêt à coder le Monopoly de vos rêves ? Alors en route !


Explications

Patrons de méthode

Tout démarre avec un patron de base, qui n’est qu’une simple classe caractérisée par deux éléments :

  • Une méthode squelette.
  • Une méthode par étape de la structure.

La méthode squelette contient l’algorithme de A à Z. Elle doit être :

  • Publique, car c’est elle qui sera appelée dans le client (le code qui utilise le module).
  • Bloquée pour empêcher les sous-types de la surcharger, sinon, ça casse tout le principe.

Bien sûr, l’idée n’est pas de mettre un gros pâté de code à l’intérieur, mais de structurer l’algorithme de sorte qu’il ne soit qu’une série d’étapes à appeler.

Éventuellement, il peut y avoir des conditions ou des boucles, mais généralement, hormis des appels de fonctions, on ne retrouve pas grand-chose.

Comme les étapes risquent de changer dans une variante du programme, elles doivent être :

  • Protégées pour préserver l’encapsulation.
  • Surchargeables pour pouvoir être implémentées dans les sous-types.

Ces sous-types sont les patrons concrets : des classes servant à créer une version alternative de l’algorithme en surchargent certaines étapes à leur sauce. C’est leur seul et unique but.

Les patrons concrets n’ont toujours qu’une seule méthode publique : leur constructeur.

Maintenant, dans le client, il suffit de :

  1. Créer un objet du type du patron de base (MonopolyTemplate).
  2. L’instancier grâce au constructeur d’un patron concret (NoDeptMonopolyTemplate ou CheaterEditionMonopolyTemplate).
  3. Appeler la méthode squelette.

Et en fonction du patron concret choisi, les résultats ne seront plus les mêmes.

Génial ! Mais je suis un peu sceptique. Ce n’est pas dangereux de tripatouiller une méthode de l’extérieur comme ça ?

Si, car il y a toujours un risque de briser le principe de substitution de Liskov (LSP), la règle d’or du polymorphisme.

Par exemple, si votre structure refuse toutes les valeurs null en sortie d’étape, mais que vous le faites quand même, vous risquez d’avoir de gros soucis.

Donc, restez vigilant, testez correctement votre code, et ça devrait passer crème. Je vous fais confiance 👌


Comportements par défaut

Plus haut, j’ai dit que les étapes devaient être surchargeables. Il y a deux manières de faire ça :

  • Si elle n’a pas de comportement par défaut, elle est abstraite.
  • Sinon, elle est virtuelle.

Dans le cas du Monopoly, toutes les étapes ont un comportement par défaut (les règles de base du jeu), mais dans certains programmes, le patron de base contient des méthodes abstraites, ce qui le rend inutilisable sans patron concret.

Ici, InitializeGame() est abstraite, donc tous les patrons concrets sont obligés de l’implémenter.

Ce que je vous recommande, c’est de toujours faire en sorte d’avoir le moins d’étapes à implémenter possible.

Plus vous en avez, plus la maintenance va être compliquée, car les sous-types vont prendre des proportions surréalistes.


Application

On commence par créer notre patron de base qui contient une méthode de structure et 2 étapes.

public class MonopolyTemplate
{
    public MonopolyTemplate() { }

    public void Play()
    {
        PlayerAction();
        LoosingCondition();
    }

    protected virtual void PlayerAction() =>
        Console.Write("Throw dices, move, buy and pay. ");

    protected virtual void LoosingCondition() =>
        Console.Write("Go bankrupt. ");
}
Bien sûr, je ne me suis pas embêté à recréer le Monopoly ^^, on se contentera de quelques messages en console.

Ensuite, on crée deux patrons concrets qui vont implémenter chacune des étapes du programme.

public class CheaterEditionMonopolyTemplate : MonopolyTemplate
{
    public CheaterEditionMonopolyTemplate() { }

    protected override void PlayerAction() =>
        Console.Write("Throw dices, move, buy, pay and cheat a lot! ");
}

public class NoDeptMonopolyTemplate : MonopolyTemplate
{
    public NoDeptMonopolyTemplate() { }

    protected override void LoosingCondition() =>
        Console.Write("Loosing all your cash. ");
}

Enfin, il ne nous reste plus qu’à appeler la méthode squelette dans le client en choisissant le bon constructeur.

static void Main(string[] args)
{
    MonopolyTemplate monopolyWithoutDept = new NoDeptMonopolyTemplate();
    monopolyWithoutDept.Play();     // Throw dices, move, buy and pay. Loosing all your cash. 

    MonopolyTemplate monopolyCheaterEdition = new CheaterEditionMonopolyTemplate();
    monopolyCheaterEdition.Play();  // Throw dices, move, buy, pay and cheat a lot! Go bankrupt. 

    MonopolyTemplate classicMonopoly = new MonopolyTemplate();
    classicMonopoly.Play();         // Throw dices, move, buy and pay. Go bankrupt. 
}
Ma classe MonopolyTemplate n’ayant aucune étape abstraite, je peux utiliser son constructeur pour avoir tous les comportements par défaut.

Et voilà ! Pour le coup, plus facile à faire qu’à dire 😉 Comme d’habitude, vous retrouverez le code source de ce programme sur GitLab.


Cas d’utilisation

Les patrons de méthode sont diablement efficaces quand :

  • Un algorithme peut être représenté en une série d’étapes. Préparer le terrain permet de prévenir des potentiels futurs changements et gagner en flexibilité.
  • Vous avez plusieurs versions similaires d’un même algorithme. Vous pouvez ainsi les fusionner et rembourser la dette technique.
  • Vous développez un framework et vous voulez donner du contrôle sur vos modules à vos utilisateurs.

Cependant, les patrons de méthode ne sont pertinents que pour modifier les étapes d’un algorithme. Si vous voulez changer sa structure, même un peu, il faudra trouver une autre solution.

En revanche, rien ne vous empêche d’avoir deux méthodes de structure utilisant les mêmes étapes, il suffit de les mettre dans le même patron de base.

Juste… Faites attention au LSP 😉


Voilà pour cet article sur les patrons de méthode que je le dédie particulièrement aux développeurs qui passent leur temps à cracher sur la POO et le polymorphisme 😘

Comme d’hab, si vous vous ennuyez, allez lire mes notes additionnelles pour en apprendre davantage sur le sujet.

Soutenez le blog en partageant cet article (merci 💙) et suivez-moi sur Twitter @ITExpertFr où je poste mes meilleures trouvailles et réflexions de douche.

Et pour les amateurs d’architecture logicielle, voici une paire d’articles à vous mettre sous la dent :

  • Série sur les patrons de conception.
  • Série sur les principes SOLID.

Quant à moi, je vous laisse, j’ai une petite liasse de flouze qui m’attend à mon hôtel dans la Rue de la Paix.

À bientôt pour un nouvel article sur le blog des développeurs ultra-efficaces. Tchao !


Accéder au bonus : Notes inintéressantes sur les patrons de méthode.