设计模式讲解与代码实践(十六)——解释器

本文来自李明子csdn博客(http://blog.csdn.net/free1985),商业转载请联系博主获得授权,非商业转载请注明出处!

1 目的

解释器(Interpreter)模式用于表达和解释抽象语法树。

2 基本形态

解释器的基本形态如类图2-1所示。
图2-1  解释器类图
图2-1 解释器类图

3 参与者

结合图2-1,下面介绍各类在解释器设计模式中扮演的角色。
3.1 Context
Context是上下文,是解释器执行解释时替换操作“形参”(或者叫占位符)的数据来源。
3.2 AbstractExpression
AbstractExpression是抽象表达式接口,声明了解释方法Interpret,以上下文Context对象为参数解释表达式。
3.3 TerminalExpression
TerminalExpression是终结表达式类,实现抽象表达式接口AbstractExpression。终结表达式没有嵌套其他子表达式,可以直接进行解释。一般来说,解释器模式会为每种运算符生成一个实现AbstractExpression接口的表达式类。根据具体的运算形式,在构造方法中提供解释时必要的参数。在对接口方法Interpret的实现中,将结合传入的上下文Context对象对表达式进行解释。
3.4 NonterminalExpression
NonterminalExpression是非终结表达式类,实现抽象表达式接口AbstractExpression。NonterminalExpression类要解释的“运算数”不是直接变量而是子表达式。NonterminalExpression类在构造方法中以参数形式接受作为运算数的AbstractExpression类型子表达式对象。实现接口方法Interpret时,NonterminalExpression解释自己的运算符,填充运算数部分时调用子表达式对象的Interpret方法。
3.5 Client
Client是客户类,是解释器模式的使用者。Client构建语法树AbstractExpression接口对象,以创建或传递的上下文Context对象为参数调用解释方法Interpret解释表达式。

4 代码实践

下面我们用一个业务场景实例来进一步讲解解释器的使用。
4.1 场景介绍
某管理系统使用某前端控件库的数据过滤条件格式保存用于自定义的查询计划。用户使用时选定查询计划,给定查询参数,后台按给定的查询参数将查询计划解释为sql语句执行。
以下各节将介绍该场景各类的具体实现及其在解释器设计模式中所对应的参与者角色。
4.2 Context
Context是上下文类,是解释操作执行时的参数。对应于解释器模式的参与者,Context是上下文Context。下面的代码给出了Context的声明。

package demo.designpattern.interpreter;

import java.util.HashMap;
import java.util.Map;

/**
 * 上下文
 * Created by LiMingzi on 2017/8/2.
 */
public class Context {
    /**
     * 变量映射表
     */
    private Map<String,Object> variableMap = new HashMap<String, Object>();

    /**
     * 获取变量值
     * @param varName 变量名
     * @return 变量值
     */
    public Object getVarValue(String varName){
        if(variableMap.containsKey(varName)){
            return variableMap.get(varName);
        }
        return null;
    }

    /**
     * 设置变量值
     * @param varName 变量名
     * @param varValue 变量值
     */
    public void setVarValue(String varName,Object varValue){
        variableMap.put(varName,varValue);
    }
}

上述代码中,14行,声明了用于表示变量映射表的成员变量variableMap。解释器解释表达式时,上下文中的实参将替代表达式中的形参以重用表达式(避免表达式重复构建)。
4.3 IFilterExp
IFilterExp是过滤器表达式接口,声明了“解释表达式为sql语句”方法interpret2Sql。对应于解释器模式的参与者,IFilterExp是抽象表达式AbstractExpression。下面的代码给出了IFilterExp的声明。

package demo.designpattern.interpreter;

/**
 * 过滤器表达式接口
 * Created by LiMingzi on 2017/8/2.
 */
public interface IFilterExp {
    /**
     * 解释表达式为sql语句
     * @param context 上下文
     * @return sql语句
     */
    String interpret2Sql(Context context);
}

4.4 InExp
InExp是in表达式,用于解释“in”操作符,实现了IFilterExp接口。对应于解释器模式的参与者,InExp是终结表达式TerminalExpression。下面的代码给出了InExp的声明。

package demo.designpattern.interpreter;

import java.util.List;

/**
 * in表达式
 * Created by Gilbert on 2017/8/2.
 */
public class InExp implements IFilterExp{
    /**
     * 域
     */
    private String field;
    /**
     * 值的变量名
     */
    private String valueVarName;

    /**
     * 构造方法
     * @param field 域
     * @param valueVarName 值的变量名
     */
    public InExp(String field, String valueVarName) {
        this.field = field;
        this.valueVarName = valueVarName;
    }

    /**
     * 解释表达式为sql语句
     *
     * @param context 上下文
     * @return sql语句
     */
    public String interpret2Sql(Context context) {
        // in中的项集合
        List<Object> inItems = (List<Object>)context.getVarValue(valueVarName);
        // 解析的sql语句
        String sql= field+" in(";
        for (int i = 0; i < inItems.size(); i++) {
            if(i>0){
                sql += ",";
            }
            sql+="'"+inItems.get(i)+"'";
        }
        sql+=")";
        return sql;
    }
}

上述代码中,35行,interpret2Sql方法解释in表达式,将满足“指定域(field)的值在指定的一组值内”条件的记录捡取这个一条件转换为sql语句。其中,域在构造对象时指定(25行),值集合在执行解释操作时从上下文获取(37行),而值集合映射变量名在构造时指定(26行)。
4.5 ContainsExp
ContainsExp是contains表达式,用于解释“contains”操作符,实现了IFilterExp接口。对应于解释器模式的参与者,ContainsExp是终结表达式TerminalExpression。下面的代码给出了ContainsExp的声明。

package demo.designpattern.interpreter;

/**
 * contains表达式
 * Created by Gilbert on 2017/8/2.
 */
public class ContainsExp implements IFilterExp {
    /**
     * 域
     */
    private String field;
    /**
     * 值的变量名
     */
    private String valueVarName;

    /**
     * 构造方法
     * @param field 域
     * @param valueVarName 值的变量名
     */
    public ContainsExp(String field, String valueVarName) {
        this.field = field;
        this.valueVarName = valueVarName;
    }

    /**
     * 解释表达式为sql语句
     *
     * @param context 上下文
     * @return sql语句
     */
    public String interpret2Sql(Context context) {
        return field+" like '%"+context.getVarValue(valueVarName)+"%'";
    }
}

上述代码中,33行,interpret2Sql方法解释contains表达式,将满足“指定域(field)的值与给定值模糊必配”条件的记录捡取这个一条件转换为sql语句。其中,域在构造对象时指定(23行),匹配值在执行解释操作时从上下文获取(34行),而匹配值映射变量名在构造时指定(24行)。
4.6 AndExp
AndExp是and表达式,用于解释“and”操作符,实现了IFilterExp接口。对应于解释器模式的参与者,AndExp是非终结表达式NonterminalExpression。下面的代码给出了AndExp的声明。

package demo.designpattern.interpreter;

/**
 * and表达式
 * Created by Gilbert on 2017/8/2.
 */
public class AndExp implements IFilterExp {
    /**
     * 左表达式
     */
    private IFilterExp leftExp;
    /**
     * 右表达式
     */
    private IFilterExp rightExp;

    /**
     * 构造方法
     * @param leftExp 左表达式
     * @param rightExp 右表达式
     */
    public AndExp(IFilterExp leftExp, IFilterExp rightExp) {
        this.leftExp = leftExp;
        this.rightExp = rightExp;
    }

    /**
     * 解释表达式为sql语句
     *
     * @param context 上下文
     * @return sql语句
     */
    public String interpret2Sql(Context context) {
        return "("+leftExp.interpret2Sql(context)+" and "+rightExp.interpret2Sql(context)+")";
    }
}

上述代码中,11行、15行分别维护了and操作要用到的左、右表达式。它们在构造方法(22行)赋值。34行,解释方法中,对左右表达式分别调用解释方法,再对其结果进行解释。
4.7 OrExp
OrExp是or表达式,用于解释“or”操作符,实现了IFilterExp接口。对应于解释器模式的参与者,OrExp是非终结表达式NonterminalExpression。下面的代码给出了OrExp的声明。

package demo.designpattern.interpreter;

/**
 * or表达式
 * Created by Gilbert on 2017/8/2.
 */
public class OrExp implements IFilterExp {
    /**
     * 左表达式
     */
    private IFilterExp leftExp;
    /**
     * 右表达式
     */
    private IFilterExp rightExp;

    /**
     * 构造方法
     * @param leftExp 左表达式
     * @param rightExp 右表达式
     */
    public OrExp(IFilterExp leftExp, IFilterExp rightExp) {
        this.leftExp = leftExp;
        this.rightExp = rightExp;
    }

    /**
     * 解释表达式为sql语句
     *
     * @param context 上下文
     * @return sql语句
     */
    public String interpret2Sql(Context context) {
        return "(" + leftExp.interpret2Sql(context) + " or " + rightExp.interpret2Sql(context) + ")";
    }
}

上述代码中,11行、15行分别维护了or操作要用到的左、右表达式。它们在构造方法(22行)赋值。34行,解释方法中,对左右表达式分别调用解释方法,再对其结果进行解释。
4.8 FilterPlan
FilterPlan是过滤计划类,用于载入、执行用户的过滤计划。对应于解释器模式的参与者,FilterPlan是客户Client。下面的代码给出了FilterPlan的声明。

package demo.designpattern.interpreter;

/**
 * 过滤方案
 * Created by Gilbert on 2017/8/2.
 */
public class FilterPlan {
    /**
     * 过滤表达式
     */
    private IFilterExp filterExp;
    /**
     * 表明
     */
    private String tableName;
    /**
     * 构造方法
     * @param id 方案id
     */
    public FilterPlan(String id) {
        loadPlan(id);
    }

    /**
     * 载入计划,demo
     * @param id 方案id
     */
    private void loadPlan(String id){
        if(id.equals("001")) {
            // 过滤姓名包含关键字的指定部门或指定角色的人员
            filterExp = new AndExp(new ContainsExp("NAME", "userName"), new OrExp(new InExp("ORG", "orgName"), new InExp("ROLE", "roleName")));
            tableName = "T_USER";
        }
    }

    /**
     * 执行查询计划
     * @param context 上下文
     */
    public void execute(Context context){
        // sql语句
        String sql="select * from "+tableName+ " where "+filterExp.interpret2Sql(context);
        System.out.println(sql);
    }

}

上述代码中,20行,构造方法通过过滤方案id载入过滤方案。31行,构造了查询方案对应的表达式对象。40行,执行查询计划方法execute通过调用表达式解释方法interpret2Sql将过滤表达式解释为sql语句并执行过滤计划返回结果。
4.9 测试代码
为了测试本文中的代码,我们可以编写如下测试代码。测试代码中实例化了一个过滤计划,并给定了两个过滤上下文。它们分别是:“过滤张姓的指定财务部、法务部或角色为管理员、经理的人员”和“过滤李姓的指定IT部、销售部或角色为助理的人员”。

 /**
     * 解释器测试
     */
    public static void interpreterTest(){
        // 过滤计划
        FilterPlan filterPlan = new FilterPlan("001");
        // 上下文1,过滤张姓的指定财务部、法务部或角色为管理员、经理的人员
        Context context1 = new Context();
        context1.setVarValue("userName","张");
        // 组织集合1
        List<String> orgs1 = new ArrayList<String>();
        orgs1.add("财务部");
        orgs1.add("法务部");
        context1.setVarValue("orgName",orgs1);
        // 角色集合1
        List<String> role1 = new ArrayList<String>();
        role1.add("管理员");
        role1.add("经理");
        context1.setVarValue("roleName",role1);
        filterPlan.execute(context1);
        // 上下文2,过滤李姓的指定IT部、销售部或角色为助理的人员
        Context context2 = new Context();
        context2.setVarValue("userName","李");
        // 组织集合2
        List<String> orgs2 = new ArrayList<String>();
        orgs2.add("IT部");
        orgs2.add("销售部");
        context2.setVarValue("orgName",orgs2);
        // 角色集合2
        List<String> role2 = new ArrayList<String>();
        role2.add("助理");
        context2.setVarValue("roleName",role2);
        filterPlan.execute(context2);
    }

编译运行后,得到如下测试结果:
select from T_USER where (NAME like ‘%张%’ and (ORG in(‘财务部’,’法务部’) or ROLE in(‘管理员’,’经理’)))*
select from T_USER where (NAME like ‘%李%’ and (ORG in(‘IT部’,’销售部’) or ROLE in(‘助理’)))*

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值