Accueil / Tutoriels / Interpreter

Interpreter

Publié le 2 Juin 2025
Conception

Interpreter est un patron de conception comportemental qui permet d’interpréter une langue formelle ou un langage spécifique. Il définit une représentation grammaticale pour un langage, et fournit un interpréteur pour traiter les phrases de ce langage.

Ce design pattern est utile lorsqu’on veut donner à un utilisateur ou à un administrateur la possibilité de décrire dynamiquement des règles, des expressions ou des filtres sans toucher au code source. On le retrouve souvent dans la création de moteurs de règles personnalisés, de langages de requête simplifiés, ou encore dans des systèmes de filtrage avancés.

Principe

Le pattern Interpreter repose sur la modélisation d’un langage simple sous forme d’un arbre syntaxique composé de différentes expressions (terminales ou non terminales). Chaque nœud de l’arbre représente une opération ou une valeur. En parcourant cet arbre, l’interpréteur peut évaluer une expression ou exécuter une logique spécifique.

Interpreter peut ainsi proposer une logique personnalisable à un utilisateur final ou à un admin, créer un langage métier simple adapté à une application, séparer la logique métier de l’implémentation technique ou encore éviter les injections de code en évitant d’exposer directement du SQL, du JavaScript ou du code exécutable.

Exemples d’utilisations

Moteur de règles dynamiques dans un CMS ou une application e-commerce

Un site e-commerce ou un CMS doit afficher certains blocs de contenu en fonction de conditions définies par l’administrateur.

Le pattern Interpreter permet de transformer ces chaînes de caractères en arbres d’expressions interprétables, offrant une logique métier dynamique et sécurisée. Il est possible de faire un affichage conditionnel de contenu (bannières, blocs dynamiques), de personnaliser sa gestion marketing de manière avancée sans devoir déployer du code supplémentaire ou encore de mettre à jour des règles en temps réel.

Filtres de recherche avancés dans une interface personnalisable (SaaS, CRM, ERP)

Un utilisateur veut filtrer des données via une interface dans une application SaaS. On propose un langage de requêtes simples qui évite l’exposition directe à SQL. On peut interpréter ces règles pour générer des requêtes SQL dynamiques, des filtres en mémoire et des appels API conditionnels. Cela rend une application modulaire, puissante et intuitive pour des utilisateurs non techniques.

Points d’attentions

Le pattern Interpreter présente certaines limites qu'il est important de connaître avant de l'intégrer dans un projet web.

Complexité en termes de performance

L’un des inconvénients du pattern Interpreter est sa performance. Si un arbre d'expressions devient trop profond ou trop complexe, son évaluation peut ralentir considérablement l’application. Cette complexité algorithmique peut impacter les performances globales dans des environnements avec des ressources limitées ou dans des traitements à grande échelle.

Difficulté de maintenance du code

Plus le langage implémenté est riche en fonctionnalités, plus le code de l’interpréteur devient difficile à maintenir. Chaque nouvelle règle ou opérateur logique ajouté implique la création de nouvelles classes ou l’extension des expressions existantes. Le code peut devenir rapidement difficile à faire évoluer ou à refactorer.

Risques de sécurité si les expressions ne sont pas validées

Un autre point de vigilance concerne la sécurité. Si les expressions fournies par l’utilisateur ne sont pas correctement validées ou nettoyées, cela peut entraîner des comportements inattendus, voire des failles de sécurité. Il est important de contrôler les éléments autorisés dans le langage et de mettre en place un système de validation stricte.

Implémenter un Interpreter

Il faut tout d’abord définir la grammaire du langage. Il s’agit ici de déterminer les opérations que l’on souhaite autoriser (par exemple : égalité, comparaison, inclusion, logique) ainsi que les types de données que le langage doit manipuler (chaînes de caractères, nombres, booléens, etc…).

Ensuite, il faut une interface générique, souvent appelée Expression, qui expose une méthode comme interpreter(context) ou evaluate(context). Cette méthode est le point d’entrée pour l’exécution de l'expression dans un contexte donné.

À partir de cette interface, on implémente plusieurs classes concrètes représentant les différents types d'expressions. On distingue généralement deux grandes catégories : les expressions terminales, qui représentent les éléments de base comme les constantes, les variables ou les comparateurs simples et les expressions non terminales, qui combinent d’autres expressions, comme And, Or, Not ou encore des groupes logiques.

Une fois ces éléments définis, on va construire un arbre syntaxique qui représente la structure logique de l’expression. Cela peut se faire via un parseur personnalisé, un builder d'expressions ou même à partir d’un générateur automatique si le langage le permet.

Enfin, l’expression est évaluée dans un contexte précis, par exemple en fonction des attributs d’un utilisateur connecté, d’une date ou d’autres variables métier. C’est cette phase d’exécution qui permet d’interpréter dynamiquement la logique définie dans le langage personnalisé.

Voici un exemple en Go :


package exp

type Context struct {
   User map[string]interface{}
}

type Expression interface {
   Interpret(ctx Context) bool
}


package exp

type And struct {
   Left, Right Expression
}

func (a And) Interpret(ctx Context) bool {
   return a.Left.Interpret(ctx) && a.Right.Interpret(ctx)
}


package exp

type Equals struct {
   Key   string
   Value interface{}
}

func (e Equals) Interpret(ctx Context) bool {
   val, ok := ctx.User[e.Key]
   return ok && val == e.Value
}


package exp

type NotEquals struct {
   Key   string
   Value interface{}
}

func (e NotEquals) Interpret(ctx Context) bool {
   val, ok := ctx.User[e.Key]
   return ok && val != e.Value
}


package main

import (
   "fmt"
   "designpatterns/interpreter/exp"
)

func main() {
   ctx := exp.Context{User: map[string]interface{}{
      "is_company": true,
      "country":    "BE",
   }}

   rule := exp.And{
      Left:  exp.Equals{Key: "is_company", Value: true},
      Right: exp.NotEquals{Key: "country", Value: "FR"},
   }

   if rule.Interpret(ctx) {
      fmt.Println("Afficher le champ TVA")
   } else {
      fmt.Println("Ne pas afficher le champ TVA")
   }
}

Le même exemple en PHP :


namespace Practice\DesignPatterns\Interpreter;

interface Expression {
   public function interpret(array $context): bool;
}


namespace Practice\DesignPatterns\Interpreter;

class AndExpr implements Expression {
   private Expression $left;
   private Expression $right;

   public function __construct(Expression $left, Expression $right) {
       $this->left = $left;
       $this->right = $right;
   }

   public function interpret(array $context): bool {
       return $this->left->interpret($context) && $this->right->interpret($context);
   }
}


namespace Practice\DesignPatterns\Interpreter;

class Equals implements Expression {
   private string $key;
   private $value;

   public function __construct(string $key, $value) {
       $this->key = $key;
       $this->value = $value;
   }

   public function interpret(array $context): bool {
       return isset($context[$this->key]) && $context[$this->key] === $this->value;
   }
}


namespace Practice\DesignPatterns\Interpreter;

class NotEquals implements Expression {
   private string $key;
   private $value;

   public function __construct(string $key, $value) {
       $this->key = $key;
       $this->value = $value;
   }

   public function interpret(array $context): bool {
       return isset($context[$this->key]) && $context[$this->key] !== $this->value;
   }
}


declare(strict_types=1);

require "./vendor/autoload.php";

use Practice\DesignPatterns\Interpreter\AndExpr;
use Practice\DesignPatterns\Interpreter\Equals;
use Practice\DesignPatterns\Interpreter\NotEquals;

$context = [
   'is_company' => true,
   'country' => 'BE',
];

$rule = new AndExpr(
   new Equals('is_company', true),
   new NotEquals('country', 'FR')
);

if ($rule->interpret($context)) {
   echo "Afficher le champ TVA";
} else {
   echo "Ne pas afficher le champ TVA";
}

Conclusion

J'espère que grâce à cet article vous êtes désormais plus à l'aise avec le design pattern Bridge. Vous pouvez retrouver mon article sur les patrons de conception ou les autres tutoriels sur Facade, Memento, Bridge, Chain of responsibility et Visitor.