Java design pattern -- Interpreter Pattern

1, Introduction

The 23 design modes are roughly divided into three categories:

There are 5 kinds of creation modes: factory method mode, abstract factory mode, singleton mode, prototype mode and builder mode.

7 types (structural mode): adapter mode, decorator mode, agent mode, appearance mode, bridge mode, combination mode and sharing mode.

11 kinds of behavioral modes: policy mode, template method mode, observer mode, iterator mode, responsibility chain mode, command mode, memo mode, status mode, visitor mode, mediator mode and interpreter mode.

Behavior type can be divided by the relationship between classes:

Basic introduction to interpreter mode:

  • In the compilation principle, an arithmetic expression forms lexical units through the lexical analyzer, and then these lexical units build a parsing tree through the parser, and finally form an abstract parsing tree. The lexical analyzer and parser here can be regarded as interpreters
  • Interpreter Pattern: given a language (expression), define a representation of its grammar, and define an interpreter to interpret sentences (expressions) in the language
  • Application scenario
    • A sentence in a language that needs to be interpreted and executed can be represented as an abstract syntax tree
    • Some recurring problems can be expressed in a simple language
    • A simple syntax needs to be explained

2, Interpreter mode

1. Schematic class diagram of interpreter mode

Description of schematic class diagram:

  • Context: is an environment role that contains global information outside the interpreter

  • AbstractExpression: abstract expression, which declares an abstract interpretation operation interpret(). This method is shared by all nodes in the abstract syntax tree

  • TerminalExpression: it is a terminator expression that implements interpretation operations related to terminators in grammar

  • NonTermialExpression: it is a non terminator expression, which implements interpretation operations for non terminators in grammar

Note: enter the Context and TerminalExpression information through the Client

3, Specific needs

1. Four arithmetic problems

  • First enter the form of the expression, such as a+b+c-d+e, which requires that the letters of the expression cannot be repeated

  • Then enter the values of a, B, C, D and e respectively

  • Finally, the result is obtained as shown in the figure (for simplicity, only addition and subtraction are considered)

2. Traditional programmes

Traditional problem analysis:

  • Write a method to receive the form of an expression, and then analyze it according to the value entered by the user to get the result
  • Problem analysis: if you add new operators, such as * / (), it is not conducive to expansion; In addition, allowing a method to parse will cause confusion and unclear program structure
  • Solution: you can consider using the interpreter mode, that is: expression - > interpreter (there can be multiple) - > result

3. Interpreter mode scheme

Thought analysis - class diagram

explain:

  • Expression: abstract class
  • VarExpression: interpreter for variables
  • Symbol expression: an interpreter for a symbol (non terminal, i.e. it has subclasses)

Concrete implementation

// Abstract class expression: the value of each variable can be obtained through the key value pair of HashMap ֵ  Expression.java
public abstract class Expression {
	//Explain the relationship between formula and value: key is the formula (expression, such as a + b - c) parameter (such as a,b,c...)
	//Value is the specific value ֵ, That is, the values of various variables are stored in the final HashMap, such as HashMap {a=10, b=20}
	public abstract int interpreter(HashMap<String, Integer> var);
}

// Variable parser VarExpression.java
public class VarExpression extends Expression {
	private String key; // key=a,key=b,key=c

	public VarExpression(String key) {
		this.key = key;
	}

	// var gets {a=10, b=20}
	// interpreter function: returns the corresponding value according to the name of the variable ֵ
	@Override
	public int interpreter(HashMap<String, Integer> var) {
		return var.get(this.key);
	}
}

// SymbolExpression.java
/**
 * Abstract operation symbol parser:
 *  1)Each operation symbol is only related to its left and right numbers
 *  2)At the same time, the left and right numbers may also be the result of analysis
 *  3)No matter what type, it is the implementation class of Expression
 */
public class SymbolExpression extends Expression {
	protected Expression left;
	protected Expression right;

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

	//Because SymbolExpression is implemented by its subclasses, interpreter() is a default implementation
	@Override
	public int interpreter(HashMap<String, Integer> var) {
		return 0;
	}
}

//Addition interpreter addressexpression.java
public class AddExpression extends SymbolExpression  {
	public AddExpression(Expression left, Expression right) {
		super(left, right);
	}

	//Process "+"
	//var is {a=10,b=20...} (value key)
	public int interpreter(HashMap<String, Integer> var) {
		//super.left.interpreter(var): returns the value corresponding to the left expression. For example, the value corresponding to a is 10
		//super.right.interpreter(var): returns the value corresponding to the right expression. For example, the value corresponding to b is 20
		return super.left.interpreter(var) + super.right.interpreter(var);
	}
}

//Subtraction interpreter SubExpression.java
public class SubExpression extends SymbolExpression {
	public SubExpression(Expression left, Expression right) {
		super(left, right);
	}

	//Find the result of subtracting the left and right expressions
	public int interpreter(HashMap<String, Integer> var) {
		return super.left.interpreter(var) - super.right.interpreter(var);
	}
}

// Calculator.java
public class Calculator {
	//Define expression
	private Expression expression;

	//Constructor parameter passing and parsing
	public Calculator(String expStr) { //For example, the incoming expStr = a+b
		// Arrange operation sequence
		Stack<Expression> stack = new Stack<>();
		// Split expression into character arrays
		char[] charArray = expStr.toCharArray();//expStr split into [a, +, b]

		Expression left = null;
		Expression right = null;
		//Traverse the character array, that is, traverse [a, +, b], and do corresponding processing for different situations
		for (int i = 0; i < charArray.length; i++) {
			switch (charArray[i]) {
			case '+': 
				left = stack.pop();//Pop up left = > "a" from the stack first
				right = new VarExpression(String.valueOf(charArray[++i]));// Take out the left expression = > "B"
				stack.push(new AddExpression(left, right));// According to the obtained left and right objects, build addresson and add it to the stack
				break;
			case '-': // 
				left = stack.pop();
				right = new VarExpression(String.valueOf(charArray[++i]));
				stack.push(new SubExpression(left, right));
				break;
			default: 
				//If it is a VAR (variable), create a VarExpression object and push it to the stack
				stack.push(new VarExpression(String.valueOf(charArray[i])));
				break;
			}
		}
		//After traversing the entire charArray array, the final Expression is obtained in the stack
		this.expression = stack.pop();
	}

	public int run(HashMap<String, Integer> var) {
		//Bind the expression a+b to var: {a=10,b=20}
		//Then it is passed to the interpreter of expression for processing / interpretation execution
		return this.expression.interpreter(var);
	}
}

// ClientTest.java
public class ClientTest {
	public static void main(String[] args) throws IOException {
		String expStr = getExpStr(); // For example: a+b
		HashMap<String, Integer> var = getValue(expStr);// var: {a=10, b=20}
		Calculator calculator = new Calculator(expStr);
		System.out.println("Operation result:" + expStr + "=" + calculator.run(var));
	}

	//Get expression
	public static String getExpStr() throws IOException {
		System.out.print("Please enter an expression:");
		return (new BufferedReader(new InputStreamReader(System.in))).readLine();
	}

	//Get value mapping
	public static HashMap<String, Integer> getValue(String expStr) throws IOException {
		HashMap<String, Integer> map = new HashMap<>();

		for (char ch : expStr.toCharArray()) {
			if (ch != '+' && ch != '-') {
				if (!map.containsKey(String.valueOf(ch))) {
					System.out.print("Please enter" + String.valueOf(ch) + "Value of:");
					String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
					map.put(String.valueOf(ch), Integer.valueOf(in));
				}
			}
		}
		return map;
	}
}

4, Precautions and details

  • When a language needs interpretation and execution, the sentences in the language can be expressed as an abstract syntax tree, and the interpreter mode can be considered to make the program have good scalability
  • Application scenarios: compiler, operation expression calculation, regular expression, robot, etc
  • Possible problems with using the interpreter: the Interpreter pattern will cause class inflation; The interpreter mode adopts recursive call method, which will lead to very complex debugging and low efficiency. (the interpreter is generally used to solve complex problems and should be used with caution)

Keywords: Java Design Pattern

Added by dearsina on Fri, 10 Sep 2021 06:40:17 +0300