解釋器模式(Interpreter Pattern)
解釋器模式(Interpreter Pattern)是一種行為型設計模式,用於定義一種語言的文法表示,並提供一個解釋器來解釋該語言中的句子。這種模式通常用於開發需要解析、解釋和執行特定語言或表達式的應用程序。
主要目的是為特定類型的問題定義一種語言,然後用該語言的解釋器來解決問題。
主要組成部分
解釋器模式的結構通常包括以下幾個部分:
-
抽象表達式(AbstractExpression) :定義解釋操作的接口。
-
終結符表達式(TerminalExpression) :表示語言中的基本元素,如數字或變量。
-
非終結符表達式(NonTerminalExpression):表示更復雜的語法規則,通過組合終結符表達式和其他非終結符表達式實現。
-
上下文(Context):存儲解釋器在解析表達式時需要的全局信息,比如變量值或共享數據。
-
客户端(Client):構建(或從外部獲取)需要解析的表達式,並使用解釋器處理表達式。
區分終結符和非終結符主要看它是不是最終的輸出,是不是不可再分的組成部分。
案例實現
設計一個動態查詢SQL 的解析器,查詢SQL模板 + 輸入的參數 動態的生成所需要的查詢SQL 。
本案例的主要功能:
- 支持佔位符替換:如
#{key}替換為參數值。 - 支持動態條件解析:如
<if>標籤根據條件決定是否生成部分 SQL。 - 支持集合操作:如
<foreach>動態生成IN子句。 - 使用解釋器模式解析查詢SQL 模板,分離模板的不同語義塊。
案例類圖
類圖簡述
-
上下文 (
Context) :存儲輸入參數,供解釋器在解析時訪問。 -
抽象表達式 (
SQLExpression) :表示 SQL 模板中的一個語義塊,定義interpret方法解析該塊,參數為Context輸入參數。 -
終結符表達式
-
文本表達式(
TextExpression):不可再分的文本部分(如靜態 SQL 片段); -
佔位符表達式 (
PlaceholderExpression):解析並替換#{key}。
- 非終結符表達式
- 條件組表達式(
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)的方法。
需要查看往期設計模式文章的,可以在個人主頁中或者文章開頭的集合中查看,可關注我,持續更新中。。。
超實用的SpringAOP實戰之日誌記錄
2023年下半年軟考考試重磅消息
通過軟考後卻領取不到實體證書?
計算機算法設計與分析(第5版)
Java全棧學習路線、學習資源和麪試題一條龍
軟考證書=職稱證書?
軟考中級--軟件設計師毫無保留的備考分享