行為型設計模式:解釋器模式

解釋器模式

解釋器模式介紹

解釋器模式使用頻率不算高,通常用來描述如何構建一個簡單“語言”的語法解釋器。它只在一些非常特定的領域被用到,比如編譯器、規則引擎、正則表達式、SQL 解析等。不過,了解它的實現原理同樣很重要,能幫助你思考如何通過更簡潔的規則來表示復雜的邏輯。

解釋器模式(Interpreter pattern)原始定義:用于定義語言的語法規則表示,并提供解釋器來處理句子中的語法。

我們通過一個例子給大家解釋一下解釋器模式。

  • 假設我們設計一個軟件用來進行加減計算。我們第一想法就是使用工具類,提供對應的加法和減法的工具方法。
//用于兩個整數相加的方法
public static int add(int a , int  b){return a + b;
}//用于三個整數相加的方法
public static int add(int a , int  b,int c){return a + b + c;
}public static int add(Integer ... arr){int sum = 0;for(Integer num : arr){sum += num;}return sum;
}+ - 

上面的形式比較單一、有限,然而現實生活中情況就比較復雜,形式變化非常多,這就不符合要求,因為加法和減法運算,兩個運算符與數值可以有無限種組合方式。比如: 5-3+2-1, 10-5+20…

文法規則和抽象語法樹

解釋器模式描述了如何為簡單的語言定義一個文法,如何在該語言中表示一個句子,以及如何解釋這些句子。

在上面提到的加法/減法解釋器中,每一個輸入表達式(比如:2+3+4-5) 都包含了3個語言單位,可以使用下面的文法規則定義:

文法是用于描述語言的語法結構的形式規則。

expression ::= value | plus | minus 
plus ::= expression ‘+’ expression   
minus ::= expression ‘-’ expression  
value ::= integer

注意: 這里的符號“::=”表示“定義為”的意思,豎線 | 表示或,左右的其中一個,引號內為字符本身,引號外為語法。

上面規則描述為 :表達式可以是一個值,也可以是 plus 或者 minus 運算,而 plus 和 minus 又是由表達式結合運算符構成,值的類型為整型數。

抽象語法樹:

在解釋器模式中還可以通過一種稱為抽象語法樹的圖形方式來直觀的表示語言的構成,每一棵抽象語法樹對應一個語言實例,例如加法/減法表達式語言中的語句 " 1+ 2 + 3 - 4 + 1" 可以通過下面的抽象語法樹表示
在這里插入圖片描述

解釋器模式原理

接下來我們畫個UML類圖,通過該類圖來了解下該模式的原理。

在這里插入圖片描述

從上述UML類圖中,看到解釋器模式包含以下幾種主要角色。

  • 抽象表達式(Abstract Expression)角色:定義解釋器的接口,約定解釋器的解釋操作,主要包含解釋方法 interpret()。
  • 終結符表達式(Terminal Expression)角色:抽象表達式的子類,用來實現文法中與終結符相關的操作,文法中的每一個終結符都有一個具體終結表達式與之相對應。上例中的value 是終結符表達式.
  • 非終結符表達式(Nonterminal Expression)角色:也是抽象表達式的子類,用來實現文法中與非終結符相關的操作,文法中的每條規則都對應于一個非終結符表達式。上例中的 plus , minus 都是非終結符表達式
  • 環境(Context)角色:通常包含各個解釋器需要的數據或是公共的功能,一般用來傳遞被所有解釋器共享的數據,后面的解釋器可以從這里獲取這些值。
  • 客戶端(Client):主要任務是將需要分析的句子或表達式轉換成使用解釋器對象描述的抽象語法樹,然后調用解釋器的解釋方法,當然也可以通過環境角色間接訪問解釋器的解釋方法。

解釋器模式實現

為了更好的給大家解釋一下解釋器模式,我們來定義了一個進行加減乘除計算的“語言”,語法規則如下:

  • 運算符只包含加、減、乘、除,并且沒有優先級的概念;
  • 表達式中,先書寫數字,后書寫運算符,空格隔開;

我們舉個例子來解釋一下上面的語法規則:

  • 比如“ 9 5 7 3 - + * ”這樣一個表達式,我們按照上面的語法規則來處理,取出數字 “9、5”“-” 運算符,計算得到 4,于是表達式就變成了“ 4 7 3 + * ”。然后,我們再取出“4 7”和“ + ”運算符,計算得到 11,表達式就變成了“ 11 3 * ”。最后,我們取出“ 11 3”和“ * ”運算符,最終得到的結果就是 33。

代碼示例:

  • 用戶按照上面的規則書寫表達式,傳遞給 interpret() 函數,就可以得到最終的計算結果。
/*** 表達式解釋器類**/
public class ExpressionInterpreter {//Deque雙向隊列,可以從隊列的兩端增加或者刪除元素private Deque<Long>  numbers = new LinkedList<>();//接收表達式進行解析public long interpret(String expression){String[] elements = expression.split(" ");int length = elements.length;//獲取表達式中的數字for (int i = 0; i < (length+1)/2; ++i) {//在 Deque的尾部添加元素numbers.addLast(Long.parseLong(elements[i]));}//獲取表達式中的符號for (int i = (length+1)/2; i < length; ++i) {String operator = elements[i];//符號必須是 + - * / 否則拋出異常boolean isValid = "+".equals(operator) || "-".equals(operator)|| "*".equals(operator) || "/".equals(operator);if (!isValid) {throw new RuntimeException("Expression is invalid: " + expression);}//pollFirst()方法, 移除Deque中的第一個元素,并返回被移除的值long number1 = numbers.pollFirst(); //數字long number2 = numbers.pollFirst();long result = 0;  //運算結果//對number1和number2進行運算if (operator.equals("+")) {result = number1 + number2;} else if (operator.equals("-")) {result = number1 - number2;} else if (operator.equals("*")) {result = number1 * number2;} else if (operator.equals("/")) {result = number1 / number2;}//將運算結果添加到集合頭部numbers.addFirst(result);}//運算完成numbers中應該保存著運算結果,否則是無效表達式if (numbers.size() != 1) {throw new RuntimeException("Expression is invalid: " + expression);}//移除Deque的第一個元素,并返回return numbers.pop();}
}

代碼重構

上面代碼的所有的解析邏輯都耦合在一個函數中,這樣顯然是不合適的。這時候,我們就要考慮拆分代碼進行優化了,將解析邏輯拆分到獨立的小類中, 前面定義的語法規則有兩類表達式,一類是數字,一類是運算符,運算符又包括加減乘除。 利用解釋器模式,我們把解析的工作拆分到以下五個類:num、plu、sub、mul和div

  • NumExpression
  • PluExpression
  • SubExpression
  • MulExpression
  • DivExpression
/*** 表達式接口**/
public interface Expression {long interpret();
}/*** 數字表達式**/
public class NumExpression implements Expression {private long number;public NumExpression(long number) {this.number = number;}public NumExpression(String number) {this.number = Long.parseLong(number);}@Overridepublic long interpret() {return this.number;}
}/*** 加法運算**/
public class PluExpression implements Expression{private Expression exp1;private Expression exp2;public PluExpression(Expression exp1, Expression exp2) {this.exp1 = exp1;this.exp2 = exp2;}@Overridepublic long interpret() {return exp1.interpret() + exp2.interpret();}
}/*** 減法運算**/
public class SubExpression implements Expression {private Expression exp1;private Expression exp2;public SubExpression(Expression exp1, Expression exp2) {this.exp1 = exp1;this.exp2 = exp2;}@Overridepublic long interpret() {return exp1.interpret() - exp2.interpret();}
}/*** 乘法運算**/
public class MulExpression implements Expression {private Expression exp1;private Expression exp2;public MulExpression(Expression exp1, Expression exp2) {this.exp1 = exp1;this.exp2 = exp2;}@Overridepublic long interpret() {return exp1.interpret() * exp2.interpret();}
}/*** 除法**/
public class DivExpression implements Expression {private Expression exp1;private Expression exp2;public DivExpression(Expression exp1, Expression exp2) {this.exp1 = exp1;this.exp2 = exp2;}@Overridepublic long interpret() {return exp1.interpret() / exp2.interpret();}
}//測試
public class Test01 {public static void main(String[] args) {ExpressionInterpreter e = new ExpressionInterpreter();long result = e.interpret("6 2 3 2 4 / - + *");System.out.println(result);}
}

上面的代碼看起來是不是清晰多了,有空的家人也去動手試試吧!

解釋器模式總結

1) 解釋器優點

  • 易于改變和擴展文法

    因為在解釋器模式中使用類來表示語言的文法規則的,因此就可以通過繼承等機制改變或者擴展文法。每一個文法規則都可以表示為一個類,因此我們可以快速的實現一個迷你的語言

  • 實現文法比較容易

    在抽象語法樹中每一個表達式節點類的實現方式都是相似的,這些類的代碼編寫都不會特別復雜

  • 增加新的解釋表達式比較方便

    如果用戶需要增加新的解釋表達式,只需要對應增加一個新的表達式類就可以了。原有的表達式類不需要修改,符合開閉原則

2) 解釋器缺點

  • 對于復雜文法難以維護

    在解釋器中一條規則至少要定義一個類,因此一個語言中如果有太多的文法規則,就會使類的個數急劇增加,當值系統的維護難以管理.

  • 執行效率低

    在解釋器模式中大量的使用了循環和遞歸調用,所有復雜的句子執行起來,整個過程也是非常的繁瑣

3) 使用場景

  • 當語言的文法比較簡單,并且執行效率不是關鍵問題.
  • 當問題重復出現,且可以用一種簡單的語言來進行表達
  • 當一個語言需要解釋執行,并且語言中的句子可以表示為一個抽象的語法樹的時候

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/92010.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/92010.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/92010.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SaTokenException: 未能獲取對應StpLogic 問題解決

&#x1f4dd; Sa-Token 異常處&#xff1a;未能獲取對應StpLogic&#xff0c;typeuser&#x1f9e8; 異常信息 cn.dev33.satoken.exception.SaTokenException: 未能獲取對應StpLogic&#xff0c;typeuser拋出位置&#xff1a; throw new SaTokenException("未能獲取對應S…

Web前端性能優化原理與方法

一、概述 1.1 性能對業務的影響 大部分網站的作用是&#xff1a;產品信息載體、用戶交互工具或商品流通渠道。這就要求網站與更多用戶建立聯系&#xff0c;同時還要保持良好的用戶黏性&#xff0c;所以網站就不能只關注自我表達&#xff0c;而不顧及用戶是否喜歡。看看網站性…

第十八節:第六部分:java高級:注解、自定義注解、元注解

認識注解自定義注解注解的原理元注解常用的兩個元注解代碼&#xff1a; MyTest1&#xff08;注解類&#xff09; package com.itheima.day10_annotation;import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Retent…

北京科技企業在軟文推廣發稿平臺發布文章,如何精準觸達客戶?

大家好&#xff01;我是你們的老朋友&#xff0c;今天咱們聊聊北京科技企業如何通過軟文推廣發稿平臺精準觸達目標客戶這個話題。作為企業營銷的老司機&#xff0c;我深知在這個信息爆炸的時代&#xff0c;如何讓你的品牌聲音被目標客戶聽到是多么重要。下面就讓我來分享一些實…

UE蒙太奇和動畫序列有什么區別?

在 UE5 中&#xff0c;Animation Sequence&#xff08;動畫序列&#xff09;和 Animation Montage&#xff08;動畫蒙太奇&#xff09;雖然都能播放骨骼動畫&#xff0c;但它們的定位、功能和使用場景有較大區別&#xff1a;1. 概念定位Animation Sequence&#xff08;動畫序列…

Nordic打印RTT[屏蔽打印中的<info> app]

屏蔽打印中的 app Nordic原裝的程序答應是這樣的,這個有" app"打印,因為習慣問題,有時候也不想打印太多造成RTT VIEW顯示被沖點,所以要把" app"去掉:這里把prefix_process函數調用屏蔽到,主要涉及到nrf_log_hexdump_entry_process和nrf_log_std_entry_proc…

Python基礎和高級【抽取復習】

1.Python 的深拷貝和淺拷貝有什么區別&#xff1f; 淺拷貝【ls.copy()】&#xff1a; 將列表的不可變對象【值】復制一份&#xff0c;同時引用其中的可變對象【列表】&#xff0c;共用一個內存地址 深拷貝【lscopy.deepcopy(list)】&#xff1a; 完全的復制原可變對象&#xff…

TinyPiXOS組件開發(一):開發規范、組件開發方法介紹,快速上手組件開發,創造各種有趣的UI組件!

本文將通過實現一個點擊切換進度的電量指示燈組件和exampleGUI組件庫介紹如何基于TinyPiXOS開發新組件。主要內容包括組件開發規范、自定義組件開發和組件庫開發三部分。 組件開發規范 命名規范 采用tp開頭命名組件類&#xff0c;名稱具備易讀性。 目錄規范 頭文件放置 in…

主流熔斷方案選型指南

主流熔斷方案選型1. Netflix Hystrix (經典但已停止維護)適用場景&#xff1a;傳統Spring Cloud項目&#xff0c;需要快速集成熔斷功能優點&#xff1a;成熟穩定&#xff0c;社區資源豐富與Spring Cloud Netflix套件無縫集成提供熔斷、降級、隔離等完整功能缺點&#xff1a;已停…

Django中get()與filter()對比

在 Django 中&#xff0c;get() 和 filter() 是 QuerySet API 中用于檢索數據的兩個核心方法&#xff0c;它們的功能和使用場景有明顯區別。以下是詳細對比&#xff1a; 1. 核心區別特性get()filter()返回值單個對象&#xff08;模型實例&#xff09;查詢集&#xff08;QuerySe…

MySQL鎖(一) 概述與分類

1.1 MySQL鎖的由來 客戶端發往 MySQL 的一條條 SQL 語句&#xff0c;實際上都可以理解成一個個單獨的事務&#xff08;一條sql語句默認就是一個事務&#xff09;。而事務是基于數據庫連接的&#xff0c;每個數據庫連接在 MySQL 中&#xff0c;又會用一條工作線程來維護&#x…

PyTorch里的張量及張量的操作

張量的簡介 張量是多重線性映射在給定基下的坐標表示&#xff0c;可視為向量和矩陣的泛化。 0 維張量&#xff1a;標量&#xff08;如 5&#xff09;1 維張量&#xff1a;向量&#xff08;如 [1, 2, 3]&#xff09;2 維張量&#xff1a;矩陣&#xff08;如 [[1, 2], [3, 4]]&…

向量數據庫Faiss vs Qdrant全面對比

Faiss vs Qdrant 全面對比表 向量數據庫是一種相對較新的方式,用于與來自不透明機器學習模型(如深度學習架構)派生的抽象數據表示進行交互。這些表示通常被稱為向量或嵌入(embeddings),它們是用于訓練機器學習模型完成諸如情感分析、語音識別、目標檢測等任務的數據的壓…

2025年AIR SCI1區TOP,縮減因子分數階蜣螂優化算法FORDBO,深度解析+性能實測

目錄1.摘要2.蜣螂優化算法DBO原理3.改進策略4.結果展示5.參考文獻6.代碼獲取7.算法輔導應用定制讀者交流1.摘要 傳統DBO存在探索與開發能力失衡、求解精度低以及易陷入局部最優等問題。因此&#xff0c;本文提出了帶有縮減因子分數階蜣螂優化算法&#xff08;FORDBO&#xff0…

爬蟲逆向之JS混淆案例(全國招標公告公示搜索引擎 type__1017逆向)

案例https://ctbpsp.com/#/ 截至2025.07.19可用 定位加密位置 加密位置&#xff1a; 定位方式&#xff0c;XHR&#xff0c;跟棧 跟棧 QL打斷點&#xff0c;重新斷住 分析為&#xff0c;一個函數傳入四個參數 var QL QI[d9(Nv.mQ)](QJ, Qh, Qv, this[d9(Nv.m9)][0xa1a * …

Hive常用命令總結

一、數據庫操作 -- 創建數據庫&#xff08;默認路徑&#xff09; CREATE DATABASE IF NOT EXISTS myhive;-- 指定路徑創建數據庫 CREATE DATABASE myhive2 LOCATION /myhive2;-- 查看數據庫信息 DESC DATABASE myhive;-- 刪除數據庫&#xff08;強制刪除表&#xff09; DROP DA…

Spring整合MyBatis詳解

Spring整合MyBatis詳解一、整合優勢與核心思路1.1 整合優勢1.2 核心整合思路二、環境搭建與依賴配置2.1 開發環境2.2 Maven依賴配置三、整合配置&#xff08;核心步驟&#xff09;3.1 數據庫配置文件&#xff08;db.properties&#xff09;3.2 Spring配置文件&#xff08;sprin…

Windows CMD(命令提示符)中最常用的命令匯總和實戰示例

CMD命令匯總 下面是 Windows CMD&#xff08;命令提示符&#xff09;中最常用的命令匯總&#xff0c;共 30 個&#xff0c;包含說明和典型代碼示例&#xff0c;適合日常開發、系統操作、文件管理、網絡診斷等場景。一、文件與目錄操作&#xff08;最常用&#xff09;命令說明示…

嵌入式硬件篇---舵機(示波器)

舵機是一種高精度的角度控制執行器件&#xff0c;廣泛應用于機器人、航模、自動化設備等領域。其核心特點是能通過控制信號精準定位到特定角度&#xff08;通常范圍為 0-180&#xff0c;部分可到 360 連續旋轉&#xff09;。常見的舵機類型可根據結構、控制方式、用途等維度劃分…

嵌入式硬件篇---按鍵

按鍵是電子系統中最基礎的人機交互部件&#xff0c;通過機械或電子方式實現電路通斷或狀態切換。根據結構和工作原理的不同&#xff0c;常見按鍵可分為機械按鍵、薄膜按鍵、觸摸按鍵等&#xff0c;以下詳細介紹其工作原理、應用場景及電路特點&#xff1a;一、機械按鍵&#xf…