设计模式是什么鬼(解释器)

原文链接:https://www.javazhiyin.com/24976.html

解释,一定是针对某种语言的拆解、释意,并按照文法翻译、转换成另一种表达形式以达到目标能够理解的目的。比如我们都知道Java编程语言是人类可以理解的语言,程序写好后要先进行编译生成字节码(class文件),然后对此文件解释成机器码,最终机器才可以理解并执行,这就是解释器存在的意义。

就拿我们人类的自然语言来举例,比如我们要进行英文翻译工作,首先要对一句话(表达式)进行拆解,而拆开后的单词就成了不可再分的终极表达式,例如说对英语句子“I love you”(非终极表达式)进行拆解,按空格分割为单词“I”、“love”、“you”(终极表达式),然后翻译每个单词再合并成“我爱你”,貌似很简单的样子。再看句子“I love that you love”,翻译成“我爱你那个你爱”。

这简直太荒谬了,这句明明是“我爱你所爱”的意思,貌似这里的拆解方式是有讲究的。“that you love”在这里应该是作为从句出现,所以它应该属于一个特殊的“非终极表达式”,有自己独特的翻译方式,而不是简单的单词拼接了。我们意识到语言的翻译绝非易事,但至少我们通过思考搞明白了终极与非终极表达式的区别、表达式的多态性、以及表达式的自包含关系结构。

化繁为简,为了保持例子的简约实用风格,我们决定自己发明一种语言,什么语言呢?对于骨灰级网游玩家来说打怪升级是最漫长的过程,既浪费时间又伤害身体,该怎么解决这个问题呢?有网瘾,电电就好。

开给玩笑,所以呢我们研发了一款挂机程序并起名”耗子精“,它可以直接发送指令给鼠标驱动来实现点击、移动操作,从此解放我们的双手让游戏人物自动打怪升级。既然不操作鼠标,那就需要一段脚本告诉“耗子精”怎样去操作鼠标指针,于是我们发明了一种脚本语言“精神食粮”,像是下面这样:

1BEGIN                // 脚本开始
2MOVE 500,600;        // 鼠标移动到坐标(500, 600)
3    BEGIN LOOP 5     // 开始循环5次
4        LEFT_CLICK;  // 循环体内单击左键
5        DELAY 1;     // 每次延时1秒
6    END;             // 循环体结束
7RIGHT_DOWN;          // 按下右键
8DELAY 7200;          // 延时2小时
9END;                 // 脚本结束

看注释很容易就能理解这是要干什么了,玩家先让鼠标挪动到地图的某个点上,然后不停地点击了n次(比如此处简化为5次)过后人物便到达了刷怪地点了(计算好延时时间),最后按下右键触发技能并一直不松开,直到挂机2小时后结束,这样便实现了自动打怪升级。

我们现在来对这个语言的表达式进行拆解、抽象、建模,可以看到除了循环(非终极表达式)以外其他的都是单个命令不可以拆了,也就是我们之前讲过的终极表达式,按照这个脚本我们先看一张结构图。

可以看到从始发节点“指令序列表达式”(根)开始被拆解成三个分支,第一步和第三步都是执行鼠标动作的终极表达式了(叶),而第二步的“循环”则属于非终极表达式(枝),它的循环体内可以包含多步指令,所以它包括一个子指令集(枝),然后继续往下延续出“左键单击表达式”(枝)和“系统延时表达式”(叶),最后“单击”其实就是“按下”与“松开”的组合了。有没有这个语义树结构好像似曾相识?没错,这就是之前讲过的“组合模式”,我们正是利用了“组合模式”(强调结构型)的结构模型构建了这个语义树(Syntax Tree),来完成我们的翻译工作(这里强调行为型)。

开始写代码,这么多表达式到底应该从哪里开始定义呢?不管三七二十一它们统统都是表达式,先写个表达式总抽象。

1public interface Expression { // 表达式接口
2    public void interpret(); // 解释方法
3}

可以看到这个接口定义了表达式的通用解释方法标准,一切表达式都得符合这个规则。接下来我们先从最基本的原子操作(终极表达式)开始定义实现类,它们应该依次是移动鼠标、左(右)键按下(松开)、系统延时表达式等,雷同的我们不做赘述,读者可以自己实现。

 1public class Move implements Expression {
 2    // 鼠标位置坐标
 3    private int x, y;
 4
 5    public Move(int x, int y) {
 6        this.x = x;
 7        this.y = y;
 8    }
 9
10    public void interpret() {
11        System.out.println("移动鼠标:【" + x + "," + y + "】");
12    }
13
14}
1public class LeftDown implements Expression {
2
3    public void interpret() {
4        System.out.println("按下鼠标:左键");
5    }
6
7}
1public class LeftUp implements Expression {
2
3    public void interpret() {
4        System.out.println("松开鼠标:左键");
5    }
6
7}
 1public class Delay implements Expression {
 2
 3    private int seconds;// 延时秒数
 4
 5    public Delay(int seconds) {
 6        this.seconds = seconds;
 7    }
 8
 9    public int getSeconds() {
10        return seconds;
11    }
12
13    public void interpret() {
14        System.out.println("系统延迟:" + seconds + "秒钟");
15        try {
16            Thread.sleep(seconds * 1000);
17        } catch (InterruptedException e) {
18            e.printStackTrace();
19        }
20    }
21
22}

很简单,它们都实现了interpret方法,并进行了相关操作的模拟。照猫画虎,下来实现非终极表达式:左(右)键单击表达式、循环表达式、以及指令集序列表达式等。

 1public class LeftClick implements Expression {
 2
 3    private Expression leftDown;
 4    private Expression leftUp;
 5
 6    public LeftClick() {
 7        this.leftDown = new LeftDown();
 8        this.leftUp = new LeftUp();
 9    }
10
11    public void interpret() {
12        //单击=先按下再松开
13        leftDown.interpret();
14        leftUp.interpret();
15    }
16
17}

这里有点意思了,单击表达式被我们继续拆分成“按下”及“松开”两个原子操作,由于点击是个固定的死操作,并不需要提供给客户端任何灵活性把它们传入进来,所以我们在构造时(第7行)主动实例化了它们。接下来是循环表达式,我们需要知道循环次数,以及循环体内要执行的表达式。

 1public class Repetition implements Expression {
 2
 3    private int loopCount;// 循环次数
 4    private Expression expression;// 循环体表达式
 5
 6    public Repetition(Expression expression, int loopCount) {
 7        this.expression = expression;
 8        this.loopCount = loopCount;
 9    }
10
11    public void interpret() {
12        while (loopCount > 0) {
13            expression.interpret();
14            loopCount--;
15        }
16    }
17
18}

清晰明了,循环表达式被初始化后用这些参数进行循环、并调用循环体表达式的解释方法,继续向下传递,至于这个表达式里具体还有什么子表达式我们根本不关心,这里主要负责循环调用,仅此而已。最后就是指令集序列表达式的实现了。

 1public class Sequence implements Expression {
 2    // 指令集序列
 3    private List<Expression> expressions;
 4
 5    public Sequence(List<Expression> expressions) {
 6        this.expressions = expressions;
 7    }
 8
 9    public void interpret() {
10        // 循环挨个解析每条指令
11        expressions.forEach(exp -> exp.interpret());
12    }
13
14}

我们要运行的脚本一定是有先后顺序的,所以这个指令集表达式里包含一个List<Expression>,在构造时(第5行)由客户端传入,并于第11行挨个顺序调用解释方法。貌似脚本用到的表达式已经定义完毕,客户端开始调用。

 1public class Client {
 2    public static void main(String[] args) {
 3        /*
 4         * BEGIN             // 脚本开始
 5         * MOVE 500,600;     // 鼠标移动到坐标(500, 600)
 6         *  BEGIN LOOP 5     // 开始循环5次
 7         *      LEFT_CLICK;  // 循环体内单击左键
 8         *      DELAY 1;     // 每次延时1秒
 9         *  END;             // 循环体结束
10         * RIGHT_DOWN;       // 按下右键
11         * DELAY 7200;       // 延时2小时
12         * END;              // 脚本结束
13         */
14
15        // 构造指令集语义树,实际情况会交给语法解析器(Evaluator or Parser)。
16        Expression sequence = new Sequence(Arrays.asList(
17            new Move(500, 600), 
18            new Repetition(
19                new Sequence(
20                    Arrays.asList(new LeftClick(), 
21                    new Delay(1))
22                ),
23                5
24            ), 
25            new RightDown(),
26            new Delay(7200)
27        ));
28
29        sequence.interpret();
30        /*打印输出
31            移动鼠标:【500,600】
32            按下鼠标:左键
33            松开鼠标:左键
34            系统延迟:1秒钟
35            按下鼠标:左键
36            松开鼠标:左键
37            系统延迟:1秒钟
38            按下鼠标:左键
39            松开鼠标:左键
40            系统延迟:1秒钟
41            按下鼠标:左键
42            松开鼠标:左键
43            系统延迟:1秒钟
44            按下鼠标:左键
45            松开鼠标:左键
46            系统延迟:1秒钟
47            按下鼠标:右键
48            系统延迟:7200秒钟
49         */
50    }
51}

注意看第16行,仔细参照注释中的脚本并实例化我们的语义树结构,最后只需调用根节点的interpret()方法即可完成整个解释工作。其实对于这个脚本转语义树的工作我们完全可以自己实现一个Evaluator来分析这段脚本并生成语义树(类似于编译的过程),由于这并不属于解释器模式的范畴,所以我们就不混淆进来了,这里我们留给读者朋友自己实现。

终于,“耗子精”有了脚本识别的能力,并顺利对接鼠标驱动,帮我们自动完成升级,玩家再也不用没日没夜地做那些无聊至极的重复动作了,并且后期如果需要更多的功能还可以对表达式继续进行扩展(比如对键盘指令的解释),我们只需优雅地植入语义树即可,就这么简单。正是因为我们对语言的语法解析、表达式抽象化,关系结构化,使得让翻译工作变得即插即用,解释器模式不但提高了代码的易读性、易用性、可维护性,更重要的是对未来语言变化的可扩展性。

语言是可以拆解的,句子是可以包括从句(子句)或单词的,单词是具有终极原子性的,它们统统重复出现。

原文地址:https://www.cnblogs.com/javazhiyin/p/10255287.html

时间: 2024-11-16 12:09:57

设计模式是什么鬼(解释器)的相关文章

大话设计模式Python实现-解释器模式

解释器模式(Interpreter Pattern):给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子. 下面是一个解释器模式的demo: 1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 __author__ = 'Andy' 5 """ 6 大话设计模式 7 设计模式--解释器模式 8 解释器模式(Interpreter Pattern):给定一个语言,定义它的文法的一种

设计模式漫谈之解释器模式

今天5月1号了,回郑州,在家待了三天.打了几天手游,花了不少钱.家里准备再盖座房子,我也应该为家里做些贡献.真他娘累,30多了,单身,现在已有些恐婚了.近期有空要做个记账软件,否则钱花的太快了.这个月,20多号还要软考,我这应该是考不过了,基本放弃了.老板原说涨3K,又变成2K,坑,套路. 近期我写的代码有上层到github和csdn code中,这种托管代码平台绝对是程序员的福利. 闲话少扯,继续说设计模式,从今天起就开始说行为模式.刚看到C#版本都更新到8.0了,让我意识到,编程思想很重要,

Java进阶篇设计模式之九----- 解释器模式和迭代器模式

前言 在上一篇中我们学习了行为型模式的责任链模式(Chain of Responsibility Pattern)和命令模式(Command Pattern).本篇则来学习下行为型模式的两个模式, 解释器模式(Interpreter Pattern)和迭代器模式(Iterator Pattern). 解释器模式 简介 解释器模式顾名思义,就是对某事物进行解释.给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器.客户端可以使用这个解释器来解释这个语言中的句子. 解释器模式

设计模式--19、解释器模式

定义:给定一种语言,定义他的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中句子. 类型:行为类模式 类图: 解释器模式是一个比较少用的模式,本人之前也没有用过这个模式.下面我们就来一起看一下解释器模式. 解释器模式的结构 抽象解释器:声明一个所有具体表达式都要实现的抽象接口(或者抽象类),接口中主要是一个interpret()方法,称为解释操作.具体解释任务由它的各个实现类来完成,具体的解释器分别由终结符解释器TerminalExpression和非终结符解释器Nontermi

【设计模式】15.解释器模式

1 package com.shejimoshi.behavioral.Interpreter; 2 3 4 /** 5 * 功能:演奏文本 6 * 时间:2016年3月3日上午9:26:19 7 * 作者:cutter_point 8 */ 9 public class PlayContext 10 { 11 private String text; 12 13 public PlayContext() 14 { 15 } 16 17 public PlayContext(String tex

[设计模式-行为型]解释器模式(Interpreter)

一句话 看起来是用来解释一种语言的文法.(类似不同的解释器子类解释不同的字符) 和编译器类似的解释器, 实际状况可能使用的比较少. 概括 解析 INTERPRETER-俺有一个<泡MM真经>,上面有各种泡MM的攻略,比如说去吃西餐的步骤.去看电影的方法等等,跟MM约会时,只要做一个Interpreter,照着上面的脚本执行就可以了. 解释器模式:给定一个语言后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器.客户端可以使用这个解释器来解释这个语言中的句子.解释器模式将描述怎样在有了

设计模式学习笔记——解释器模式

1.特点:若某特定问题发生的频率足够高时考虑,需定义文法表示与解释器本身. 2.概念:在软件开发特别是DSL开发中常常需要使用一些相对较复杂的业务语言,如果业务语言使用频率足够高,且使用普通的编程模式来实现会导致非常复杂的变化,那么就可以考虑使用解释器模式构建一个解释器对复杂的业务语言进行翻译.这种做法虽然效率相对较低,但可以允许用户使用自定义的业务语言来处理逻辑,因此在效率不是关键问题的场合还是较为有用的. 3.类图: 4.程序实现: public static class ChineseEn

《Java设计模式》之解释器模式

解释器模式是类的行为模式.给定一个语言之后,解释器模式能够定义出其文法的一种表示,并同一时候提供一个解释器. client能够使用这个解释器来解释这个语言中的句子. 解释器模式的结构 以下就以一个示意性的系统为例,讨论解释器模式的结构.系统的结构图例如以下所看到的: 模式所涉及的角色例如以下所看到的: (1)抽象表达式(Expression)角色:声明一个全部的详细表达式角色都须要实现的抽象接口.这个接口主要是一个interpret()方法,称做解释操作. (2)终结符表达式(Terminal

设计模式(15)--Interpreter(解释器模式)--行为型

1.模式定义: 解释器模式是类的行为模式.给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器.客户端可以使用这个解释器来解释这个语言中的句子. 2.模式特点: 解释器模式在实际的系统开发中使用的非常少,因为它会引起效率.性能以及维护等问题,一般在大中型的框架型项目能够找到它的身影,比如一些数据分析工具.报表设计工具.科学计算工具等等,若你确实遇到“一种特定类型的问题发生的频率足够高”的情况,准备使用解释器模式时,可以考虑一下Expression4J.MESP(Math