Interpreter (Интерпретатор)

Паттерн Interpreter (Интерпретатор) описывает способ представления грамматики простого языка и интерпретации предложений этого языка. Он полезен, когда часто требуется разбирать и выполнять выражения фиксированной, но расширяемой грамматики: фильтры, поисковые запросы, правила доступа.

Когда применять Interpreter

  • Грамматика проста и часто меняется, нужно легко добавлять новые правила.
  • Выражений много, но каждое небольшое — важна скорость разработки, а не максимальная производительность.
  • Нужно отделить описание языка (дерево выражений) от его интерпретации (контекст и вычисление).

PlantUML-диаграмма

UML диаграмма Interpreter

// Пример мини-языка логических выражений:
// Поддерживаем операции AND, OR, NOT и терминалы: переменные/константы (true/false).

// Контекст — хранит значения переменных
public class Context
{
    private readonly Dictionary<string, bool> _vars = new();

    public void Set(string name, bool value) => _vars[name] = value;
    public bool Get(string name) => _vars.TryGetValue(name, out var v) ? v : false;
}

// Абстрактное выражение
public interface IExpression
{
    bool Interpret(Context context);
}

// Терминалы
public class Constant : IExpression
{
    private readonly bool _value;
    public Constant(bool value) => _value = value;
    public bool Interpret(Context context) => _value;
}

public class Variable : IExpression
{
    private readonly string _name;
    public Variable(string name) => _name = name;
    public bool Interpret(Context context) => context.Get(_name);
}

// Нетерминалы (правила грамматики)
public class NotExpression : IExpression
{
    private readonly IExpression _expr;
    public NotExpression(IExpression expr) => _expr = expr;
    public bool Interpret(Context context) => !_expr.Interpret(context);
}

public class AndExpression : IExpression
{
    private readonly IExpression _left, _right;
    public AndExpression(IExpression left, IExpression right)
    {
        _left = left; _right = right;
    }
    public bool Interpret(Context context) => _left.Interpret(context) && _right.Interpret(context);
}

public class OrExpression : IExpression
{
    private readonly IExpression _left, _right;
    public OrExpression(IExpression left, IExpression right)
    {
        _left = left; _right = right;
    }
    public bool Interpret(Context context) => _left.Interpret(context) || _right.Interpret(context);
}

// Пример парсинга простейшей записи вручную (в проде — парсер)
class Program
{
    static void Main()
    {
        // Выражение: (isAdmin AND NOT isBanned) OR isModerator
        IExpression expr =
            new OrExpression(
                new AndExpression(
                    new Variable("isAdmin"),
                    new NotExpression(new Variable("isBanned"))
                ),
                new Variable("isModerator")
            );

        var ctx = new Context();
        ctx.Set("isAdmin", true);
        ctx.Set("isBanned", false);
        ctx.Set("isModerator", false);

        Console.WriteLine($"Результат: {expr.Interpret(ctx)}"); // true

        ctx.Set("isBanned", true);
        Console.WriteLine($"Результат: {expr.Interpret(ctx)}"); // false

        ctx.Set("isModerator", true);
        Console.WriteLine($"Результат: {expr.Interpret(ctx)}"); // true
    }
}
type Context struct {
	vars map[string]bool
}

func NewContext() *Context { return &Context{vars: map[string]bool{}} }

func (c *Context) Set(name string, value bool) { c.vars[name] = value }

func (c *Context) Get(name string) bool { return c.vars[name] }

type Expression interface {
	Interpret(ctx *Context) bool
}

type Constant bool

func (c Constant) Interpret(*Context) bool { return bool(c) }

type Variable string

func (v Variable) Interpret(ctx *Context) bool { return ctx.Get(string(v)) }

type Not struct{ Expr Expression }

func (n Not) Interpret(ctx *Context) bool { return !n.Expr.Interpret(ctx) }

type And struct{ Left, Right Expression }

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

type Or struct{ Left, Right Expression }

func (o Or) Interpret(ctx *Context) bool {
	return o.Left.Interpret(ctx) || o.Right.Interpret(ctx)
}

func main() {
	// (isAdmin AND NOT isBanned) OR isModerator
	var expr Expression = Or{
		Left: And{
			Left:  Variable("isAdmin"),
			Right: Not{Expr: Variable("isBanned")},
		},
		Right: Variable("isModerator"),
	}

	ctx := NewContext()
	ctx.Set("isAdmin", true)
	ctx.Set("isBanned", false)
	ctx.Set("isModerator", false)

	fmt.Printf("Результат: %t\n", expr.Interpret(ctx))

	ctx.Set("isBanned", true)
	fmt.Printf("Результат: %t\n", expr.Interpret(ctx))

	ctx.Set("isModerator", true)
	fmt.Printf("Результат: %t\n", expr.Interpret(ctx))
}
from typing import Protocol


class Context:
    def __init__(self) -> None:
        self._vars: dict[str, bool] = {}

    def set(self, name: str, value: bool) -> None:
        self._vars[name] = value

    def get(self, name: str) -> bool:
        return self._vars.get(name, False)


class Expression(Protocol):
    def interpret(self, context: Context) -> bool: ...


class Constant:
    def __init__(self, value: bool) -> None:
        self._value = value

    def interpret(self, context: Context) -> bool:
        return self._value


class Variable:
    def __init__(self, name: str) -> None:
        self._name = name

    def interpret(self, context: Context) -> bool:
        return context.get(self._name)


class Not:
    def __init__(self, expr: Expression) -> None:
        self._expr = expr

    def interpret(self, context: Context) -> bool:
        return not self._expr.interpret(context)


class And:
    def __init__(self, left: Expression, right: Expression) -> None:
        self._left = left
        self._right = right

    def interpret(self, context: Context) -> bool:
        return self._left.interpret(context) and self._right.interpret(context)


class Or:
    def __init__(self, left: Expression, right: Expression) -> None:
        self._left = left
        self._right = right

    def interpret(self, context: Context) -> bool:
        return self._left.interpret(context) or self._right.interpret(context)


# (isAdmin AND NOT isBanned) OR isModerator
expr: Expression = Or(And(Variable("isAdmin"), Not(Variable("isBanned"))), Variable("isModerator"))

ctx = Context()
ctx.set("isAdmin", True)
ctx.set("isBanned", False)
ctx.set("isModerator", False)

print(f"Результат: {expr.interpret(ctx)}")

ctx.set("isBanned", True)
print(f"Результат: {expr.interpret(ctx)}")

ctx.set("isModerator", True)
print(f"Результат: {expr.interpret(ctx)}")
class Context {
    private readonly vars = new Map<string, boolean>();

    set(name: string, value: boolean): void {
        this.vars.set(name, value);
    }

    get(name: string): boolean {
        return this.vars.get(name) ?? false;
    }
}

interface Expression {
    interpret(context: Context): boolean;
}

class Constant implements Expression {
    constructor(private readonly value: boolean) {}

    interpret(): boolean {
        return this.value;
    }
}

class Variable implements Expression {
    constructor(private readonly name: string) {}

    interpret(context: Context): boolean {
        return context.get(this.name);
    }
}

class Not implements Expression {
    constructor(private readonly expr: Expression) {}

    interpret(context: Context): boolean {
        return !this.expr.interpret(context);
    }
}

class And implements Expression {
    constructor(
        private readonly left: Expression,
        private readonly right: Expression,
    ) {}

    interpret(context: Context): boolean {
        return this.left.interpret(context) && this.right.interpret(context);
    }
}

class Or implements Expression {
    constructor(
        private readonly left: Expression,
        private readonly right: Expression,
    ) {}

    interpret(context: Context): boolean {
        return this.left.interpret(context) || this.right.interpret(context);
    }
}

// (isAdmin AND NOT isBanned) OR isModerator
const expr: Expression = new Or(
    new And(new Variable("isAdmin"), new Not(new Variable("isBanned"))),
    new Variable("isModerator"),
);

const ctx = new Context();
ctx.set("isAdmin", true);
ctx.set("isBanned", false);
ctx.set("isModerator", false);

console.log(`Результат: ${expr.interpret(ctx)}`);

ctx.set("isBanned", true);
console.log(`Результат: ${expr.interpret(ctx)}`);

ctx.set("isModerator", true);
console.log(`Результат: ${expr.interpret(ctx)}`);
import java.util.HashMap;
import java.util.Map;

class Context {
    private final Map<String, Boolean> vars = new HashMap<>();

    void set(String name, boolean value) {
        vars.put(name, value);
    }

    boolean get(String name) {
        return vars.getOrDefault(name, false);
    }
}

interface Expression {
    boolean interpret(Context context);
}

record Constant(boolean value) implements Expression {
    public boolean interpret(Context context) {
        return value;
    }
}

record Variable(String name) implements Expression {
    public boolean interpret(Context context) {
        return context.get(name);
    }
}

record Not(Expression expr) implements Expression {
    public boolean interpret(Context context) {
        return !expr.interpret(context);
    }
}

record And(Expression left, Expression right) implements Expression {
    public boolean interpret(Context context) {
        return left.interpret(context) && right.interpret(context);
    }
}

record Or(Expression left, Expression right) implements Expression {
    public boolean interpret(Context context) {
        return left.interpret(context) || right.interpret(context);
    }
}

public class Program {
    public static void main(String[] args) {
        // (isAdmin AND NOT isBanned) OR isModerator
        Expression expr = new Or(
                new And(new Variable("isAdmin"), new Not(new Variable("isBanned"))),
                new Variable("isModerator"));

        Context ctx = new Context();
        ctx.set("isAdmin", true);
        ctx.set("isBanned", false);
        ctx.set("isModerator", false);

        System.out.println("Результат: " + expr.interpret(ctx));

        ctx.set("isBanned", true);
        System.out.println("Результат: " + expr.interpret(ctx));

        ctx.set("isModerator", true);
        System.out.println("Результат: " + expr.interpret(ctx));
    }
}
class Context {
    private val vars = mutableMapOf<String, Boolean>()

    fun set(name: String, value: Boolean) {
        vars[name] = value
    }

    fun get(name: String): Boolean = vars[name] ?: false
}

// Грамматика как sealed-иерархия: компилятор проверит, что разобраны все узлы.
sealed interface Expression {
    fun interpret(context: Context): Boolean
}

data class Constant(val value: Boolean) : Expression {
    override fun interpret(context: Context) = value
}

data class Variable(val name: String) : Expression {
    override fun interpret(context: Context) = context.get(name)
}

data class Not(val expr: Expression) : Expression {
    override fun interpret(context: Context) = !expr.interpret(context)
}

data class And(val left: Expression, val right: Expression) : Expression {
    override fun interpret(context: Context) = left.interpret(context) && right.interpret(context)
}

data class Or(val left: Expression, val right: Expression) : Expression {
    override fun interpret(context: Context) = left.interpret(context) || right.interpret(context)
}

fun main() {
    // (isAdmin AND NOT isBanned) OR isModerator
    val expr: Expression = Or(
        And(Variable("isAdmin"), Not(Variable("isBanned"))),
        Variable("isModerator"),
    )

    val ctx = Context()
    ctx.set("isAdmin", true)
    ctx.set("isBanned", false)
    ctx.set("isModerator", false)

    println("Результат: ${expr.interpret(ctx)}")

    ctx.set("isBanned", true)
    println("Результат: ${expr.interpret(ctx)}")

    ctx.set("isModerator", true)
    println("Результат: ${expr.interpret(ctx)}")
}

Плюсы и минусы

Плюсы Минусы
Простое добавление новых правил/выражений путём создания классов Много мелких классов, усложняющих структуру проекта
Чёткое разделение грамматики и интерпретации Низкая производительность на больших выражениях (глубокие деревья)
Подходит для DSL, фильтров, правил валидации/доступа Требует парсера для полноценного языка

Заключение

Interpreter удобен для небольших предметно-ориентированных языков и систем правил, где важна расширяемость и читабельность грамматики. Для сложных и высоконагруженных сценариев лучше использовать специализированные парсеры, компиляторы или механизмы правил.