Interpreter pattern is used to define a representation of a language’s grammar along with an interpreter that uses this representation to interpret sentences in that language. It is especially useful when a problem can be expressed in a simple grammar. The interpreter pattern involves defining an abstract expression for the grammar, concrete expressions for terminal and non-terminal symbols, and an interpreter that evaluates expressions based on the context.

Applicability

  • When the grammar of a simple language needs to be interpreted or parsed
  • When recurring expressions in a language can be represented using object-oriented classes
  • Useful for domain-specific languages, mathematical expression parsing, or custom scripting

Pros and Cons

Advantages

  • Easy to extend grammar
    • You can add new rules or expressions by introducing new classes without altering the existing system.
  • Readable object structure
    • Grammars and expressions are represented in object-oriented form, making the logic more modular and easy to trace.

Drawbacks

  • Complex for large grammars
    • For complex or real-world grammars, the number of classes can grow rapidly, making the system harder to maintain.
  • Low performance
    • Because it uses a recursive structure and numerous objects, performance is often lower than dedicated parsers.

Approach

  1. Define the abstract expression: This is the base interface or abstract class for all expression types.
  2. Create terminal expressions: Represent the leaves of the grammar tree (e.g., literals, numbers).
  3. Create non-terminal expressions: These represent rules composed of other expressions (e.g., AddExpression, SubtractExpression).
  4. Define context: A class that stores global information used during interpretation (e.g., a variable map).
  5. Build and interpret expressions: Construct an expression tree and call the interpret() method.

Components

  • AbstractExpression
    • Declares an interpret(context) method shared by all expressions.
  • TerminalExpression
    • Implements interpretation for a terminal symbol (literal, variable).
  • NonTerminalExpression
    • Represents rules involving other expressions, e.g., addition, subtraction.
  • Context
    • Holds variable mappings or external data needed during evaluation.

Example

The arithmetic expression interpreter showcases how expressions like (5 + 3) - 2 can be evaluated using interpreter pattern.

Abstract expression

interface Expression {
    int interpret();
}

Terminal expression

class NumberExpression implements Expression {
    private final int number;
 
    public NumberExpression(int number) {
        this.number = number;
    }
 
    @Override
    public int interpret() {
        return number;
    }
}

Non-terminal expression

class AddExpression implements Expression {
    private final Expression left, right;
 
    public AddExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }
 
    @Override
    public int interpret() {
        return left.interpret() + right.interpret();
    }
}
 
class SubtractExpression implements Expression {
    private final Expression left, right;
 
    public SubtractExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }
 
    @Override
    public int interpret() {
        return left.interpret() - right.interpret();
    }
}
 

Client

public class InterpreterExample {
    public static void main(String[] args) {
        // Represent (5 + 3) - 2
        Expression expr = new SubtractExpression(
            new AddExpression(
                new NumberExpression(5),
                new NumberExpression(3)
            ),
            new NumberExpression(2)
        );
 
        int result = expr.interpret();
        System.out.println("Result: " + result);  // Output: 6
    }
}

Back to parent page: Behavioural Patterns

Design_Pattern Behavioural_Design_Patterns Interpreter_Pattern SOFT3202