設計模式學習筆記(二十二)解釋器模式及其實現

来源:https://www.cnblogs.com/EthanWong/archive/2022/04/12/16135855.html
-Advertisement-
Play Games

解釋器模式(Interpreter Design Pattern)指給定一個“語言”,定義它的文法的一種表示,並定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。這裡所指的“語言”是指使用規定格式和語法的代碼。 比如說在計算器中,我們輸入一個加法/減法表達式(中綴表達式)“1+6-5”字元串, ...


解釋器模式(Interpreter Design Pattern)指給定一個“語言”,定義它的文法的一種表示,並定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。這裡所指的“語言”是指使用規定格式和語法的代碼。

比如說在計算器中,我們輸入一個加法/減法表達式(中綴表達式)“1+6-5”字元串,就能在計算器中輸出結果2。而我們知道,一般的程式語言像C++JavaPython無法直接解釋這個字元串,那麼就必須定義規定格式和語法的解釋器,來對這個字元串表達式進行解釋成程式語言能計算的方式(尾碼表達式),最後再進行輸出。也就是中綴表達式轉換成尾碼表達式。

image-20220412102618569

那麼在著這個轉換中就需要滿足這樣的語法:

首先 依次 遍歷中綴表達式,

如果是數字 直接輸出

如果是符號: 

​		左括弧, 直接進棧

​		運算符, 與棧頂元素 進行比較如果  棧頂元素 優先順序較低, 直接入棧即可。

​						如果棧頂元素 優先順序較高,將棧頂元素 彈出 並輸出。 之後進棧即可。

​		右括弧, 將棧中的元素依次彈出 直到遇到左括弧。

遍歷結束後,如果棧中還有元素依次彈出並輸出 即可。 

一、解釋器模式介紹

在介紹解釋器模式的結構前,先來瞭解兩個概念文法規則和抽象語法樹

1.1 文法和抽象語法樹

1.1.1 文法

文法也就是用於描述語言的語法結構,比如對於上面提到的表達式“1+6-5”,可以使用一下文法規則來定義:

# 表達式的組成方式,value和operation是兩個語言構造成分或語言單位
expression :: = value | operation
# 非終結表達式,操作符的種類這裡定義兩種’+‘和’-‘
operation :: = expression '+' expression | expression '-' expression
# 終結表達式,組成元素是最基本的語言單位,這裡指像1、6等的整數值
value :: = an integer
  • 符號expression是表達式的組成方式,其中valueoperation是後面兩個語言單位的定義
  • 符號::=是定義為的意思
  • 語言單位分為終結符表達式和非終結表達式,value是終結符表達式不可再分,operation是非終結符表達式,可以再分

1.1.2 抽象語法樹

除了使用文法規則來定義語言外,在解釋器模式中還可以通過抽象語法樹(Abstract Syntax Tree, AST)圖形的方式來直觀地表示語言構成,比如“1+6-5”可以用語法樹表達為:

image-20220412111705957

1.2 解釋器模式的結構

從上面的文法規則可以知道,表達式可分為終結符表達式和非終結符表達式,因此解釋器模式的結構與組合模式的結構類似,它的結構類圖如下所示:

image-20220412122703139

  • AbstractExpression:抽象表達式,聲明抽象的解釋操作,終結符表達式和非終結符表達式的公共介面
  • TerminalExpression:終結符表達式,實現抽象表達式以及相關的解釋操作
  • NonterminalExpression:非終結符表達式,實現抽象表達式的相關解釋操作。其中既可以包含終結符表達式,也可以包含非終結符表達式
  • Context:上下文類,用於存儲解釋器外的一些全局信息
  • Client:客戶端

1.3 解釋器模式的實現

根據上面的類圖,首先來看一下終結符表達式和非終結符表達式的公共介面抽象表達式

public interface AbstractExpression {
    
	void interpret(Context ctx);
}

接下來是終結符表達式和非終結符表達式類

public class TerminalExpression implements AbstractExpression{

    @Override
    public void interpret(Context context) {
        System.out.println("對終結符表達式進行處理");
    }
}
public class NonterminalExpression implements AbstractExpression{

    private AbstractExpression left;
    private AbstractExpression right;

    public NonterminalExpression(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public void interpret(Context context) {
        System.out.println("非終結符表達式進行處理中~");
        //遞歸調用每一個組成部分的 interpret()方法

    }
}

最後是上下文類(Context):

public class Context {
    private Map<String, String> contextMap = new HashMap<String, String>();

    public void assign(String key, String value) {
        //向上下文Map中設置值
        contextMap.put(key, value);
    }

    public String lookup(String key) {
        return contextMap.get(key);
    }

}

二、解釋器模式應用場景

在下列的情況可以考慮使用解釋器模式:

  1. 可以將一個需要解釋執行的語言中的句子表示為一顆抽象語法樹
  2. 一些重覆出現的問題可以用一種簡單的語言進行表達
  3. 一個語言的文法較為簡單,對於負責的文法,解釋器模式中的文法類層次結構將變得很龐大而無法管理,此時最好的方式是使用語法分析程式生成器

三、解釋器模式實戰

本案例中模擬監控業務系統的運行情況,及時將異常報告發送給開發者,比如,如果每分鐘介面出錯數超過100,監控系統就通過簡訊、微信、郵件等方式發送告警給開發者。(案例來源於《設計模式之美》)

首先設置一個告警規則:每分鐘API總錯數超過100或者每分鐘API總調用數超過10000就觸發告警

api_error_per_minute > 100 || api_count_per_minute > 10000

我們定義告警的判斷規則有五種:||、&&、<、>、==,其中<、>、==運算符的優先順序高於||和&&

image-20220412162955921

代碼的結構如下:

src
  ├─main
  │  ├─java
  │    │  AlertRuleInterpreter.java
  │    │
  │    └─expression
  │        │  Expression.java
  │        │
  │        └─impl
  │            	AndExpression.java
  │            	EqualExpression.java
  │            	GreaterExpression.java
  │            	LesserExpression.java
  │            	OrExpression.java
  │  
  └─test
      └─java
           ApiTest.java

具體代碼

  1. 抽象告警規則介面
public interface Expression {

    boolean interpret(Map<String, Long> stats);
}
  1. 具體告警規則實現

分別有||、&&、<、>、==五種運算符判斷規則

public class OrExpression implements Expression {

    private List<Expression> expressions = new ArrayList<>();

    public OrExpression(List<Expression> expressions) {
        this.expressions.addAll(expressions);
    }

    public OrExpression(String strOrExpression) {
        String[] andExpressions = strOrExpression.split("\\|\\|");
        for (String andExpression : andExpressions) {
            expressions.add(new AndExpression(andExpression));
        }
    }

    @Override
    public boolean interpret(Map<String, Long> stats) {
        for (Expression expression : expressions) {
            if (expression.interpret(stats)) {
                return true;
            }
        }
        return false;
    }
}
public class AndExpression implements Expression {

    private List<Expression> expressions = new ArrayList<>();

    public AndExpression(List<Expression> expressions) {
        this.expressions.addAll(expressions);
    }

    public AndExpression(String strAndExpression) {
        String[] strExpressions = strAndExpression.split("&&");
        for (String strExpression : strExpressions) {
            if (strExpression.contains(">")) {
                expressions.add(new GreaterExpression(strExpression));
            } else if (strExpression.contains("<")) {
                expressions.add(new LesserExpression(strExpression));
            } else if (strAndExpression.contains("==")) {
                expressions.add(new EqualExpression(strExpression));
            } else {
                throw new RuntimeException("Expression is invalid: " + strAndExpression);
            }
        }
    }

    @Override
    public boolean interpret(Map<String, Long> stats) {
        for (Expression expression : expressions) {
            if (!expression.interpret(stats)) {
                return false;
            }
        }
        return true;
    }
}
public class EqualExpression implements Expression {

    private String key;
    private Long value;

    public EqualExpression(String key, Long value) {
        this.key = key;
        this.value = value;
    }

    public EqualExpression(String strExpression) {
        String[] elements = strExpression.trim().split("\\s+");
        if (elements.length != 3 || !elements[1].trim().equals("==")) {
            throw new RuntimeException("Expression is invalid: " + strExpression);
        }
        this.key = elements[0].trim();
        this.value = Long.parseLong(elements[2].trim());
    }

    @Override
    public boolean interpret(Map<String, Long> stats) {
        if (!stats.containsKey(key)) {
            return false;
        }
        Long statsValue = stats.get(key);
        return statsValue == value;
    }
}
public class GreaterExpression implements Expression {

    private String key;
    private long value;

    public GreaterExpression(String key, long value) {
        this.key = key;
        this.value = value;
    }

    public GreaterExpression(String strExpression) {
        String[] elements = strExpression.trim().split("\\s+");
        if (elements.length != 3 || !elements[1].trim().equals(">")) {
            throw new RuntimeException("Expression is invalid: " + strExpression);
        }
        this.key = elements[0].trim();
        this.value = Long.parseLong(elements[2].trim());
    }

    @Override
    public boolean interpret(Map<String, Long> stats) {
        if (!stats.containsKey(key)) {
            return false;
        }
        Long statValue = stats.get(key);
        return statValue > value;
    }
}
public class LesserExpression implements Expression {

    private String key;
    private long value;

    public LesserExpression(String key, long value) {
        this.key = key;
        this.value = value;
    }

    public LesserExpression(String strExpression) {
        String[] elements = strExpression.trim().split("\\s+");
        if (elements.length != 3 || !elements[1].trim().equals("<")) {
            throw new RuntimeException("Expression is invalid: " + strExpression);
        }
        this.key = elements[0].trim();
        this.value = Long.parseLong(elements[2].trim());
    }

    @Override
    public boolean interpret(Map<String, Long> stats) {
        if (!stats.containsKey(key)) {
            return false;
        }
        Long statsValue = stats.get(key);
        return statsValue < value;
    }
}
  1. 告警規則解釋器

負責解釋並實現告警規則

public class AlertRuleInterpreter {

    private Expression expression;

    public AlertRuleInterpreter(String ruleExpression) {
        this.expression = new OrExpression(ruleExpression);
    }

    public boolean interpret(Map<String, Long> stats) {
        return expression.interpret(stats);
    }
}
  1. 測試類及結果
public class ApiTest {

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    @Test
    public void test() {
        String rule = "api_error_per_minute > 100 || api_count_per_minute > 10000";
        AlertRuleInterpreter alertRuleInterpreter = new AlertRuleInterpreter(rule);
        HashMap<String, Long> statsMap = new HashMap<>();
        statsMap.put("api_error_per_minute", 99l);
        statsMap.put("api_count_per_minute", 121l);
        boolean alertInterpret = alertRuleInterpreter.interpret(statsMap);
        String alert = alertInterpret == true ? "超過閾值,危險!!" : "目前運行良好";
        logger.info("預警結果為:alert:{}", alert);

    }
}

最後的測試結果為:

16:18:14.525 [main] INFO  ApiTest - 預警結果為:alert:目前運行良好

參考資料

《設計模式之美》

《Java設計模式》

《設計模式》

http://c.biancheng.net/view/1402.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 才學習Vue.js,跟著b站視頻學習,視頻中用到了Vue.js的Chrome調試插件——Vue.js devtools,我下載的是官方正式版,其他版本不一定適合本教程。 首先,有關安裝Vue.js devtools不能用的問題,我在這裡簡單過一下,因為很容易百度到解決辦法。 Chrome安裝好Vue ...
  • 今天是node學習的第二天,其實越往後面學越感覺有點熟悉的味道了,光針對於node來說哈,為什麼呢,因為我之前學過一點雲計算的東西,當時感覺沒什麼用搞了下伺服器客戶端這些,沒想到這裡還能用一用,至少看到伺服器這些概念一點不陌生,看到npm一點不奇怪,我當時用的都是yum。 1. 我們今天先看到htt ...
  • 當我們給一個元素的一系列子元素設置display: inline-block; 時,會發現子元素之間存在間隙,如 <style> div { display: inline-block; width: 100px; height: 100px; background-color: yellow; } ...
  • 初識JavaScript 瀏覽器執行js簡介 渲染引擎:解析css和html js引擎:編譯js代碼,逐行解析 <script type="null"> alert('我是編程語言,用來控制電腦網頁彈出你好'); alert('我是編程語言,用來控制電腦網頁彈出你好'); </script> js組 ...
  • 一、安裝node.js 參考教程:https://www.runoob.com/nodejs/nodejs-install-setup.html 中文官網:https://nodejs.org/zh-cn/download/ 歷史版本:https://nodejs.org/zh-cn/downloa ...
  • 之前在公眾號轉發了好友 Vajoy 的一篇文章 -- 巧用 CSS 把圖片馬賽克風格化。 核心是利用了 CSS 中一個很有意思的屬性 -- image-rendering,它可以用於設置圖像縮放演算法。 何為 image-rendering? CSS 屬性 image-rendering 用於設置圖像 ...
  • 模板方法模式是什麼 模版方法模式是設計模式中的行為型的一種模式,它在基類中定義了一個演算法的框架,允許子類在不修改結構的情況下重寫演算法的特定步驟。 為什麼要用模板方法模式 模板方法將整個演算法轉換為一系列獨立的步驟,以便子類能對其進行擴展,同時還可讓超類中所定義的結構保持完整。或者當多個類的演算法步驟一致 ...
  • 一、約定編程 Spring AOP是一種約定流程的編程,咱們可以先通過動態代理模式的實現來理解Spring AOP的概念。 代理的邏輯很簡單,例如,當你需要採訪一名兒童時,首先需要經過他父母的同意,在一些問題上父母也許會替他回答,而對於另一些問題,也許父母覺得不太適合這個小孩會拒絕掉,顯然這時父母就 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...