Interpreter Pattern

The Interpreter Pattern is a behavioral design pattern that defines a way to evaluate sentences or expressions in a language. It falls under the category of behavioral patterns because it addresses how objects and classes interact and distribute responsibilities. This pattern involves creating an interpreter to interpret sentences in a particular language.

The Interpreter Pattern is useful when you have a domain-specific language (DSL) or expressions that need to be interpreted. It provides a way to define a grammar for a language and then provides an interpreter that can interpret sentences in that language.

Important Components

  1. Abstract Expression: An interface or abstract class declaring an interpret() method that all concrete expressions implement.
  2. Terminal Expression: Represents the leaf nodes of the grammar (e.g., literals or basic elements) and implements the interpret() method to evaluate itself.
  3. Non-Terminal Expression: Represents composite expressions (e.g., operations combining other expressions) and implements the interpret() method by delegating to its child expressions.
  4. Context: Contains global information or state (e.g., variables, input data) used during interpretation.
  5. Client: Builds the abstract syntax tree (AST) from expressions and invokes the interpret() method.

How It Works

  • The grammar of the language is represented as a hierarchy of expression classes (terminal and non-terminal).
  • The Client constructs an abstract syntax tree (AST) by combining terminal and non-terminal expressions.
  • The interpret() method is called on the root of the AST, which recursively evaluates the expression by delegating to child expressions.
  • The Context provides external data (e.g., variable values) needed for evaluation.
  • The pattern interprets the expression step-by-step, producing a result based on the grammar rules.

Sample Implementation

// Context
class Context {
    private Map<String, Integer> variables;

    public Context() {
        variables = new HashMap<>();
    }

    public void setVariable(String name, Integer value) {
        variables.put(name, value);
    }

    public Integer getVariable(String name) {
        return variables.getOrDefault(name, 0);
    }
}

// Abstract Expression
interface Expression {
    int interpret(Context context);
}

// Terminal Expression
class NumberExpression implements Expression {
    private String value; // Could be a number or variable name

    public NumberExpression(String value) {
        this.value = value;
    }

    @Override
    public int interpret(Context context) {
        try {
            // Try parsing as a number
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            // If not a number, treat as a variable
            return context.getVariable(value);
        }
    }
}

// Non-Terminal Expression for Addition
class AddExpression implements Expression {
    private Expression left;
    private Expression right;

    public AddExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret(Context context) {
        return left.interpret(context) + right.interpret(context);
    }
}

// Non-Terminal Expression for Subtraction
class SubtractExpression implements Expression {
    private Expression left;
    private Expression right;

    public SubtractExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret(Context context) {
        return left.interpret(context) - right.interpret(context);
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        // Create context with some variables
        Context context = new Context();
        context.setVariable("x", 10);
        context.setVariable("y", 5);

        // Build expression: (x + 3) - y
        // Equivalent to: (10 + 3) - 5 = 8
        Expression expression = new SubtractExpression(
            new AddExpression(
                new NumberExpression("x"),
                new NumberExpression("3")
            ),
            new NumberExpression("y")
        );

        // Interpret the expression
        int result = expression.interpret(context);
        System.out.println("Result of (x + 3) - y = " + result);

        // Another expression: 7 + 2
        Expression simpleExpression = new AddExpression(
            new NumberExpression("7"),
            new NumberExpression("2")
        );
        System.out.println("Result of 7 + 2 = " + simpleExpression.interpret(context));
    }
}

/*
Result of (x + 3) - y = 8
Result of 7 + 2 = 9
*/Code language: PHP (php)

Use case and Implementation

Parsing and executing financial scripts or custom transaction rules defined by users.

//CommandPatternDemo.java
import java.util.*;
// BankAccount class to manage balance
class BankAccount {
    private int balance;

    public BankAccount(int initialBalance) {
        this.balance = initialBalance;
    }

    public void deposit(int amount) {
        balance += amount;
    }

    public void withdraw(int amount) {
        if (balance >= amount) {
            balance -= amount;
        } else {
            System.out.println("Insufficient balance for withdrawal of: " + amount);
        }
    }

    public int getBalance() {
        return balance;
    }
}

// Command Interface
interface Command {
    void execute();
    void undo();
}

// Concrete Command: Transaction
class Transaction implements Command {
    private BankAccount account;
    private String operation;
    private int amount;

    public Transaction(BankAccount account, String operation, int amount) {
        this.account = account;
        this.operation = operation;
        this.amount = amount;
    }

    @Override
    public void execute() {
        if ("Deposit".equals(operation)) {
            account.deposit(amount);
            System.out.println("Deposited: " + amount + ", Balance: " + account.getBalance());
        } else if ("Withdraw".equals(operation)) {
            account.withdraw(amount);
            System.out.println("Withdrew: " + amount + ", Balance: " + account.getBalance());
        }
    }

    @Override
    public void undo() {
        if ("Deposit".equals(operation)) {
            account.withdraw(amount); // Undo deposit by withdrawing the amount
            System.out.println("Undoing deposit of: " + amount + ", Balance: " + account.getBalance());
        } else if ("Withdraw".equals(operation)) {
            account.deposit(amount); // Undo withdraw by depositing the amount back
            System.out.println("Undoing withdrawal of: " + amount + ", Balance: " + account.getBalance());
        }
    }
}

// Invoker: TransactionManager
class TransactionManager {
    private List<Command> transactions = new ArrayList<>();

    public void addTransaction(Command transaction) {
        transactions.add(transaction);
    }

    public void executeTransactions() {
        for (Command transaction : transactions) {
            transaction.execute();
        }
    }

    public void undoLastTransaction() {
        if (!transactions.isEmpty()) {
            Command lastTransaction = transactions.remove(transactions.size() - 1);
            lastTransaction.undo();
        } else {
            System.out.println("No transactions to undo.");
        }
    }
}

// Client
public class CommandPatternDemo {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000); // Initial balance of 1000
        TransactionManager manager = new TransactionManager();

        System.out.println("Initial Balance: " + account.getBalance());

        // Adding transactions
        manager.addTransaction(new Transaction(account, "Deposit", 1000));
        manager.addTransaction(new Transaction(account, "Withdraw", 500));

        // Executing transactions
        System.out.println("\nExecuting Transactions:");
        manager.executeTransactions();

        // Balance after transactions
        System.out.println("\nBalance After Transactions: " + account.getBalance());

        // Undoing the last transaction
        System.out.println("\nUndoing Last Transaction:");
        manager.undoLastTransaction();

        // Balance after undoing
        System.out.println("\nBalance After Undo: " + account.getBalance());
    }
}

/*
C:\>javac CommandPatternDemo.java

C:\>java CommandPatternDemo
Initial Balance: 1000

Executing Transactions:
Deposited: 1000, Balance: 2000
Withdrew: 500, Balance: 1500

Balance After Transactions: 1500

Undoing Last Transaction:
Undoing withdrawal of: 500, Balance: 2000

Balance After Undo: 2000
*/

Pros

  • Modularity: Each expression type is encapsulated, making it easy to add new expressions (e.g., multiplication, division).
  • Extensibility: New grammar rules can be added by creating new expression classes.
  • Reusability: The same expression classes can interpret different sentences in the language.
  • Clear Structure: Represents the grammar as a class hierarchy, aligning with the language’s syntax.

Cons

  • Complexity: Requires a class for each grammar rule, which can lead to a large number of classes for complex grammars.
  • Performance: Recursive interpretation of complex expressions can be slow.
  • Scalability: Not ideal for very complex languages, where parser generators (e.g., ANTLR, JavaCC) are more efficient.
  • Maintenance: Managing many expression classes can be cumbersome for large grammars.

When to Use

  • When you need to interpret a simple domain-specific language or grammar.
  • When you want to evaluate expressions or sentences defined by a clear set of rules.
  • When the grammar is relatively small and doesn’t require a full-fledged parser.
  • When you need to provide a way for users to define custom expressions (e.g., rule engines, calculators).

Real-World Example

  • SQL Interpreters: Parsing and evaluating SQL queries by breaking them into expressions (e.g., SELECT, WHERE).
  • Rule Engines: Evaluating business rules defined as expressions (e.g., “if age > 18 and income > 50000”).
  • Mathematical Calculators: Interpreting expressions like 2 + 3 * 4 in a calculator application.
  • Configuration Scripts: Interpreting simple scripting languages for configuration files.

 

The Interpreter Pattern is a behavioral design pattern that provides a way to evaluate language grammar or expressions. It defines a grammatical representation for a language and provides an interpreter to process the sentences in the language.

This pattern is particularly useful when dealing with simple scripting languages, command interpreters, or expression evaluators, where the grammar is relatively small and simple. It promotes flexibility and extensibility by allowing new expressions or rules to be added easily without modifying the existing interpreter structure.

Scroll to Top