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.
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
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.
For simplicity, four employees with four different grades—G1,G2,G3, and G4—are considered here.
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
Package Explorer View
Implementation
Output
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
Modified Package Explorer View
Modified Implementation
Modified Output
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.
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.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.
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.