Using Interpreter Design Pattern In Java
The Interpreter Design Pattern is one of the Gang of Four design patterns which specifies how to evaluate sentences in a language.
Join the DZone community and get the full member experience.
Join For FreeHere I am with another design pattern to discuss — Interpreter Design Pattern.
Interpreter Design Pattern
- The Interpreter Design Pattern is one of the Gang of Four design patterns which specifies how to evaluate sentences in a language.
- The Interpreter pattern is a behavioral pattern that provides a way to evaluate language grammar or expression.
- The Interpreter pattern uses an expression interface that tells how to interpret a particular context.
- The Interpreter pattern is used to define a grammatical representation for a language and also provides an interpreter to deal with the grammar.
- The basic idea of the Interpreter pattern is to have a class for each symbol:
- Terminal - are the elementary symbols of the language defined by a formal grammar.
- NonTerminal - also known as syntactic variables that are replaced by groups of terminal symbols according to the production rules. The NonTerminal uses a composite design pattern in general.
- A grammar is defined by production rules that specify which symbols may replace which other symbols. The production rules or simply called productions may be used to generate and parse strings.
- SQL interpreter is a good example of this pattern.
- Language interpreters are another great example of this.
- I have also used the Interpreter pattern in the example of the Command Design Pattern for interpreting the appliance and its operation to perform in the command string.
Employee Data Management Using Interpreter Design Pattern
Let's create a class for storing Employee data:
package org.trishinfotech.interpreter;
public abstract class Employee {
protected long employeeId;
protected String employeeName;
protected String designation;
protected Department department;
protected int salary;
public Employee(long employeeId, String employeeName, String designation, Department department, int salary) {
super();
this.employeeId = employeeId;
this.employeeName = employeeName;
this.designation = designation;
this.department = department;
this.salary = salary;
}
public long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(long employeeId) {
this.employeeId = employeeId;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public String getDesignation() {
return designation;
}
public void setDesignation(String designation) {
this.designation = designation;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public void setSalary(int salary) {
this.salary = salary;
}
public int getSalary() {
return salary;
}
public abstract int teamSize();
public abstract String teamNames();
public abstract boolean isManager();
public String fullDetails() {
StringBuilder builder = new StringBuilder();
builder.append("Employee [").append(employeeId).append(", ").append(employeeName).append(", ")
.append(designation).append(", ").append(department).append(", ").append(salary).append(", TeamSize=")
.append(teamSize()).append(", Team=").append(teamNames()).append("]");
return builder.toString();
}
public String shortDetails() {
StringBuilder builder = new StringBuilder();
builder.append("'").append(employeeName).append("'");
return builder.toString();
}
public String toString() {
return fullDetails();
}
}
Here's the code for the Engineer class:
xxxxxxxxxx
package org.trishinfotech.interpreter;
public class Engineer extends Employee {
public Engineer(long employeeId, String employeeName, String designation, Department department, int salary) {
super(employeeId, employeeName, designation, department, salary);
}
public int teamSize() {
return 1;
}
public boolean isManager() {
return false;
}
public String teamNames() {
return "{NA}";
}
}
Here's the code for the Manager class:
xxxxxxxxxx
package org.trishinfotech.interpreter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class Manager extends Employee {
List<Employee> managingEmployees = new ArrayList<Employee>();
public Manager(long employeeId, String employeeName, String designation, Department department, int salary) {
super(employeeId, employeeName, designation, department, salary);
}
public boolean manages(Employee employee) {
return managingEmployees.add(employee);
}
public boolean stopManaging(Employee employee) {
return managingEmployees.remove(employee);
}
public List<Employee> getManagingEmployees() {
return managingEmployees;
}
public int teamSize() {
return managingEmployees.stream().mapToInt(employee -> employee.teamSize()).sum();
}
public boolean isManager() {
return true;
}
public String teamNames() {
StringBuilder builder = new StringBuilder();
builder.append("{").append(String.join(", ", managingEmployees.stream().map(employee -> {
if (employee.isManager()) {
return employee.getEmployeeName() + " " + employee.teamNames();
} else {
return employee.getEmployeeName();
}
}).collect(Collectors.toList()))).append("}");
return builder.toString();
}
}
Here's the code for Department enum:
xxxxxxxxxx
package org.trishinfotech.interpreter;
public enum Department {
ENG, // engineering
HR, // human resource
ADMIN, // admin
FACILITY, // facilities
SUPPORT // customer support
}
Now, let's start implementing the Expression pattern. First, we define the interface for Expression:
xxxxxxxxxx
package org.trishinfotech.interpreter.expr;
import org.trishinfotech.interpreter.Employee;
public interface Expression {
public boolean interpret(Employee context);
}
Here's the code for TerminalExpression class:
xxxxxxxxxx
package org.trishinfotech.interpreter.expr;
public abstract class TerminalExpression implements Expression {
public TerminalExpression() {
super();
}
}
Here's the code for NonTerminalExpression class:
x
package org.trishinfotech.interpreter.expr;
public abstract class NonTerminalExpression implements Expression {
protected Expression expression;
public NonTerminalExpression() {
super();
}
public Expression getExpression() {
return expression;
}
public void setExpression(Expression expression) {
this.expression = expression;
}
}
I am using only TerminalExpression in the example and NonTerminalExpression is provided for reference and enhancement at your end.
Here's the code for NameExpression class:
xxxxxxxxxx
package org.trishinfotech.interpreter.expr;
import org.trishinfotech.interpreter.Employee;
public class NameExpression extends TerminalExpression {
protected String name;
public NameExpression(String name) {
this.name = name;
}
public boolean interpret(Employee context) {
return context.getEmployeeName().equalsIgnoreCase(name);
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("NameExpression [name=").append(name).append("]");
return builder.toString();
}
}
Here's the code for DesignationExpression class:
xxxxxxxxxx
package org.trishinfotech.interpreter.expr;
import org.trishinfotech.interpreter.Employee;
public class DesignationExpression extends TerminalExpression {
protected String designation;
public DesignationExpression(String designation) {
this.designation = designation;
}
public boolean interpret(Employee context) {
return context.getDesignation().equalsIgnoreCase(designation);
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("DesignationExpression [designation=").append(designation).append("]");
return builder.toString();
}
}
Here's the code for DepartmentExpression class:
xxxxxxxxxx
package org.trishinfotech.interpreter.expr;
import org.trishinfotech.interpreter.Employee;
public class DepartmentExpression extends TerminalExpression {
protected String department;
public DepartmentExpression(String department) {
super();
this.department = department;
}
public boolean interpret(Employee context) {
return context.getDepartment().name().equalsIgnoreCase(department);
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("DepartmentExpression [department=").append(department).append("]");
return builder.toString();
}
}
Here's the code for ManagerOfExpression class:
xxxxxxxxxx
package org.trishinfotech.interpreter.expr;
import java.util.stream.Collectors;
import org.trishinfotech.interpreter.Employee;
import org.trishinfotech.interpreter.Manager;
public class ManagerOfExpression extends TerminalExpression {
protected String name;
public ManagerOfExpression(String name) {
this.name = name;
}
public boolean interpret(Employee context) {
if (context.isManager()) {
Manager manager = (Manager) context;
return manager.getManagingEmployees().stream()
.map(managingEmployee -> managingEmployee.getEmployeeName().toLowerCase())
.collect(Collectors.toList()).contains(name.toLowerCase());
}
return false;
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("ManagerOfExpression [name=").append(name).append("]");
return builder.toString();
}
}
Here's the code for SalaryExpression class:
xxxxxxxxxx
package org.trishinfotech.interpreter.expr;
import org.trishinfotech.interpreter.Employee;
public class SalaryExpression extends TerminalExpression {
protected String salary;
public SalaryExpression(String salary) {
super();
this.salary = salary;
}
public boolean interpret(Employee context) {
if (!Character.isDigit(salary.charAt(0))) {
char operator = salary.charAt(0);
return interpretIntSalary(context, salary.substring(1), operator);
} else {
return interpretIntSalary(context, salary, '=');
}
}
private boolean interpretIntSalary(Employee context, String salary, char operator) {
try {
int intSalary = Integer.parseInt(salary);
switch (operator) {
case '>':
return context.getSalary() > intSalary;
case '<':
return context.getSalary() < intSalary;
case '+':
case '-':
default:
return context.getSalary() == intSalary;
}
} catch (NumberFormatException exp) {
return false;
}
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("SalaryExpression [salary=").append(salary).append("]");
return builder.toString();
}
}
Additionally, I am writing two more classes to combine expressions via 'And' or 'Or'.
Here's the code for AndExpression class:
xxxxxxxxxx
package org.trishinfotech.interpreter.expr;
import org.trishinfotech.interpreter.Employee;
public class AndExpression implements Expression {
protected Expression left;
protected Expression right;
public AndExpression(Expression left, Expression right) {
super();
this.left = left;
this.right = right;
}
public boolean interpret(Employee context) {
return left.interpret(context) && right.interpret(context);
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("AndExpression [left=").append(left).append(", right=").append(right).append("]");
return builder.toString();
}
}
Similarly, here's the code for OrExpression class:
xxxxxxxxxx
package org.trishinfotech.interpreter.expr;
import org.trishinfotech.interpreter.Employee;
public class OrExpression implements Expression {
protected Expression left;
protected Expression right;
public OrExpression(Expression left, Expression right) {
super();
this.left = left;
this.right = right;
}
public boolean interpret(Employee context) {
return left.interpret(context) || right.interpret(context);
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("OrExpression [left=").append(left).append(", right=").append(right).append("]");
return builder.toString();
}
}
Again, I am using AndExpression only and I am providing OrExpression for your reference to use in your use case.
Now let write a class to parse the expression. Here I am using the below format for the expression.
<Key>:<Value>, <Key>:<Value>
Having a pattern for parsing is recommended and that offers easy implementation, maintains an understanding of the expression. The format defines the grammar of the expression which is needed for the accurate evaluation of the expression.
Now since we are agreed on the format for expression, let's write a class for parsing the expression and create an Expression object. Writing parser is not part of the Interpreter Pattern and provided only for completing the code and example.
Expression format and Parser are tightly coupled and we have to modify or use a different parser if we do change the expression format.
Here's the code for ExpressionParser class:
package org.trishinfotech.interpreter.expr;
public class ExpressionParser {
protected static final String NAME = "name";
protected static final String DESIG = "desig";
protected static final String DEPTT = "deptt";
protected static final String MANAGES = "manages";
protected static final String SALARY = "salary";
public static Expression parseExpression(String contextString) {
Expression expression = null;
String[] keyValues = contextString.split(",");
for (int index = 0; index < keyValues.length; index++) {
String keyValue = keyValues[index];
String[] words = keyValue.trim().split(":");
Expression anotherExpression = getExpression(words[0].trim(), words[1].trim());
if (expression == null) {
expression = anotherExpression;
} else {
expression = new AndExpression(expression, anotherExpression);
}
}
return expression;
}
public static Expression getExpression(String keyword, String value) {
if (NAME.equalsIgnoreCase(keyword)) {
return new NameExpression(value);
} else if (DESIG.equalsIgnoreCase(keyword)) {
return new DesignationExpression(value);
} else if (DEPTT.equalsIgnoreCase(keyword)) {
return new DepartmentExpression(value);
} else if (MANAGES.equalsIgnoreCase(keyword)) {
return new ManagerOfExpression(value);
} else if (SALARY.equalsIgnoreCase(keyword)) {
return new SalaryExpression(value);
}
return null;
}
}
We can see that each expression concrete class is bound with a keyword to decode and evaluate.
Now since our, all component is ready to use for Expression pattern, let's write our Main class to execute and test the output:
x
package org.trishinfotech.interpreter;
import org.trishinfotech.interpreter.expr.Expression;
import org.trishinfotech.interpreter.expr.ExpressionParser;
public class Main {
public static void main(String[] args) {
Engineer ajay = new Engineer(1001l, "Ajay", "Developer", Department.ENG, 75000);
Engineer vijay = new Engineer(1002l, "Vijay", "Sr. Developer", Department.ENG, 90000);
Engineer jay = new Engineer(1003l, "Jay", "Lead", Department.ENG, 100000);
Engineer martin = new Engineer(1004l, "Martin", "QA", Department.ENG, 70000);
Manager kim = new Manager(1005l, "Kim", "Manager", Department.ENG, 110000);
Engineer andersen = new Engineer(1006l, "Andersen", "Developer", Department.ENG, 95000);
Manager niels = new Manager(1007l, "Niels", "Sr. Manager", Department.ENG, 140000);
Engineer robert = new Engineer(1008l, "Robert", "Developer", Department.ENG, 85000);
Manager rachelle = new Manager(1009l, "Rachelle", "Product Manager", Department.ENG, 150000);
Engineer shailesh = new Engineer(1010l, "Shailesh", "Engineer", Department.ENG, 80000);
kim.manages(ajay);
kim.manages(martin);
kim.manages(vijay);
niels.manages(jay);
niels.manages(andersen);
niels.manages(shailesh);
rachelle.manages(kim);
rachelle.manages(robert);
rachelle.manages(niels);
String contextString = "Desig:manager, Deptt:eng, Manages:martin, salary:110000";
Expression expression = ExpressionParser.parseExpression(contextString);
System.out.println("contextString= " + contextString);
System.out.println();
System.out.println(kim);
System.out.printf("For '%s', %s: %s.\n", kim.getEmployeeName(), expression, expression.interpret(kim));
System.out.println("=======================================================================\n");
contextString = "Desig:developer, Deptt:eng, salary:<85000";
expression = ExpressionParser.parseExpression(contextString);
System.out.println("contextString= " + contextString);
System.out.println();
System.out.println(ajay);
System.out.printf("For '%s', %s: %s.\n", ajay.getEmployeeName(), expression, expression.interpret(ajay));
System.out.println();
System.out.println(andersen);
System.out.printf("For '%s', %s: %s.\n", andersen.getEmployeeName(), expression,
expression.interpret(andersen));
}
}
And below is the output of the program:
xxxxxxxxxx
contextString= Desig:manager, Deptt:eng, Manages:martin, salary:110000
Employee [1005, Kim, Manager, ENG, 110000, TeamSize=3, Team={Ajay, Martin, Vijay}]
For 'Kim', AndExpression [left=AndExpression [left=AndExpression [left=DesignationExpression [designation=manager], right=DepartmentExpression [department=eng]], right=ManagerOfExpression [name=martin]], right=SalaryExpression [salary=110000]]: true.
=======================================================================
contextString= Desig:developer, Deptt:eng, salary:<85000
Employee [1001, Ajay, Developer, ENG, 75000, TeamSize=1, Team={NA}]
For 'Ajay', AndExpression [left=AndExpression [left=DesignationExpression [designation=developer], right=DepartmentExpression [department=eng]], right=SalaryExpression [salary=<85000]]: true.
Employee [1006, Andersen, Developer, ENG, 95000, TeamSize=1, Team={NA}]
For 'Andersen', AndExpression [left=AndExpression [left=DesignationExpression [designation=developer], right=DepartmentExpression [department=eng]], right=SalaryExpression [salary=<85000]]: false.
The Source Code can be found here: Interpreter-Design-Pattern-Sample-Code
I hope this tutorial helps in the understanding of the Interpreter design pattern.
Liked the article? Please don't forget to press that like button. Happy coding!
Opinions expressed by DZone contributors are their own.
Comments