美文网首页Java设计模式设计模式
《设计模式》解释器模式

《设计模式》解释器模式

作者: 敏捷Studio | 来源:发表于2019-08-19 16:35 被阅读0次

    定义

    假定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。

    介绍

    • 解释器模式属于行为型模式
    • 解释器模式提供了一种解释语言的语法或表达式的方式。
    • 解释器模式实际开发中很少用到。

    UML类图

    解释器模式UML类图

    角色说明:

    • AbstractExpression(抽象表达式):定义一个抽象的解释方法,其具体的实现在各个具体的子类解释器中完成。
    • TerminalExpression(终结符表达式):实现对文法中与终结符有关的解释操作。
    • NonterminalExpression(非终结符表达式):实现对文法中的非终结符有关的解释操作。
    • Context(环境角色):包含解释器之外的全部信息。
    • Client(客户端角色):解析表达式,构建抽象语法树,执行具体的解释操作等。

    实现

    以加减法的实现为例,我们实现下面表达式的解释并输出结果,为了方便解释,在表达式中介加了空格方便处理。

    a = 1024
    b = 512
    a + b
    a - b
    

    1、创建抽象表达式

    // 抽象算术表达式
    public abstract class ArithmeticExpression {
      // 抽象解释方法
      public abstract Object interpret(Context context);
    }
    

    2、终结符表达式。从上面的表达式可以看出,终结符有两种,一种是数字,另外一种是变量。

    // 数字表达式,用来解释数字
    public class NumExpression extends ArithmeticExpression {
      private String strNum;
    
      public NumExpression(String strNum) {
        this.strNum = strNum;
      }
    
      // 解释数字
      @Override
      public Integer interpret(Context context) {
        return Integer.parseInt(strNum);
      }
    }
    
    // 变量表达式,用来解释变量
    class VarExpression extends ArithmeticExpression {
      private String var;
    
      public VarExpression(String var) {
        this.var = var;
      }
    
      // 解释变量
      @Override
      public String interpret(Context context) {
        return var;
      }
    }
    

    3、创建非终结符表达式。上面的表达式有三种非终结符,分别是+号、-号和=号。

    // 加法表达式,用来解释加法,如a+b
    public class AddExpression extends ArithmeticExpression {
      // 加号左右两边的内容
      private ArithmeticExpression left, right;
    
      public AddExpression(ArithmeticExpression left, ArithmeticExpression right) {
        this.left = left;
        this.right = right;
      }
    
      // 解释加法表达式的结果,即算出left+right的结果
      @Override
      public Integer interpret(Context context) {
        return  context.get((String) left.interpret(context)) + context.get((String) right.interpret(context));
      }
    }
    
    // 减法表达式,用来解释减法,如a-b
    public class SubExpression extends ArithmeticExpression {
      // 减号左右两边的内容
      private ArithmeticExpression left, right;
    
      public SubExpression(ArithmeticExpression left, ArithmeticExpression right) {
        this.left = left;
        this.right = right;
      }
    
      // 解释减法表达式的结果,即算出left-right的结果
      @Override
      public Integer interpret(Context context) {
        return context.get((String) left.interpret(context)) - context.get((String) right.interpret(context));
      }
    }
    
    // 等号表达式,用来解释变量赋值,如a=1024
    public class EqualExpression extends ArithmeticExpression {
      // 等号左右两边的内容
      private ArithmeticExpression left, right;
    
      public EqualExpression(ArithmeticExpression left, ArithmeticExpression right) {
        this.left = left;
        this.right = right;
      }
    
      // 解释等号表达式的结果,并将结果保存到context,变量名为key,值为value
      @Override
      public Object interpret(Context context) {
        context.put((String) left.interpret(context), (int) right.interpret(context));
        return null;
      }
    }
    

    4、创建环境角色。创建环境主要包含解释器之外的全部信息,这里用来保存变量以及其值。

    public class Context {
      // 使用HashMap来保存结果
      Map<String, Object> mMap = new HashMap<>();
    
      public void put(String key, int value) {
        mMap.put(key, value);
      }
    
      public int get(String key) {
        return (int) mMap.get(key);
      }
    }
    

    5、创建客户端角色。客户端角色主要负责解析表达式,构建抽象语法树,执行具体的解释操作等。

    // 计算器类
    public class Calculator {
      Context mContext = new Context();
      private ArithmeticExpression mExpression;
    
      // 读取表达式
      public void read(String expression) {
        // 表达式以空格隔开,方便拆分
        String[] split = expression.split(" ");
        // 根据不同符号去执行具体的解析操作
        switch (split[1]) {
          case "=":
            mExpression = new EqualExpression(new VarExpression(split[0]), new NumExpression(split[2])).interpret(mContext);
            break;
          case "+":
            mExpression = new AddExpression(new VarExpression(split[0]), new VarExpression(split[2]));
            break;
          case "-":
            mExpression = new SubExpression(new VarExpression(split[0]), new VarExpression(split[2]));
            break;
        }
      }
    
      // 计算结果
      public int calculate() {
        return (int) mExpression.interpret(mContext);
      }
    }
    

    6、客户端测试:

    public void test() {
      // 读取表达式
      Calculator calculator = new Calculator();
      calculator.read("a = 1024");
      calculator.read("b = 512");
      System.out.println("a = 1024");
      System.out.println("b = 512");
    
      // 计算结果
      calculator.read("a + b");
      System.out.println("a + b = " + calculator.calculate());
      calculator.read("a - b");
      System.out.println("a - b = " + calculator.calculate());
    }
    

    输出结果:

    a = 1024
    b = 512
    a + b = 1536
    a - b = 512
    

    应用场景

    • 简单的语法需要解释时,如解释一个sql语句。
    • 一些重复发生的问题,比如加减乘除四则运算,但是公式每次都不同,有时是a+b-cd,有时是ab+c-d等,公式千变万化,但是都是由加减乘除四个非终结符来连接的,这时我们就可以使用解释器模式。

    优缺点

    优点

    • 灵活的扩展性,想扩展语法规则时只需新增新的解释器就可以了。如上面的例子中,想增加乘除法,只增加相应的解释类,并增加相应的表达式解释操作即可。

    缺点

    • 每一个文法都至少对应一个解释器,会产生大量的类,难于维护。
    • 解释器模式由于大量使用循环和递归,需要考虑效率的问题,而且调试也不方便。
    • 对于复杂的文法,构建其抽象语法树会显得异常繁琐。
    • 所以不推荐在重要的模块中使用解释器模式,维护困难。

    Android中的源码分析

    对于AndroidManifest.xml这个文件,我们是相当熟悉。实际上AndroidManifest.xml是由PackageManagerService使用了PackageParser这个类来解释的,这里面就用到了解释器模式。对于AndroidManifest.xml中的每一个标签,都有对应的类去保存相应的信息。

    1、PackageParserparseBaseApkCommon方法。基于Android 27的源码,不同版本的源码方法名可能不一样。

    private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res,
                                       XmlResourceParser parser, int flags, String[] outError) 
            throws XmlPullParserException, IOException {
      // 其他代码略
      if (tagName.equals(TAG_APPLICATION)) {
        // 其他代码略
    
        // 解释application标签
        if (!parseBaseApplication(pkg, res, parser, flags, outError)) {
          return null;
        }
      } else if (tagName.equals(TAG_OVERLAY)) {
        // 其他代码略
      } else if (tagName.equals(TAG_KEY_SETS)) {
        if (!parseKeySets(pkg, res, parser, outError)) {
          return null;
        }
      } else if (tagName.equals(TAG_PERMISSION_GROUP)) {
        if (!parsePermissionGroup(pkg, flags, res, parser, outError)) {
          return null;
        }
      } else if (tagName.equals(TAG_PERMISSION)) {
        if (!parsePermission(pkg, res, parser, outError)) {
          return null;
        }
      } else if (tagName.equals(TAG_PERMISSION_TREE)) {
        if (!parsePermissionTree(pkg, res, parser, outError)) {
          return null;
        }
      } else if (tagName.equals(TAG_USES_PERMISSION)) {
        if (!parseUsesPermission(pkg, res, parser)) {
          return null;
        }
      } else if (tagName.equals(TAG_USES_PERMISSION_SDK_M)
                || tagName.equals(TAG_USES_PERMISSION_SDK_23)) {
        if (!parseUsesPermission(pkg, res, parser)) {
          return null;
        }
      } else if (tagName.equals(TAG_USES_CONFIGURATION)) {
        // 其他代码略
      } else if (tagName.equals(TAG_USES_FEATURE)) {
        // 其他代码略
      } else if (tagName.equals(TAG_FEATURE_GROUP)) {
        // 其他代码略
      } else if (tagName.equals(TAG_USES_SDK)) {
        // 其他代码略
      } else if (tagName.equals(TAG_SUPPORT_SCREENS)) {
        // 其他代码略
      } else if (tagName.equals(TAG_PROTECTED_BROADCAST)) {
        // 其他代码略
      } else if (tagName.equals(TAG_INSTRUMENTATION)) {
        // 其他代码略
      } else if (tagName.equals(TAG_ORIGINAL_PACKAGE)) {
        // 其他代码略
      } else if (tagName.equals(TAG_ADOPT_PERMISSIONS)) {
        // 其他代码略
      } else if (tagName.equals(TAG_USES_GL_TEXTURE)) {
        // 其他代码略
      } else if (tagName.equals(TAG_COMPATIBLE_SCREENS)) {
        // 其他代码略
      } else if (tagName.equals(TAG_SUPPORTS_INPUT)) {
        // 其他代码略
      } else if (tagName.equals(TAG_EAT_COMMENT)) {
        // 其他代码略
      } else if (tagName.equals(TAG_PACKAGE)) {
        // 其他代码略
      } else if (tagName.equals(TAG_RESTRICT_UPDATE)) {
        // 其他代码略
      } else if (RIGID_PARSER) {
        // 其他代码略
      } else {
        // 其他代码略
      }
    }
    

    从上面代码可以看到,就是对各个标签的内容进行解释。我们再来看看parseBaseApplication这个方法,这个是对Application进行解释。

    2、parseBaseApplication方法

    private boolean parseBaseApplication(Package owner, Resources res,
                                         XmlResourceParser parser, int flags, String[] outError)
            throws XmlPullParserException, IOException {
    
      // 其他代码略
      String tagName = parser.getName();
      if (tagName.equals("activity")) {
        // 解释activity
        Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false,
                    owner.baseHardwareAccelerated);
        // 其他代码略
      } else if (tagName.equals("receiver")) {
        // 解释receiver
        Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
                    true, false);
        // 其他代码略
      } else if (tagName.equals("service")) {
        // 解释service
        Service s = parseService(owner, res, parser, flags, outError, cachedArgs);
        // 其他代码略
      } else if (tagName.equals("provider")) {
        // 解释provider
        Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);
        // 其他代码略
      } else if (tagName.equals("activity-alias")) {
        Activity a = parseActivityAlias(owner, res, parser, flags, outError, cachedArgs);
        // 其他代码略
      } else if (parser.getName().equals("meta-data")) {
        // 其他代码略
      } else if (tagName.equals("static-library")) {
        // 其他代码略
      } else if (tagName.equals("library")) {
        // 其他代码略
      } else if (tagName.equals("uses-static-library")) {
        if (!parseUsesStaticLibrary(owner, res, parser, outError)) {
          return false;
        }
      } else if (tagName.equals("uses-library")) {
        // 其他代码略
      } else if (tagName.equals("uses-package")) {
        // 其他代码略
      } else {
        // 其他代码略
      }
      
      // 其他代码略
    
      return true;
    }
    

    可以看到,上面有对activityreceiverservice等标签的解释,activity的具体解释在parseActivity这个方法里面,有兴趣的可以自行去看下,这里就不细说了,同时可以看到receiver也是在parseActivity这个方法中解释。

    相关文章

      网友评论

        本文标题:《设计模式》解释器模式

        本文链接:https://www.haomeiwen.com/subject/zbissctx.html