© Vaskaran Sarcar 2019
Vaskaran SarcarJava Design Patternshttps://doi.org/10.1007/978-1-4842-4078-6_23

23. Interpreter Pattern

Vaskaran Sarcar1 
(1)
Bangalore, Karnataka, India
 

This chapter covers the interpreter pattern.

GoF Definition

Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.

Concept

To understand this pattern, you need to be familiar with some key terms, like sentences, grammar, languages, and so forth. So, you may need to visit the topics of formal languages in Automata, if you are not familiar with them.

Normally, this pattern deals with how to evaluate sentences in a language. So, you first need to define a grammar to represent the language. Then the interpreter deals with that grammar. This pattern is best if the grammar is simple.

Each class in this pattern may represent a rule in that language, and it should have a method to interpret an expression. So, to handle a greater number of rules, you need to create a greater number of classes. This is why an interpreter pattern should not be used to handle complex grammar.

Let’s consider different arithmetic expressions in a calculator program. Though these expressions are different, they are all constructed using some basic rules, which are defined in the grammar of the language (of these arithmetic expressions). So, it is best if you can interpret a generic combination of these rules rather than treat each combination of rules as separate cases. An interpreter pattern can be used in such a scenario.

A typical structure of this pattern is often described with a diagram similar to Figure 23-1.
../images/395506_2_En_23_Chapter/395506_2_En_23_Fig1_HTML.jpg
Figure 23-1

Structure of a typical interpreter pattern

The terms are described as follows.
  • AbstractExpression : Typically an interface with an interpret method. You need to pass a context object to this method.

  • TerminalExpression : Used for terminal expressions. A terminal expression does not need other expressions to interpret. These are basically leaf nodes (i.e., they do not have child nodes) in the data structure.

  • NonterminalExpression : Used for nonterminal expressions. Also known as AlternationExpression, RepititionExpression, or SequenceExpression. These are like composites that can contain both the terminal and nonterminal expressions. When you call interpret() method on this, you basically call it on all of its children.

  • Context: Holds the global information that the interpreter needs.

  • Client: Calls the interpret() method . It can optionally build a syntax tree based on the rules of the language.

Note

An interpreter is used to process a language with simple rules or grammar. Ideally, developers do not want to create their own languages. This is the reason why they seldom use this pattern.

Real-World Example

  • A translator who translates a foreign language.

  • Consider music notes as grammar, where musicians play the role of interpreters.

Computer-World Example

  • Java compiler interprets the Java source code into byte code that is understandable by JVM.

  • In C#, the source code is converted to MSIL code that is interpreted by CLR. Upon execution, this MSIL (intermediate code) is converted to native code (binary executable code) by JIT compiler.

Note

In Java, you may also notice the java.util.regex.Pattern class that acts as an interpreter. You can create an instance of this class by invoking the compile() method and then you can use a Matcher instance to evaluate a sentence against the grammar.

Illustration

These are some important steps to implement this pattern.
  • Step 1. Define the rules of the language for which you want to build an interpreter.

  • Step 2. Define an abstract class or an interface to represent an expression. It should contain a method to interpret an expression.
    • Step 2A. Identify terminal and nonterminal expressions. For example, in the upcoming example, IndividualEmployee class is a terminal expression class.

    • Step2B. Create nonterminal expression classes. Each of them calls interpret method on their children. For example, in the upcoming example, OrExpression and AndExpression classes are nonterminal expression classes.

  • Step 3. Build the abstract syntax tree using these classes. You can do this inside the client code or you can create a separate class to accomplish the task.

  • Step 4. A client now uses this tree to interpret a sentence.

  • Step 5. Pass the context to the interpreter. It typically has the sentences that are to be interpreted. An interpreter can do additional tasks using this context.

In the upcoming program, I use the interpreter pattern as a rule validator. I am instantiating different employees with their “years of experience” and current grades. Note the following lines.
       Employee emp1 = new IndividualEmployee(5,"G1");
       Employee emp2 = new IndividualEmployee(10,"G2");
       Employee emp3 = new IndividualEmployee(15,"G3");
       Employee emp4 = new IndividualEmployee(20,"G4");

For simplicity, four employees with four different grades—G1,G2,G3, and G4—are considered here.

Also note the context, as follows.
       //Minimum Criteria for promoton is:
       //The year of experience is minimum 10 yrs. and
       //Employee grade should be either G2 or G3
       Context context=new Context(10,"G2","G3");

So, you can assume that I want to validate some condition against the context, which basically tells you that to be promoted, an employee should have a minimum of 10 years of experience and he/she should be either from the G2 grade or the G3 grade. Once these expressions are interpreted, you see the output in terms of a boolean value.

One important point to note is that this design pattern does not instruct you how to build the syntax tree or how to parse the sentences. It gives you freedom on how to proceed. So, to present a simple scenario, I used an EmployeeBuilder class with a method called buildExpression() to accomplish my task.

Class Diagram

Figure 23-2 shows the class diagram.
../images/395506_2_En_23_Chapter/395506_2_En_23_Fig2_HTML.jpg
Figure 23-2

Class diagram

Package Explorer View

Figure 23-3 shows the high-level structure of the program.
../images/395506_2_En_23_Chapter/395506_2_En_23_Fig3_HTML.jpg
Figure 23-3

Package Explorer view

Implementation

Here is the implementation.
package jdp2e.interpreter.demo;
import java.util.ArrayList;
import java.util.List;
interface Employee
{
    public boolean interpret(Context context);
}
class IndividualEmployee implements Employee
{
    private int yearOfExperience;
    private String currentGrade;
    public IndividualEmployee(int experience, String grade){
        this.yearOfExperience=experience;
        this.currentGrade=grade;
    }
    @Override
    public boolean interpret(Context context)
    {
        if(this.yearOfExperience>=context.getYearofExperience() && context.getPermissibleGrades().contains(this.currentGrade))
        {
            return true;
        }
        return false;
    }
}
class OrExpression implements Employee
{
    private Employee  emp1;
    private Employee  emp2;
    public OrExpression(Employee emp1, Employee emp2)
    {
        this.emp1 = emp1;
        this.emp2 = emp2;
    }
    @Override
    public boolean interpret(Context context)
    {
        return emp1.interpret(context) || emp2.interpret(context);
    }
}
class AndExpression implements Employee
{
    private Employee  emp1;
    private Employee  emp2;
    public AndExpression(Employee emp1, Employee emp2)
    {
        this.emp1 = emp1;
        this.emp2 = emp2;
    }
    @Override
    public boolean interpret(Context context)
    {
        return emp1.interpret(context) && emp2.interpret(context);
    }
}
class NotExpression implements Employee
{
    private Employee  emp;
    public NotExpression(Employee  expr)
    {
        this.emp = expr;
    }
    @Override
    public boolean interpret(Context context)
    {
        return !emp.interpret(context);
    }
}
class Context
{
    private int yearofExperience;
    private List<String> permissibleGrades;
    public Context(int experience,String... allowedGrades)
    {
        this.yearofExperience=experience;
        this.permissibleGrades=new ArrayList<>();
        for( String grade:allowedGrades)
        {
            permissibleGrades.add(grade);
        }
    }
    public int getYearofExperience()
    {
        return yearofExperience;
    }
    public List<String> getPermissibleGrades()
    {
        return permissibleGrades;
    }
}
class EmployeeBuilder
{
    public Employee buildExpression(Employee emp1,  String operator, Employee emp2)
    {
        //Whatever the input,converting it to lowarcase
        switch(operator.toLowerCase())
        {
        case "or":
            return new OrExpression(emp1,emp2);
        case "and":
            return new AndExpression(emp1,emp2);
        case "not":
            return new NotExpression(emp1);
        default:
            System.out.println("Only AND,OR and NOT operators are allowed at present");
            return null;
        }
    }
}
public class InterpreterPatternExample {
    public static void main(String[] args) {
        System.out.println("***Interpreter Pattern Demo*** ");
        //Minimum Criteria for promoton is:
        //The year of experience is minimum 10 yrs. and
        //Employee grade should be either G2 or G3
        Context context=new Context(10,"G2","G3");
        //Different employees with grades
        Employee emp1 = new IndividualEmployee(5,"G1");
        Employee emp2 = new IndividualEmployee(10,"G2");
        Employee emp3 = new IndividualEmployee(15,"G3");
        Employee emp4 = new IndividualEmployee(20,"G4");
        EmployeeBuilder builder=new EmployeeBuilder();
        System.out.println("emp1 is eligible for promotion. " + emp1.interpret(context));
        System.out.println("emp2 is eligible for promotion. " + emp2.interpret(context));
        System.out.println("emp3 is eligible for promotion. " + emp3.interpret(context));
        System.out.println("emp4 is eligible for promotion. " + emp4.interpret(context));
        System.out.println("Is either emp1 or emp3 is eligible for promotion?" +builder.buildExpression(emp1,"Or",emp3).interpret(context));
        System.out.println("Is both emp2 and emp4 are eligible for promotion? ?" + builder.buildExpression(emp2,"And",emp4).interpret(context));
        System.out.println("The statement 'emp3 is NOT eligible for promotion' is true? " + builder.buildExpression(emp3, "Not",null).interpret(context));
        //Invalid input expression
        //System.out.println("Is either emp1 or emp3 is eligible for promotion?" +builder.buildExpression(emp1,"Wrong",emp3).interpret(context));
    }
}

Output

Here is the output.
***Interpreter Pattern Demo***
emp1 is eligible for promotion. false
emp2 is eligible for promotion. true
emp3 is eligible for promotion. true
emp4 is eligible for promotion. false
Is either emp1 or emp3 is eligible for promotion?true
Is both emp2 and emp4 are eligible for promotion? ?false
The statement 'emp3 is NOT eligible for promotion' is true? false

Analysis

You can see that each of the composite expressions are invoking the interpret() method on all of its children.

Modified Illustration

You have just seen a simple example of the interpreter pattern. From this implementation, it may appear to you that you have handled some easy and straightforward expressions. So, lets handle some complex rules or expressions in the modified implementation.

Modified Class Diagram

In the modified implementation, the key changes are made only in the EmployeeBuilder class. So, let’s have a quick look of the class diagram for this class only (see Figure 23-4).
../images/395506_2_En_23_Chapter/395506_2_En_23_Fig4_HTML.jpg
Figure 23-4

Modified Class diagram for EmployeeBuilder class

Modified Package Explorer View

In the modified implementation, the key changes are reflected only in the EmployeeBuilder class. So, in this section I expanded this class only. Figure 23-5 shows the modified Package Explorer view.
../images/395506_2_En_23_Chapter/395506_2_En_23_Fig5_HTML.jpg
Figure 23-5

Modified Package Explorer View

Modified Implementation

Here is the modified implementation. Key changes are shown in bold.
package jdp2e.interpreter.modified.demo;
import java.util.ArrayList;
import java.util.List;
interface Employee
{
    public boolean interpret(Context context);
}
class IndividualEmployee implements Employee
{
    private int yearOfExperience;
    private String currentGrade;
    public IndividualEmployee(int experience, String grade){
        this.yearOfExperience=experience;
        this.currentGrade=grade;
    }
    @Override
    public boolean interpret(Context context)
    {
        if(this.yearOfExperience>=context.getYearofExperience() && context.getPermissibleGrades().contains(this.currentGrade))
        {
            return true;
        }
        return false;
    }
}
class OrExpression implements Employee
{
    private Employee  emp1;
    private Employee  emp2;
    public OrExpression(Employee emp1, Employee emp2)
    {
        this.emp1 = emp1;
        this.emp2 = emp2;
    }
    @Override
    public boolean interpret(Context context)
    {
        return emp1.interpret(context) || emp2.interpret(context);
    }
}
class AndExpression implements Employee
{
    private Employee  emp1;
    private Employee  emp2;
    public AndExpression(Employee emp1, Employee emp2)
    {
        this.emp1 = emp1;
        this.emp2 = emp2;
    }
    @Override
    public boolean interpret(Context context)
    {
        return emp1.interpret(context) && emp2.interpret(context);
    }
}
class NotExpression implements Employee
{
    private Employee  emp;
    public NotExpression(Employee  expr)
    {
        this.emp = expr;
    }
    @Override
    public boolean interpret(Context context)
    {
        return !emp.interpret(context);
    }
}
class Context
{
    private int yearofExperience;
    private List<String> permissibleGrades;
    public Context(int experience,String... allowedGrades)
    {
        this.yearofExperience=experience;
        this.permissibleGrades=new ArrayList<>();
        for( String grade:allowedGrades)
        {
            permissibleGrades.add(grade);
        }
    }
    public int getYearofExperience()
    {
        return yearofExperience;
    }
    public List<String> getPermissibleGrades()
    {
        return permissibleGrades;
    }
}
class EmployeeBuilder
{
    // Building the tree
    //Complex Rule-1: emp1 and (emp2 or (emp3 or emp4))
    public Employee buildTree(Employee emp1, Employee emp2,Employee emp3,Employee emp4)
    {
        //emp3 or emp4
        Employee firstPhase=new OrExpression(emp3,emp4);
        //emp2 or (emp3 or emp4)
        Employee secondPhase=new OrExpression(emp2,firstPhase);
        //emp1 and (emp2 or (emp3 or emp4))
        Employee finalPhase=new AndExpression(emp1,secondPhase);
        return finalPhase;
    }
    //Complex Rule-2: emp1 or (emp2 and (not emp3 ))
    public Employee buildTreeBasedOnRule2(Employee emp1, Employee emp2,Employee emp3)
    {
        //Not emp3
        Employee firstPhase=new NotExpression(emp3);
        //emp2 or (not emp3)
        Employee secondPhase=new AndExpression(emp2,firstPhase);
        //emp1 and (emp2 or (not emp3 ))
        Employee finalPhase=new OrExpression(emp1,secondPhase);
        return finalPhase;
    }
}
public class ModifiedInterpreterPatternExample {
    public static void main(String[] args) {
        System.out.println("***Modified Interpreter Pattern Demo*** ");
        //Minimum Criteria for promoton is:
        //The year of experience is minimum 10 yrs. and
        //Employee grade should be either G2 or G3
        Context context=new Context(10,"G2","G3");
        //Different Employees with grades
        Employee emp1 = new IndividualEmployee(5,"G1");
        Employee emp2 = new IndividualEmployee(10,"G2");
        Employee emp3 = new IndividualEmployee(15,"G3");
        Employee emp4 = new IndividualEmployee(20,"G4");
        EmployeeBuilder builder=new EmployeeBuilder();
        //Validating the 1st complex rule
        System.out.println("Is emp1 and any of emp2,emp3, emp4 is eligible for promotion?" +builder.buildTree(emp1,emp2, emp3,emp4).interpret(context));
        System.out.println("Is emp2 and any of emp1,emp3, emp4 is eligible for promotion?" +builder.buildTree(emp2,emp1, emp3,emp4).interpret(context));
        System.out.println("Is emp3 and any of emp1,emp2, emp3 is eligible for promotion?" +builder.buildTree(emp3,emp1, emp2,emp4).interpret(context));
        System.out.println("Is emp4 and any of emp1,emp2, emp3 is eligible for promotion?" +builder.buildTree(emp4,emp1, emp2,emp3).interpret(context));
        System.out.println("");
        //Validating the 2nd complex rule
        System.out.println("Is emp1 or (emp2 but not emp3) is eligible for promotion?" +builder.buildTreeBasedOnRule2(emp1, emp2, emp3).interpret(context));
        System.out.println("Is emp2 or (emp3 but not emp4) is eligible for promotion?" +builder.buildTreeBasedOnRule2(emp2, emp3, emp4).interpret(context));
    }
}

Modified Output

Here is the modified output.
***Modified Interpreter Pattern Demo***
Is emp1 and any of emp2,emp3, emp4 is eligible for promotion?false
Is emp2 and any of emp1,emp3, emp4 is eligible for promotion?true
Is emp3 and any of emp1,emp2, emp4 is eligible for promotion?true
Is emp4 and any of emp1,emp2, emp3 is eligible for promotion?false
Is emp1 or (emp2 but not emp3) is eligible for promotion?false
Is emp2 or (emp3 but not emp4) is eligible for promotion?true

Analysis

Now you have an idea of how to handle complex rules that follow the approach shown by using an interpreter pattern.

Q&A Session

  1. 1.

    When should I use this pattern?

    In daily programming, it is not needed very much. Though in some rare situations, you may need to work with your own programming language to define specific protocols. In a situation like this, this pattern may become handy. But before you proceed, you must ask yourself about the return on investment (ROI).

     
  2. 2.
    What are the advantages of using an interpreter design pattern?
    • You are very much involved in the process of how to define grammar for your language and how to represent and interpret those sentences. You can change and extend your grammar also.

    • You have full freedom over how to interpret these expressions.

     
  3. 3.

    What are the challenges associated with using interpreter design patterns?

    I believe that the amount of work is the biggest concern. Also maintaining complex grammar becomes tricky because you may need to create (and maintain) separate classes to deal with different rules.

     
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.75.165