掌握设计模式--解释器模式

解释器模式(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 模板,分离模板的不同语义块。

案例类图

类图简述

  1. 上下文 (Context) :存储输入参数,供解释器在解析时访问。

  2. 抽象表达式 (SQLExpression) :表示 SQL 模板中的一个语义块,定义 interpret 方法解析该块,参数为Context输入参数。

  3. 终结符表达式

  • 文本表达式(TextExpression):不可再分的文本部分(如静态 SQL 片段);

  • 占位符表达式 (PlaceholderExpression) :解析并替换 #{key}

  1. 非终结符表达式
  • 条件组表达式(ConditionalGroupExpression) :解析<where> 标签中的一组条件;
  • 条件表达式 (IfExpression) :解析 <if> 标签中的动态 SQL;
  • 集合表达式 (ForEachExpression) :解析 <foreach> 动态生成 SQL;
  • 复合表达式(CompositeExpression):将多个表达式组合成一个整体。

上下文

存储动态 SQL 的参数,供解释器在解析时访问。

java 复制代码
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 方法解析该块。

java 复制代码
public interface SQLExpression {
    String interpret(Context context);
}

终结符表达式

文本表达式(TextExpression):不可再分的文本部分(如静态 SQL 片段);

占位符表达式 (PlaceholderExpression) :解析并替换 #{key}

java 复制代码
// 终结符表达式:文本片段
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):将多个表达式组合成一个整体。

java 复制代码
// 非终结符表达式:条件组,自动管理 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的生成测试

java 复制代码
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());
    }
}

运行结果

plaintext 复制代码
测试 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全栈学习路线、学习资源和面试题一条龙

软考证书=职称证书?

软考中级--软件设计师毫无保留的备考分享

相关推荐
杨充15 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充15 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充15 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充15 小时前
2.面向对象的特性
设计模式
杨充15 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充15 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充16 小时前
3.接口vs抽象类比较
设计模式
咖啡八杯19 小时前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法
咖啡八杯1 天前
GoF设计模式——解释器模式
java·后端·spring·设计模式