一,解释器模式的定义
解释器模式是一种行为型设计模式,它用于定义一个语言的文法并解析语言中的表达式,使开发者可以实现自定义语言,并通过解释器对语言中的表达式进行解析和执行。
解释器模式主要用于解决对特定表达式的解析与执行,它定义了一种解释器的语法规则,通过解释器来解析并执行自定义的语言表达式。
通过解释器,可以将复杂的语言解析问题拆分为简单的表达式解析,并且可以通过增加新的解释器对象来扩展语言的功能。
解释器模式在现实生活中的抽象实例:
自然语言解释器:比如翻译软件,可以将一种语言的句子解释为另一种语言。
数学表达式解析器:计算机程序中的数学表达式可以由解析器解释和计算。
正则表达式解析器:正则表达式是一种用于描述字符串模式的语言,正则表达式解析器可以解析正则表达式。
指令解释器:计算机程序中的指令集可以由解释器解释和执行,将指令转换为对计算机底层的操作。
二,解释器模式的结构
解释器模式主要包含以下组件:
1.抽象表达式(Abstract Expression):
定义解释器的接口,声明抽象的解释方法interpret()。
2.终结符表达式(Terminal Expression):
实现抽象表达式的interpret()方法,用于解析语言中的终结符表达式。
3.非终结符表达式(Nonterminal Expression):
实现抽象表达式的interpret()方法,用于解析语言中的非终结符表达式。非终结符表达式通常由多个终结符表达式组成,可以通过递归的方式进行解析。
4.上下文(Context):
包含解释器需要解析的数据,用于存储需要被解释器解析的上下文信息。
5.客户端(Client):
负责创建抽象表达式及具体表达式对象,并调用解释方法进行解析。
在解释器的工作过程中,从输入语言文本开始,解析器首先识别出终结符,然后递归处理它们之间的嵌套结构,直到整个语言表达式都被处理完毕。
解释器模式的关键成分:
终结符表达式(Terminal Expression):
终结符表达式是语法中的基本单元,它们通常是程序的最原子的成分,如数字、关键字、标识符等。终结符表达式的处理相对简单,通常由解释器直接处理并返回结果,在编码实现中,它们对应于语法解析树中的叶子节点。
非终结符表达式(Non-Terminal Expression):
非终结符表达式则代表了更复杂的语法结构,比如判断条件、循环等,它是通过组合其他终结符和非终结符构建而成的。非终结符表达式在解释器模式中通常映射到具体的抽象类或接口,其作用是在适当的时候递归地调用解析器去处理它的组成成分。当遇到非终结符时,解释器会创建一个新的实例,并调用对应的解析方法,这个方法会根据上下文做进一步的解析。
组件之间的工作步骤如下:
1.客户端创建上下文对象,并将待解析的语言表达式传入到上下文。
2.客户端创建终结符表达式和非终结符表达式对象,并根据解释器语法规则将它们组合成一个语法解析树。
3.客户端调用解释器的interpret()方法,依次对每个终结符和非终结符进行解释和执行。
4.解释器根据具体的语法规则对语言表达式进行解析,并执行相应的操作。
5.整个执行流程中,解释器可以将解析得到的值存储在上下文中,以供后续的解析使用。
对应UML类图:
三,解释器模式代码样例
Demo1:解析数学表达式
#include <iostream>
#include <string>
#include <stack>
#include <sstream>
class Expression {
public:
virtual int interpret() = 0;
};
class NumberExpression : public Expression {
public:
NumberExpression(int number) : m_number(number) {}
int interpret() override {
return m_number;
}
private:
int m_number;
};
class AddExpression : public Expression {
public:
AddExpression(Expression* left, Expression* right)
: m_left(left), m_right(right) {}
int interpret() override {
return m_left->interpret() + m_right->interpret();
}
private:
Expression* m_left;
Expression* m_right;
};
class Context {
public:
Expression* evaluate(const std::string& expression) {
std::stack<Expression*> stack;
std::stringstream ss(expression);
std::string token;
while (getline(ss, token, ' ')) {
if (token == "+") {
Expression* right = stack.top();
stack.pop();
Expression* left = stack.top();
stack.pop();
Expression* addExp = new AddExpression(left, right);
stack.push(addExp);
}
else {
int number = stoi(token);
Expression* numberExp = new NumberExpression(number);
stack.push(numberExp);
}
}
return stack.top();
}
};
int main() {
Context context;
Expression* expression = context.evaluate("2 3 +");
int result = expression->interpret();
std::cout << "Result: " << result << std::endl;
delete expression;
return 0;
}
运行结果:
Result: 5
Demo2:解析字符串
#include <iostream>
#include <string>
#include <vector>
class Expression {
public:
virtual bool interpret(const std::string& context) = 0;
};
class TerminalExpression : public Expression{
private:
std::string expression;
public:
TerminalExpression(const std::string& expression) : expression(expression) {}
bool interpret(const std::string& context) override {
if (context.find(expression) != std::string::npos) {
return true;
}
return false;
}
};
class OrExpression: public Expression {
private:
Expression* expr1;
Expression* expr2;
public:
OrExpression(Expression* expr1, Expression* expr2)
: expr1(expr1), expr2(expr2) {}
bool interpret(const std::string& context) override {
return expr1->interpret(context) || expr2->interpret(context);
}
};
int main() {
Expression* expr1 = new TerminalExpression("hello");
Expression* expr2 = new TerminalExpression("world");
Expression* orExpr = new OrExpression(expr1, expr2);
std::vector<std::string> contexts = { "hello", "world", "hello world", "hi" };
for (const auto& context: contexts) {
if (orExpr->interpret(context)) {
std::cout << "Expression is true for context: " << context << std::endl;
}
else {
std::cout << "Expression is false for context: " << context << std::endl;
}
}
delete expr1;
delete expr2;
delete orExpr;
return 0;
}
运行结果:
Expression is true for context: hello
Expression is true for context: world
Expression is true for context: hello world
Expression is false for context: hi
四,解释器模式的应用场景
脚本解析器:如JavaScript、Python等动态语言中,解释器模式用于读取源码并逐行解释执行,不需要预先编译。
数据处理:在数据分析或科学计算库(如NumPy)中,可以使用解释器模式处理复杂的数学表达式。
命令行工具:许多命令行工具会采用解释器模式来解析用户输入的命令及其参数,然后执行相应的操作。
特定描述语言(DSL):比如SQL解析器,将SQL语句转换为数据库操作指令。
五,解释器模式的优缺点
解释器模式的优点:
灵活可扩展,解释器可以动态地添加新的语言规则,这使得系统对新语言或方言的变化更具适应性。
结构清晰,通过一步步执行解析过程,帮助开发者理解并调试。
解释器模式将语法解析和业务逻辑分离,可独立维护。
解释器模式的缺点:
性能开销大,同为语法解析,解释器通常比编译器生成的机器码运行速度慢。
当需要处理高级语法时,代码复杂度很高。
可能会包含重复计算的场景,容易消耗系统资源。
六,代码实战
Demo1:模拟的科学计算库
#include <iostream>
#include <string>
class Expression {
public:
virtual double evaluate() = 0;
};
class Addition: public Expression {
private:
double left;
double right;
public:
Addition(double l, double r) : left(l), right(r) {}
double evaluate() override { return left + right; }
};
class Subtraction: public Expression {
private:
double left;
double right;
public:
Subtraction(double l, double r) : left(l), right(r) {}
double evaluate() override { return left - right; }
};
class Interpreter {
private:
Expression* expr;
public:
Interpreter(Expression* e) : expr(e) {}
double interpret() {
return expr->evaluate();
}
};
int main() {
Expression* addExpr = new Addition(3.0, 5.0);
Expression* subExpr = new Subtraction(10.0, 7.0);
Interpreter intp(addExpr);
std::cout << "Addition result: "<< intp.interpret() << std::endl;
Interpreter intp2(subExpr);
std::cout << "Subtraction result: "<< intp2.interpret() << std::endl;
delete addExpr;
delete subExpr;
return 0;
}
运行结果:
Addition result: 8
Subtraction result: 3
Demo2:模拟的汇编语言解析器
#include <iostream>
#include <string>
#include <unordered_map>
class Instruction {
public:
virtual void execute() const = 0;
};
class LoadInstruction: public Instruction {
private:
int value;
public:
LoadInstruction(int v): value(v) {}
void execute() const override {
std::cout << "Loading value: " << value << std::endl;
}
};
class StoreInstruction: public Instruction {
private:
int address;
public:
StoreInstruction(int addr) : address(addr) {}
void execute() const override {
std::cout << "Storing at address: " << address << std::endl;
}
};
class Interpreter {
private:
std::vector<Instruction*> instructions;
public:
void addInstruction(Instruction* instr) {
instructions.push_back(instr);
}
void interpret() {
for (auto& instr : instructions) {
instr->execute();
}
}
};
int main() {
Interpreter interpreter;
LoadInstruction loadInstr(5);
StoreInstruction storeInstr(10);
interpreter.addInstruction(&loadInstr);
interpreter.addInstruction(&storeInstr);
interpreter.interpret();
return 0;
}
运行结果:
Loading value: 5
Storing at address: 10
Demo3:模拟的shell脚本解析器
#include <iostream>
#include <string>
#include <map>
class Command {
public:
virtual ~Command() {}
virtual void execute() = 0;
};
class CDCommand: public Command {
private:
std::string directory;
public:
CDCommand(const std::string& dir) : directory(dir) {}
void execute() override {
std::cout << "Changing directory to " << directory << std::endl;
}
};
class LSCommand: public Command {
public:
void execute() override {
std::cout << "Listing files in current directory\n";
}
};
class EchoCommand: public Command {
private:
std::string message;
public:
EchoCommand(const std::string& msg) : message(msg) {}
void execute() override {
std::cout << "Echoing: " << message << "\n";
}
};
class Interpreter {
private:
std::map<std::string, Command*> command_map;
public:
void add_command(const std::string& name, Command* cmd) {
command_map[name] = cmd;
}
void interpret(const std::string& cmd_str) {
if (command_map.find(cmd_str) != command_map.end()) {
command_map[cmd_str]->execute();
}
else {
std::cerr << "Unknown command: " << cmd_str << '\n';
}
}
};
int main() {
Interpreter interpreter;
interpreter.add_command("cd", new CDCommand("/path/to/directory"));
interpreter.add_command("ls", new LSCommand());
interpreter.add_command("echo", new EchoCommand("Hello, World!"));
interpreter.interpret("cd /home");
interpreter.interpret("ls");
interpreter.interpret("echo hi");
return 0;
}
运行结果:
Unknown command: cd /home
Listing files in current directory
Unknown command: echo hi
七,参考阅读
https://blog.gmem.cc/interpreter-pattern
https://www.geeksforgeeks.org/interpreter-design-pattern/
https://www.tutorialspoint.com/design_pattern/interpreter_pattern.html
https://incusdata.com/blog/design-patterns-interpreter-pattern-part1
https://sourcemaking.com/design_patterns/interpreter/cpp/
https://mcturra2000.wordpress.com/2017/08/24/a-simple-basic-interpreter-in-c/