TypeScript设计模式之解释器

看看用TypeScript怎样实现常见的设计模式,顺便复习一下。 学模式最重要的不是记UML,而是知道什么模式可以解决什么样的问题,在做项目时碰到问题可以想到用哪个模式可以解决,UML忘了可以查,思想记住就好。 这里尽量用原创的,实际中能碰到的例子来说明模式的特点和用处。

解释器模式 Interpreter

特点:使用给定语法来解释一段内容。

用处:管理类系统经常会定义一些搜索语句格式来使用户方便搜索库里的内容,这时就可以考虑用解释器来翻译执行这些语句。

注意:适合相对简单的语法。

解释器模式通过把一段表达式拆开成很多个,分为不同的解析类,一个一个的去解析并执行,这过程中经常会用Context来保存解析过程的信息。 这种解释器的优点在于各种表达式的解析相对独立,要加入新的规则也不会影响现有的解析。缺点也很明显,一个表达式一个类,复杂语法或复合语法的话表达式数量就非常多,并且表达式之间也很难真正独立。

下面用TypeScript写一个简单正则表达式的解释器: 要解释的表达式有:{}, [], \d, ^, $这几种。

先建立一个Expression接口,所有解释器都实现这个接口:

interface Expression{
    interpret(context: Context);
}

可以看到接口里用到了一个Context,这个用来保存解析时的一些数据和进度,包含: pattern: 整个表达式 currentPatternIndex: 当前正在验证的表达式的位置 lastExpression: 上一个表达式,用于{}解析 text: 需要验证的文本 currentTextIndex: 当前验证到text里的哪个字符的位置 isMatch: 是否匹配成功

class Context{
    constructor(public pattern: string, public text: string){

    }

    currentTextIndex: number = 0;
    get currentText(): string{
        return this.text[this.currentTextIndex];
    }

    currentPatternIndex: number = 0;
    lastExpression: string;
    get currentPatternExpression(): string{
        return this.pattern[this.currentPatternIndex];
    }

    isMatch: boolean;
}

现在分别给那些符号写解析类:

// 开始符号:^
class BeginExpression implements Expression{
    interpret(context: Context){
        context.isMatch = context.currentPatternIndex === 0;
        context.currentPatternIndex++;
    }
}

// 结束符号:$
class EndExpression implements Expression{
    interpret(context: Context){
        context.isMatch = context.currentTextIndex === context.text.length;
        context.currentPatternIndex++;
    }
}

// 反斜杠:\d,只支持\d
class BslashExpression implements Expression{
    interpret(context: Context){
        if(context.pattern[context.currentPatternIndex + 1] !== 'd'){
            throw new Error('only support \\d');
        }

        let target = context.currentText;
        context.lastExpression = '\\d';
        context.isMatch = Number.isInteger(Number.parseInt(target));
        context.currentPatternIndex+=2;
        context.currentTextIndex++;
    }
}

// 中括号:[]
class BracketExpression implements Expression{
    interpret(context: Context){
        let prevIndex = context.currentPatternIndex;
        while(context.pattern[++context.currentPatternIndex] !== ']'){
            if(context.currentPatternIndex+1 === context.pattern.length){
                throw new Error(`miss symbol ]`);
            }
        }
        let expression = context.pattern.substr(prevIndex+1, context.currentPatternIndex - prevIndex - 1);
        let target = context.currentText;

        context.lastExpression = `[${expression}]`;
        context.isMatch = [...expression].indexOf(target) > -1;
        context.currentPatternIndex++;
        context.currentTextIndex++;
    }
}

// 大括号:{}
class BraceExpression implements Expression{
    interpret(context: Context){
        let endIndex = context.currentPatternIndex;
        while(context.pattern[++endIndex] !== '}'){
            if(i+1 === context.pattern.length){
                throw new Error(`miss symbol }`);
            }
        }
        let expression = context.pattern.substr(context.currentPatternIndex + 1, endIndex - context.currentPatternIndex - 1);
        let num = Number.parseInt(expression);
        if(!Number.isInteger(num)){
            throw new Error('{} only support number');
        }
        let newExpression = '';
        for(let i=1;i<num;i++){
            newExpression += context.lastExpression;
        }
        context.pattern = context.pattern.substr(0, context.currentPatternIndex) + 
                                     newExpression + 
                                     context.pattern.substr(endIndex+1);
    }
}

// 普通字符
class StringExpression implements Expression{
    interpret(context: Context){
        context.lastExpression = context.currentPatternExpression;
        context.isMatch = context.currentPatternExpression === context.currentText;
        context.currentPatternIndex++;
        context.currentTextIndex++;
    }
}

有了这些解释器,现在解析表达式就很轻松了:

class Regex{
    mapping: {[key:string]: Expression} = {
                                         '^': new BeginExpression(),
                                         '$': new EndExpression(),
                                         '{': new BraceExpression(),
                                         '[': new BracketExpression(),
                                         '\\':new BslashExpression(),
                                        }; // 这是一个表达式-解释器的映射表
    stringExp: Expression = new StringExpression();        

    constructor(private pattern: string){

    }

    IsMatch(text: string): boolean{
        let context = new Context(this.pattern, text);
        
        for(context.currentPatternIndex=0;context.currentPatternIndex<context.pattern.length;){
            let symbol = this.mapping[context.currentPatternExpression];
            symbol ? symbol.interpret(context) : this.stringExp.interpret(context); //通过找到对应的解释器来解释匹配文本
            if(!context.isMatch){
                break;
            }
        }
        return context.isMatch;
    }
}

写个手机号码验证的正则表达式测试一下:

let pattern = '/^1[34578]\d{9}$/';
let regex = new Regex(pattern);

let text = '13712345678';
console.log(`match ${text}: ${regex.IsMatch(text)}`); // 正常手机号:成功

text = '1371234567p';
console.log(`match ${text}: ${regex.IsMatch(text)}`); // 手机号里有字母:失败

text = '137123456789';
console.log(`match ${text}: ${regex.IsMatch(text)}`); // 多了一位:失败

text = '1371234567';
console.log(`match ${text}: ${regex.IsMatch(text)}`); // 少了一位:失败

结果符合预期,可以看到用解释器把表达分开解释的好处很明显,各个解释器互不干扰,主体部分调用这些解释器分别进行解释就可以了,非常方便。 当然这也只是处理简单的语法,如果语法很复杂就需要考虑引入分析引擎或编译器了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏木木玲

JVM中 对象的内存布局 以及 实例分析

1888
来自专栏Danny的专栏

未经处理的异常在 System.Data.dll 中发生。其他信息:在应使用条件的上下文(在 '***' 附近)中指定了非布尔类型的表达式。

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/...

822
来自专栏刘望舒

Android内存优化(三)避免可控的内存泄漏

前言 内存泄漏向来都是内存优化的重点,它如同幽灵一般存于我们的应用当中,有时它不会现身,但一旦现身就会让你头疼不已。因此,如何避免、发现和解决内存泄漏就变得尤为...

17610
来自专栏coder修行路

Go基础之--操作Mysql(二)

在上一篇文章中主要整理了Golang连接mysql以及一些基本的操作,并进行了大概介绍,这篇文章对增删查改进行详细的整理 读取数据 在上一篇文章中整理查询数据...

4136
来自专栏chenssy

【死磕Java并发】—–J.U.C之Java并发容器:ConcurrentLinkedQueue

要实现一个线程安全的队列有两种方式:阻塞和非阻塞。阻塞队列无非就是锁的应用,而非阻塞则是CAS算法的应用。下面我们就开始一个非阻塞算法的研究:Coucurren...

3375
来自专栏阿杜的世界

【译】Java 8的新特性—终极版1. 简介2. Java语言的新特性3. Java编译器的新特性4. Java官方库的新特性5. 新的Java工具6. JVM的新特性7. 结论8. 参考资料

前言: Java 8 已经发布很久了,很多报道表明Java 8 是一次重大的版本升级。在Java Code Geeks上已经有很多介绍Java 8新特性的文章,...

794
来自专栏技术墨客

JVM与字节码——类的方法区模型 原

这是一段平凡得不能再平凡的Java代码,稍微有点编程语言入门知识的人都能理解它表达的意思:

522
来自专栏北京马哥教育

AWK处理日志入门

前言 这两天自己挽起袖子处理日志,终于把AWK给入门了。其实AWK的基本使用,学起来也就半天的时间,之前总是靠同事代劳,惰性呀。 此文仅为菜鸟入门,运维们请勿...

3074
来自专栏GreenLeaves

SQL学习之使用常用函数处理数据

一、在介绍使用函数处理数据前,先说下使用DBMS(数据库管理系统)处理数据所带来的问题! 1、与几乎所有的DBMS都同等的支持SQL语句(如SELECT)不同,...

1775
来自专栏GreenLeaves

oracle 层次化查询(生成菜单树等)

1、简介:Oracle层次化查询是Oracle特有的功能实现,主要用于返回一个数据集,这个数据集存在树的关系(数据集中存在一个Pid记录着当前数据集某一条记录的...

1848

扫码关注云+社区