Interpreter Design Pattern in Ruby

1. Definition

The Interpreter Design Pattern provides a way to evaluate language grammars or expressions for particular languages. It involves implementing an interpreter for a defined grammar. The pattern involves multiple levels of interpretation to evaluate complex expressions.

2. Problem Statement

Imagine you have a specific language, perhaps for configuration, querying, or certain domain-specific operations and you need to evaluate and execute expressions in this language. Implementing this directly can be cumbersome and hard to maintain.

3. Solution

The Interpreter pattern suggests modeling the domain with classes representing each grammar rule. Each rule can be interpreted or evaluated, leading to a tree of objects that can be traversed to interpret complex expressions.

4. Real-World Use Cases

1. SQL parsers evaluate SQL queries.

2. Regular expression engines interpret and match regex patterns.

3. Specialized languages like configuration or query languages.

5. Implementation Steps

1. Define a grammar for the language.

2. Create an abstract base class to represent individual grammar rules.

3. Implement concrete classes for each grammar rule.

4. Interpret the expressions by traversing the object structure.

6. Implementation in Ruby

# Step 1 and 2: Abstract Expression
class Expression
  def interpret(context)
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
end
# Step 3: Concrete Terminal Expression
class TerminalExpression < Expression
  def initialize(data)
    @data = data
  end
  def interpret(context)
    context.include?(@data)
  end
end
# Step 3: Concrete OR Expression
class OrExpression < Expression
  def initialize(expr1, expr2)
    @expr1 = expr1
    @expr2 = expr2
  end
  def interpret(context)
    @expr1.interpret(context) || @expr2.interpret(context)
  end
end
# Step 3: Concrete AND Expression
class AndExpression < Expression
  def initialize(expr1, expr2)
    @expr1 = expr1
    @expr2 = expr2
  end
  def interpret(context)
    @expr1.interpret(context) && @expr2.interpret(context)
  end
end
# Client code
def get_male_expression
  robert = TerminalExpression.new("Robert")
  john = TerminalExpression.new("John")
  OrExpression.new(robert, john)
end
def get_married_woman_expression
  julie = TerminalExpression.new("Julie")
  married = TerminalExpression.new("Married")
  AndExpression.new(julie, married)
end
male_expr = get_male_expression
married_woman_expr = get_married_woman_expression
puts "John is male? #{male_expr.interpret('John')}"
puts "Julie is a married woman? #{married_woman_expr.interpret('Married Julie')}"

Output:

John is male? true
Julie is a married woman? true

Explanation:

1. Expression is an abstract class that represents a grammar rule.

2. TerminalExpression represents individual data or terminal symbols in the grammar.

3. OrExpression and AndExpression are composite expressions that combine other expressions.

4. In the client code, we create expressions to check if a string represents a male name or a married woman's name and interpret these expressions.

7. When to use?

Use the Interpreter Pattern when:

1. You need to interpret a specialized language.

2. The grammar of the language is relatively simple.

3. Efficiency is not a top priority (interpreters can be slow compared to other approaches).


Comments