解释器模式——我最懂你

解释器的概念其实很好理解,假如你女朋友问你你在干嘛呀,这个时候她往往是想你了。奇怪,我们获取到的信息明明是“你在干嘛呀”,但是经过“解释器“处理之后,就变成了“我想你了”。这下子好理解了吧,基于这个特点,解释器模式可以构造一个可以用于创建脚本化应用的迷你语言解释器。

那么解释器模式到底有什么用呢?

假设现在我们做的是一个问卷调查平台,命题人可以通过我们的平台来设计问卷以及问卷答案。为了检测用户输入的数据中是否包含答案,我们知道可以用php的正则表达式来实现,但是,一般命题人或许并不会正则表达式,所以为了方便命题人使用我们的系统,假如我们现在有一套更加容易理解的迷你语言用来设置答案,那么就能大大地提高用户体验。话不多说,让我们来看看具体是怎么实现的(以下只展示解释器部分,实际上还需要解析器部分)

现在我们 的迷你语言中包含这些语法元素,变量,字符串,and(与),equals(相等判断).

首先我们定义一个数据存储类,用来存放于表达式对象相关的数据

 // 共享的数据存储
 class intepreterContext{
     //存储数组
     public $expressionStore=array();
     //表达式对象 键值对 插入存储数组
     public function insert(expression $expr,$value){
          $this->expressionStore[$expr->getKey()]=$value;
     }
     //获取与表达式对象相关联的数据
     public function get(expression $expr){
         return ($this->expressionStore[$expr->getKey()]);
     }
 }

一个表达式的组成既有操作数,也有操作符。先定义一个表达式抽象基类

abstract class expression{
    //用于获取递增的不重复的索引
    public static $keycount=0;
    //表达式对象的索引,用于在共享的数据空间中获得数据
    public $key;
    //对表达式对象进行索引分配
    public function getKey(){
       if(!isset($this->key)){
           self::$keycount++;
           $this->key=self::$keycount;
       }
       return $this->key;
    }
   //通过调用interpret对表达式对象进行解释
   abstract public function interpret(intepreterContext $context);
}

在interpret方法中传入interpreterContext对象是因为在对表达式对象的解释过程中会涉及对象的存取,可以看出表达式类是依赖interpreterContext类的。接下来定义字符串表达式类和变量表达式类,在变量表达式类中我们使用变量名作为变量表达式对象的索引,所以需要对原先抽象父类expression中的getKey方法进行重写,同时也增加了setValue方法用于修改变量值。代码如下:

class  variableExpression extends expression{
           public $name;//变量名
           public $value=null;//变量值
           public function __construct($name,$value){
               $this->name=$name;
               $this->value=$value;
           } 
           public function getKey()
          {
               return $this->name;
          }               }
          public function  setValue($value){
               $this->value=$value;
          }
          public function interpret(intepreterContext$context)
          {
             if(!is_null($this->value)) {
                  $context->insert($this, $this->value);
                  $this->value=null;
             }
           }
}

还有一点需要注意的,就是在interpret方法中插入变量名和变量值后,将该变量对象的value置空,因为如果后期定义了变量名相同的变量,则在存储空间中该变量名索引所对应的变量值会被更新,如果这里我们没有把已经存储到存储空间的变量对象的变量值属性置空,则后期如果不小心再次调用了该变量对象的interpret方法就会修改当前值,这样显然是不行的。

接下来定义字符串表达式:

class stringExpression extends expression {
    public $value=null;
    public function __construct($value)
    {
        $this->value=$value;
    }
    public function interpret(intepreterContext $context)
    {
        $context->insert($this,$this->value);
    }
}

这里字符串对象在存储空间的索引我们采用默认的方式。

到这里我们先来简单总结一下,可以看出操作数对象的解释主要是将与操作数对象相关联的数据放入存储空间,同时为设置索引以便引用。

而对于操作符对象,我们可以将操作符对象理解为一个组合对象(二元操作符的话需要包含两个表达式对象,注意表达式对象可以是操作符对象也可以是操作数对象)。因此,我们定义了一个抽象操作符基类。

abstract class operatorExpression extends expression {
    public $l_operand;
    public $r_operand;
    public function __construct(expression $e1,expression $e2)
    {
        $this->l_operand=$e1;
        $this->r_operand=$e2;
    }
    public function interpret(intepreterContext $context)
    {
    //两边的操作数有可能是变量也有可能是表达式,
    //如果是变量则相当于进行了一次重写和读取,如果是表达式则需要进行解释
        $this->l_operand->interpret($context);
        $this->r_operand->interpret($context);
        $l_result=$context->get($this->l_operand);
        $r_result=$context->get($this->r_operand);
        $this->dointerpret($context,$l_result,$r_result);
    }
    abstract function dointerpret(intepreterContext $context,$l_result,$r_result);
}

有了该操作符抽象基类,我们就可以来定义具体的操作符类了。

class equals extends operatorExpression {
    public function dointerpret(intepreterContext $context,$l_result,$r_result)
    {
       $context->insert($this,$l_result==$r_result);
    }
}
class andExpr extends operatorExpression{
    public function dointerpret(intepreterContext $context, $l_result, $r_result)
    {
        $context->insert($this,$l_result && $r_result);
    }
}

下面来演示一下用法,

问题:我和你妈妈掉进水里你救谁

答案:你和妈妈。

为了设置答应,我们可以这样来使用这个迷你语言,假设input是输入,那么我们写出来的语句大概是这样子。input1 equals ‘你’ and input2 equals ‘妈妈’,忽略对语句进行解析的部分,我们直接跳都解释器部分。代码如下:

直到实例化了and操作符对象,我们这时候已经通过传递对象的方式成功构建了一个表达式树,两个equals对象分别包含了一个变量对象和一字符串对象,然后最顶层的And对象包含了两个equals对象。当调用and对象的解释方法时,便会隐式地调用其包含对象的解释方法,最终将and操作结果和and对象的索引写入存储空间。在var_dump之后便可得到一个true。

好了,以上便是解释器模式的基本介绍,由于水平有限未免有不当之处,欢迎指出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值