动态

详情 返回 返回

掌握設計模式--解釋器模式 - 动态 详情

解釋器模式(Interpreter Pattern)

解釋器模式(Interpreter Pattern)是一種行為型設計模式,用於定義一種語言的文法表示,並提供一個解釋器來解釋該語言中的句子。這種模式通常用於開發需要解析、解釋和執行特定語言或表達式的應用程序。

主要目的是為特定類型的問題定義一種語言,然後用該語言的解釋器來解決問題。

主要組成部分

解釋器模式的結構通常包括以下幾個部分:

  1. 抽象表達式(AbstractExpression) :定義解釋操作的接口。

  2. 終結符表達式(TerminalExpression) :表示語言中的基本元素,如數字或變量。

  3. 非終結符表達式(NonTerminalExpression):表示更復雜的語法規則,通過組合終結符表達式和其他非終結符表達式實現。

  4. 上下文(Context):存儲解釋器在解析表達式時需要的全局信息,比如變量值或共享數據。

  5. 客户端(Client):構建(或從外部獲取)需要解析的表達式,並使用解釋器處理表達式。

區分終結符和非終結符主要看它是不是最終的輸出,是不是不可再分的組成部分。

案例實現

設計一個動態查詢SQL 的解析器,查詢SQL模板 + 輸入的參數 動態的生成所需要的查詢SQL 。

本案例的主要功能

  1. 支持佔位符替換:如 #{key} 替換為參數值。
  2. 支持動態條件解析:如 <if> 標籤根據條件決定是否生成部分 SQL。
  3. 支持集合操作:如 <foreach> 動態生成 IN 子句。
  4. 使用解釋器模式解析查詢SQL 模板,分離模板的不同語義塊。

案例類圖

image

類圖簡述

  1. 上下文 (Context) :存儲輸入參數,供解釋器在解析時訪問。

  2. 抽象表達式 (SQLExpression) :表示 SQL 模板中的一個語義塊,定義 interpret 方法解析該塊,參數為Context輸入參數。

  3. 終結符表達式

  • 文本表達式(TextExpression):不可再分的文本部分(如靜態 SQL 片段);

  • 佔位符表達式 (PlaceholderExpression):解析並替換 #{key}

  1. 非終結符表達式
  • 條件組表達式(ConditionalGroupExpression):解析<where> 標籤中的一組條件;
  • 條件表達式 (IfExpression):解析 <if> 標籤中的動態 SQL;
  • 集合表達式 (ForEachExpression):解析 <foreach> 動態生成 SQL;
  • 複合表達式(CompositeExpression):將多個表達式組合成一個整體。

上下文

存儲動態 SQL 的參數,供解釋器在解析時訪問。

public class Context {
    private Map<String, Object> parameters;

    public Context(Map<String, Object> parameters) {
        this.parameters = parameters;
    }

    public Object getParameter(String key) {
        return parameters.get(key);
    }
}

抽象表達式

表示 SQL 模板中的一個語義塊,定義 interpret 方法解析該塊。

public interface SQLExpression {
    String interpret(Context context);
}

終結符表達式

文本表達式(TextExpression):不可再分的文本部分(如靜態 SQL 片段);

佔位符表達式 (PlaceholderExpression):解析並替換 #{key}

// 終結符表達式:文本片段
public class TextExpression implements SQLExpression {
    private String text;

    public TextExpression(String text) {
        this.text = text;
    }

    @Override
    public String interpret(Context context) {
        return text;
    }
}

// 終結符表達式:佔位符替換
class PlaceholderExpression implements SQLExpression {
    private String text;

    public PlaceholderExpression(String text) {
        this.text = text;
    }

    @Override
    public String interpret(Context context) {
        // 替換 #{key} 為參數值
        Pattern pattern = Pattern.compile("#\\{(\\w+)}");
        Matcher matcher = pattern.matcher(text);
        StringBuffer result = new StringBuffer();

        while (matcher.find()) {
            String key = matcher.group(1);
            Object value = context.getParameter(key);
            if (value == null) {
                throw new RuntimeException("參數 " + key + " 未提供");
            }
            String replacement = (value instanceof String) ? "'" + value + "'" : value.toString();
            matcher.appendReplacement(result, replacement);
        }
        matcher.appendTail(result);

        return result.toString();
    }
}

非終結符表達式

條件組表達式(ConditionalGroupExpression):解析<where> 標籤中的一組條件;

條件表達式 (IfExpression):解析 <if> 標籤中的動態 SQL;

集合表達式 (ForEachExpression):解析 <foreach> 動態生成 SQL;

複合表達式(CompositeExpression):將多個表達式組合成一個整體。

// 非終結符表達式:條件組,自動管理 WHERE/AND/OR
public class ConditionalGroupExpression implements SQLExpression {
    private List<SQLExpression> conditions = new ArrayList<>();

    public void addCondition(SQLExpression condition) {
        conditions.add(condition);
    }

    @Override
    public String interpret(Context context) {
        StringBuilder result = new StringBuilder();
        int validConditions = 0;

        for (SQLExpression condition : conditions) {
            String conditionResult = condition.interpret(context).trim();
            if (!conditionResult.isEmpty()) {
                // 對首個有效條件去掉前綴
                if (validConditions == 0) {
                    if (conditionResult.toUpperCase().startsWith("AND ")) {
                        conditionResult = conditionResult.substring(4);
                    } else if (conditionResult.toUpperCase().startsWith("OR ")) {
                        conditionResult = conditionResult.substring(3);
                    }
                } else {
                    // 非首條件,確保沒有多餘的空格或重複邏輯
                    if (!conditionResult.toUpperCase().startsWith("AND") && !conditionResult.toUpperCase().startsWith("OR")) {
                        result.append(" AND ");
                    } else {
                        result.append(" ");
                    }
                }
                result.append(conditionResult);
                validConditions++;
            }
        }

        return validConditions > 0 ? "WHERE " + result.toString().trim() : "";
    }
}
// 非終結符表達式:條件
class IfExpression implements SQLExpression {
    private String condition;
    private SQLExpression innerExpression;

    public IfExpression(String condition, SQLExpression innerExpression) {
        this.condition = condition;
        this.innerExpression = innerExpression;
    }

    @Override
    public String interpret(Context context) {
        // 解析條件,支持 key != null 和 key == value 等
        if (evaluateCondition(condition, context)) {
            return innerExpression.interpret(context);
        }
        return ""; // 條件不滿足時返回空字符串
    }

    // 解析條件語法
    private boolean evaluateCondition(String condition, Context context) {
        // 簡單支持 key != null 和 key == value 的邏輯
        if (condition.contains("!=")) {
            String[] parts = condition.split("!=");
            String key = parts[0].trim();
            Object value = context.getParameter(key);
            return value != null; // 判斷 key 是否存在
        } else if (condition.contains("==")) {
            String[] parts = condition.split("==");
            String key = parts[0].trim();
            String expectedValue = parts[1].trim().replace("'", ""); // 移除單引號
            Object actualValue = context.getParameter(key);
            return expectedValue.equals(actualValue != null ? actualValue.toString() : null);
        }
        throw new RuntimeException("不支持的條件: " + condition);
    }
}

// 非終結符表達式:集合操作
class ForEachExpression implements SQLExpression {
    private String itemName;
    private String collectionKey;
    private String open;
    private String separator;
    private String close;

    public ForEachExpression(String itemName, String collectionKey, String open, String separator, String close) {
        this.itemName = itemName;
        this.collectionKey = collectionKey;
        this.open = open;
        this.separator = separator;
        this.close = close;
    }

    @Override
    public String interpret(Context context) {
        Object collection = context.getParameter(collectionKey);
        if (!(collection instanceof Collection)) {
            throw new RuntimeException("參數 " + collectionKey + " 必須是集合");
        }
        Collection<?> items = (Collection<?>) collection;
        StringJoiner joiner = new StringJoiner(separator, open, close);
        for (Object item : items) {
            joiner.add(item instanceof String ? "'" + item + "'" : item.toString());
        }
        return joiner.toString();
    }
}

// 複合表達式:將多個表達式組合成一個整體
class CompositeExpression implements SQLExpression {
    private List<SQLExpression> expressions = new ArrayList<>();

    public CompositeExpression(SQLExpression... expressions) {
        this.expressions.addAll(Arrays.asList(expressions));
    }

    @Override
    public String interpret(Context context) {
        StringBuilder result = new StringBuilder();
        for (SQLExpression expression : expressions) {
            result.append(expression.interpret(context));
        }
        return result.toString();
    }
}

測試客户端

兩條動態查詢SQL的生成測試

public class DynamicSQLInterpreterDemo {
    public static void main(String[] args) {
        // 動態 SQL 模板
        List<SQLExpression> expressions = new ArrayList<>();
        expressions.add(new TextExpression("SELECT * FROM t_users"));

        // WHERE 條件組
        ConditionalGroupExpression whereGroup = new ConditionalGroupExpression();
        whereGroup.addCondition(new IfExpression("id != null", new PlaceholderExpression("and id = #{id}")));
        whereGroup.addCondition(new IfExpression("name != null", new PlaceholderExpression("OR name = #{name}")));
        whereGroup.addCondition(new IfExpression("ids != null && !ids.isEmpty()", new CompositeExpression(
                new TextExpression("AND id IN "),
                new ForEachExpression("id", "ids", "(", ",", ")")
        )));
        expressions.add(whereGroup);

        // 測試參數
        Map<String, Object> parameters1 = new HashMap<>();
        parameters1.put("id", 1);
        parameters1.put("name", "Alice");
        parameters1.put("ids", Arrays.asList(1, 2, 3));

        Map<String, Object> parameters2 = new HashMap<>();
        parameters2.put("ids", Arrays.asList(1, 2, 3));

        // 輸出最終 SQL
        System.out.println("測試 1:");
        generateSQL(expressions, parameters1);
        System.out.println("測試 2:");
        generateSQL(expressions, parameters2);
    }

    private static void generateSQL(List<SQLExpression> expressions, Map<String, Object> parameters) {
        Context context = new Context(parameters);
        StringBuilder parsedSQL = new StringBuilder();

        for (SQLExpression expression : expressions) {
            String result = expression.interpret(context).trim();
            if (!result.isEmpty()) {
                parsedSQL.append(" ").append(result);
            }
        }
        System.out.println(parsedSQL.toString().trim());
    }
}

運行結果

測試 1:
SELECT * FROM t_users WHERE id = 1 OR name = 'Alice' AND id IN (1,2,3)
測試 2:
SELECT * FROM t_users WHERE id IN (1,2,3)

該案例簡單實現了動態查詢SQL的生成。List<SQLExpression> expressions 變量在動態SQL表達式只執行兩次add()操作,第一次是SELECT * FROM t_user,第二次是where條件語句,然後再根據參數值替換佔位符來實現動態SQL的生成。

所有的表達式都可以獨立進行擴展調整而不相互影響(靈活性、擴展性)。比如、新增分組查詢、查詢結果排序等表達式,以及原有表達式的不斷優化調整。

也可以,將動態SQL模版改為xml文檔進行SQL配置化。解析過程變為:先經過xml文檔解析,根據請求參數再進行動態SQL解釋,從而靈活生成SQL。這看起來有點MyBatis的味道。

解釋器模式的應用

Hibernate:使用 ORM 技術,通過對象關係映射來執行查詢;

MyBatis:通過映射器和 XML 配置來處理動態 SQL;

兩者都使用瞭解釋器模式來處理查詢的解析。

優缺點和適用場景

優點

  • 擴展性好:可以輕鬆地添加新的表達式解析規則。
  • 直觀性強:語言的規則和實現代碼一一對應,清晰明瞭。
  • 適用於領域特定語言:非常適合解決領域特定問題。

缺點

  • 複雜性增加:對於複雜的語法規則,類的數量會迅速增加,導致維護成本高。
  • 性能問題:解釋器模式效率較低,特別是在需要解析大量複雜表達式時。

適用場景

解釋器模式適合在以下情況下使用:

  • 特定語法或規則:需要為某個特定領域設計一個語言或表達式處理工具。
  • 重複問題:問題可以通過一組標準規則或語法重複解決。
  • 可擴展性需求:希望能夠輕鬆添加新規則或表達式。

常見示例:

  • 計算器程序(解析數學表達式)。
  • SQL解析器。
  • 編譯器中的語法解析器。
  • 簡單的腳本解釋器。

總結

解釋器模式適合用於實現輕量級的解析器或簡單的領域特定語言,但在面對複雜語法或高性能需求的場景時,可能需要其他更高效的解析工具(如正則表達式、ANTLR等)。

解釋器模式提供了一種創建領域特定語言(DSL)的方法。

image

需要查看往期設計模式文章的,可以在個人主頁中或者文章開頭的集合中查看,可關注我,持續更新中。。。


超實用的SpringAOP實戰之日誌記錄

2023年下半年軟考考試重磅消息

通過軟考後卻領取不到實體證書?

計算機算法設計與分析(第5版)

Java全棧學習路線、學習資源和麪試題一條龍

軟考證書=職稱證書?

軟考中級--軟件設計師毫無保留的備考分享

user avatar _wss 头像 xingchenheyue 头像
点赞 2 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.