大纲
基于状态的构建
- 基于自动机的编程
- 设计模式:Memento提供了将对象恢复到之前状态的功能(撤消)。
- 设计模式:状态允许对象在其内部状态改变时改变其行为。
表驱动结构*
基于语法的构建
- 语法和解析器
- 正则表达式(regexp)
- 设计模式:解释器实现一种专门的语言。
基于状态的构建
基于状态的编程是一种编程技术,它使用有限状态机(FSM)来描述程序行为,即使用“状态”来控制程序的流程。
使用有限状态机来定义程序的行为,使用状态来控制程序的执行
- 例如,在电梯的情况下,可能会停止,向上移动,向下移动,停止,关闭门并打开门。
这些都被认为是一个状态,接下来发生的事情是由电梯的当前状态决定的。
根据当前状态,决定下一步要执行什么操作,执行操作之后要转移到什么新的状态
- 如果电梯刚刚关好,接下来会发生什么情况? 它可以停止,向上移动或向下移动。
- 当电梯停下时,你预计下一个动作是门打开,向上移动或向下移动。
(1) 基于自动机的编程
基于自动机的编程是一种编程模式,其中程序或其一部分被认为是有限状态机(FSM)或任何其他形式自动机的模型。
- 将程序视为有限自动机。
- 每台自动机可以一次接受一个“步骤”,程序的执行分解为单独的步骤。
- 这些步骤通过改变代表“状态”的变量的值来相互沟通。
- 程序的控制流程由该变量的值决定。
应用程序设计方法应与控制系统(Automata System)的设计类似。
核心思想:将程序看作是一个有限状态自动机,侧重于对“状态”和“状态转换”的抽象和编程
程序的执行被分解为一组自动执行的步骤
- 每个步骤实际上是一个代码段的执行(所有步骤都相同),它有一个入口点。 这样的部分可以是功能或其他例程,或者只是一个循环体。
各步骤之间的通讯通过“状态变量”进行
- 在任何两个步骤之间,程序不能有其状态的隐式分量,例如本地(堆栈)变量值,返回地址,当前指令指针等。
- 在进入自动机步骤的任何两个时刻取得的整个程序的状态只能在被认为是自动机状态的变量值中有所不同。
如何实施?
基于自动机的代码的整个执行过程都是自动机步骤的一个(可能是显式的)循环。
“状态”变量可以是简单的枚举数据类型,但可以使用更复杂的数据结构。
一种常见的技术是创建一个状态转换表,一个包含表示每种可能状态的行的二维数组,以及表示输入参数的列。
- 行和列满足的表格的值是在符合两个条件的情况下机器应转换到的下一个状态。
应用领域
高可靠性系统
- 军事应用
- 航空航天工业
- 汽车行业
嵌入式系统
移动系统
可视化系统
Web应用程序
客户端服务器应用程序
(2) State Pattern
状态模式 (behavioral pattern)
假设一个对象总是处于几个已知状态之一
对象所处的状态决定了几种方法的行为
可以在每种方法中使用if / case语句
更好的解决方案:状态模式
有一个状态对象的引用
- 通常,状态对象不包含任何字段
- 更改状态:更改状态对象
- 方法委托给状态对象
状态模式注释
可以为每个状态类的实例使用单例
- 状态对象不封装状态,所以可以共享 - 不可变
轻松添加新的状态
- 新状态可以扩展其他状态
- 仅覆盖选定的功能
(3) Memento Pattern
备忘录模式 (behavioral)
意图
- 在不违反封装的情况下,捕获并外部化对象的内部状态,以便稍后可以将对象返回到此状态。
- 封装“检查点”功能的魔术饼干(cookie)。
- 促进撤消或回滚到完整的对象状态。
问题:需要将对象恢复到以前的状态(例如“撤销”或“回滚”操作)。
记住对象的历史状态,以便于“回滚”
备忘录设计模式定义了三种不同的角色:
- 发起者 - 知道如何保存自己的对象。需要“备忘”的类
- 看守者 - 知道发起者需要保存和恢复的原因和时间的对象。添加发起者的备忘记录和恢复
- 备忘录 - 由发起人撰写和阅读的锁盒,由看守人管理。备忘录,记录发起者对象的历史状态
*表驱动的构造
什么是“表驱动”?
表驱动方法是一种使用表来查询信息而不是使用逻辑语句(例如if-else和switch-case)的模式。
在简单情况下,使用逻辑语句更快更容易,但随着逻辑链变得更复杂,表驱动的代码:
- 比复杂的逻辑简单
- 更容易修改
- 更高效
表驱动编程的核心思想:将代码中复杂的if-else和switch-case语句从代码中分离出来,通过“查表”的方式完成,从而提高可维护性
查找东西的方法
• 直接访问
• 索引访问
• 阶梯访问
选择其中之一取决于数据的性质以及数据域的大小。
(1) 直接访问表
简单
- 您只是通过一个或多个索引“查找事物”。
- 与所有查找表一样,直接访问表取代了更复杂的逻辑控制结构。
- 他们是“直接进入”的,因为你不必跳过任何复杂的循环来找到你想要的信息。
(2) 索引访问表
有时直接索引是一个问题,特别是如果可能的值域很大。
例如,如果您想使用产品ID(8位数字),并制作一张映射200个产品的表格。
查找索引与直接索引
索引元素很小(整数),值可以有效地大(只有你需要的那么多),比如字符串(名字,描述,错误信息等)。
多个索引可以访问相同的数据(员工信息可以按名称,聘用日期,出售等进行映射)
可维护 - 从应用程序界面隔离查找方法。
(3) 阶梯访问表
表格中的条目对数据范围有效,而不适用于不同的数据点
关键点
表格提供了复杂逻辑和继承结构的替代方案。 如果您发现程序的逻辑或继承树让您感到困惑,那么问问自己是否可以通过查找表进行简化。
使用表格的一个关键考虑因素是决定如何访问表格。 您可以通过直接访问,索引访问或阶梯访问来访问表。
使用表格的另一个关键考虑因素是决定放入表格的具体内容。
语法驱动的构造(Grammar-based construction)
基于语法的构建目标
理解语法生成和正则表达式操作符的思想
能够读取语法或正则表达式,并确定它是否匹配一系列字符
能够编写语法或正则表达式来匹配一组字符序列并将其解析为数据结构
基于字符串/流的I / O
某些程序模块以字节序列或字符序列的形式输入或输出输出,当它存储在内存中时称为字符串,或者在流入或流出模块时称为字符串。 有一类应用,从外部读取文本数据,在应用中做进一步处理。
具体来说,一个字节或字符序列可能是:
- 磁盘上的文件,这种情况下,规范称为文件格式,程序需读取文件并从中抽取正确的内容
- 通过网络发送的消息,在这种情况下,规范是有线协议从网络上传输过来的消息,遵循特定的协议
- 用户在控制台上键入的命令,在这种情况下,规范是命令行界面,用户在命令行输入的指令,遵循特定的格式
- 存储在内存中的字符串,也有格式需要
语法的概念
对于这些类型的序列,语法的概念是设计的一个好选择:
- 它不仅可以帮助区分合法序列和非法序列,还可以将序列解析为程序可以使用的数据结构。 使用语法判断字符串是否合法,并解析成程序里使用的数据结构
- 从语法产生的数据结构通常是递归数据类型。通常是递归的数据结构
正则表达式
- 这是一个广泛使用的工具,用于许多字符串处理任务,需要反汇编字符串,从中提取信息或进行转换。
解析器生成器是一种将语法自动转换为该语法的解析器的工具。 根据语法生成它的解析器,用于后续的解析
(1) 语法的组成部分
终结:语法中的文字串
为了描述一串符号,无论它们是字节,字符还是其他类型的从固定集合中抽取的符号,我们都使用称为语法的紧凑表示法。
语法定义了一组字符串。用语法定义一个“字符串”
- 例如,URL的语法将指定HTTP协议中合法URL的一组字符串。
文法中的文字被称为终结节点,叶节点
- 它们被称为终结,因为它们是代表字符串结构的解析树的叶子。语法解析树的叶子节点
- 他们没有孩子,不能再进一步扩大。 无法再往下扩展
- 我们通常用引号将终结写入,如'http'或':'。 通常表示为字符串
语法中的非终结者与生产者
一个语法由一组产品描述,每个产品定义一个非终结非终止节点
- 非终结符就像一个变量,它表示一组字符串,而生成则表示该变量根据其他变量(非终结符),运算符和常量(终结)的定义。 遵循特定规则,利用操作符,终止节点和其他非终止节点,构造新的字符串
- 非终结符是表示字符串的树的内部节点。
语法中的生产具有这种形式
- 非终结符:: =终结,非终结符和运算符的表达式
语法的非终结点之一被指定为根。
- 语法识别的字符串集合是匹配根非终结符的字符串。
- 这个非终结者通常被称为root或start。根节点
(2) 语法中的操作符
三个基本的语法运算符
生产表达中最重要的三个操作是:
- 连接,不是由一个符号表示,而是一个空格:x :: = y z an x是一个y,后跟一个z
- 重复,用表示:x :: = y x是零或更多y
- 联合,也称为选择,由|:x :: = y |表示 z an x是y或z
(5) 正则语法和正则表达式
正则语法
正则语法有一个特殊的性质:通过用右端代替每个非终结符(除了根结尾之外),可以将它缩减为单根生成,只有终结和操作符在右侧。
正则语法:简化之后可以表达为一个产生式而不包含任何非终止节点
正则表达式(正则表达式)
终结和操作符的简化表达式可以用更紧凑的形式写成,称为正则表达式。
正则表达式避免了终结周围的引号以及终结和运算符之间的空格,因此它只包含终结字符,用于分组的括号和运算符字符。去除引号和空格,从而表达更简洁(更难懂)
- 正则表达式比原始语法的可读性要低得多,因为它缺少记录每个子表达式意义的非终结符名称。
- 但是一个正则表达式的实现很快,并且有很多支持正则表达式的编程语言的库。
正则表达式中的一些特殊运算符
. 任何单个字符
d任意数字,与[0-9]相同
是任何空格字符,包括空格,制表符,换行符
w任何单词字符,包括字母和数字
,(,), *, +,...转义一个操作符或特殊字符,以便它按字面顺序匹配
上下文无关文法
通常,可以用我们的语法系统表达的语言称为上下文无关的。
- 并非所有的上下文无关语言也是正则的; 也就是说,有些语法不能简化为单一的非递归生成。
- HTML语法是上下文无关的,但不是正则的。
大多数编程语言的语法也是无上下文的。
一般来说,任何具有嵌套结构的语言(如嵌套括号或大括号)都是上下文无关的,但不是正则的。
(6) *解析器
语法,解析器和解析器生成器
目标:
- 能够将语法与解析器生成器结合使用,将字符序列解析为解析树
- 能够将分析树转换为有用的数据类型
解析器将输入文本转为解析树
解析器需要一系列字符并尝试将该序列与语法进行匹配。 解析器:输入一段文本,与特定的语法规则建立匹配,输出结果
解析器通常会生成一个解析树,该解析树显示如何将语法生成扩展为与字符序列匹配的句子。 解析器:将文本转化为解析树
- 解析树的根是语法的起始非终结符。
- 解析树的每个节点都扩展为语法的一个生成。
解析的最后一步是对这个分析树做一些有用的工作。 利用产生的分析树,进行下一步的处理
表示语言表达式的递归抽象数据类型称为抽象语法树(AST)。
解析器生成器根据语法定义生成解析器
解析器生成器是一种读取语法规范并将其转换为可识别语法匹配的Java程序的工具。
更广泛地:
- 解析器生成器是一种编程工具,它根据某种形式的语言形式描述创建解析器,解释器或编译器。
- 输入可能是一个文本文件,其中包含用BNF或EBNF编写的定义编程语言语法的语法。 - 输出是语法分析器的一些源代码。
Backus Normal Form(BNF)巴克斯范式
1959年6月,Backus Normal Form(BNF)首次提出,以递归形式描述语言的各种成分,凡遵守其规则的程序就可保证语法上的正确性。
- 经过Peter Naur的改进与完善以及Niklaus Wirth的扩充,形成了EBNF(扩展BNF),也就是目前使用的BNF。
- 经Donald Knuth的建议,BNF中的N变成了Naur(Backus-Naur Form)。
Grammar定义语法规则(BNF格式的文本),Parser generator根据 语法规则产生一个parser,用户利用parser来解析文本,看其是否符 合语法定义并对其做各种处理(例如转成parse tree)
(7) 在Java中使用正则表达式
用于正则表达式处理的java.util.regex
java.util.regex包主要由三个类组成:
- 一个Pattern对象是一个正则表达式的编译后的表示。 Pattern类不提供公共构造函数。 要创建一个模式,你必须首先调用其公共静态编译方法之一,然后返回一个Pattern对象。 这些方法接受一个正则表达式作为第一个参数。模式是对正则表达式的正则表达式进行编译之后得到的结果
- 匹配对象是解释模式并对输入字符串执行匹配操作的引擎。 像Pattern类一样,Matcher没有定义公共构造函数。 您通过调用Pattern对象上的匹配器方法来获得Matcher对象。 Matcher:利用Pattern对输入字符串进行解析
- PatternSyntaxException对象是指示正则表达式模式中的语法错误的未经检查的异常。
正则表达式在编程语言中非常有用。
- 在Java中,可以使用正则表达式来处理字符串(例如String.split,String.matches,java.util.regex.Pattern)。
- 它们作为现代脚本语言(如Python,Ruby和Javascript)的一流功能而内置,您可以在许多文本编辑器中将它们用于查找和替换。
(8)* Interpreter
解释器模式
解释器模式提供了评估语言语法或表达的方法。
意图
- 给定一种语言,为其语法定义一个表示法,以及一个使用表示法来解释语言句子的解释器。 给定一种语法,定义该语法的程序内部表示,形成该语法的解释器,将遵循语法规则的文本解释成程序内部的表示(例如一组对象)
- 将一个领域映射到一种语言,将语言映射到一种语法,将语法映射为一种等级面向对象设计。解释语法的“引擎”:遵循语法的文本⇒OO表示
- 用于定义语法,标记输入并存储它。
实施
- 它使用复合模式来表示语法。
因为语法通常形成树结构,故使用复合模式来表达遵循语法的内容。
- 它定义了行为,而复合只定义了结构。
针对该层次化树形结构,定义了一组行为来处理结构中的不同类型节点
解释器与语法+解析器
语法+解析器
- 语法由BNF等形式定义
- 解析器读取用户输入的待解析的文本,判定其是否与语法匹配,并转为符合语法的解析树,交给其他功能做后续处理 - 可用于高度复杂的语法规则
解释器模式
- 语法由程序员手工定义为一组接口/类及其之间的关系,对语法的解释(即解析器)由此组的类的内部操作(Interpret())负责
- 每条语法规则(生产)都要定义相应的类 - 相当于开发一个简单的解析器 - 用户使用的时候,调用这个类类完成对输入文本的解释(这里的“解释”,其实相当于 “翻译”) - 只适用于简单的语法规则,过于复杂的语法就需要引入大量的类
总结
机器处理的文本语言在计算机科学中无处不在。
语法是描述这种语言的最流行的形式
正则表达式是语法的一个重要子类,可以在不递归的情况下表达。
减少错误保证安全
- 语法和正则表达式是字符串和流的声明性规范,可以由库和工具直接使用。
- 这些规范通常比手工分析代码更简单,更直接,更不容易出错。
容易明白
- 语法以比手写解析代码更易于理解的形式捕获序列的形状。
- 正则表达式,唉,往往不易理解,因为它们是可能是一个更容易理解的正则语法的简化形式。
准备好改变
- 语法可以很容易地编辑,但不幸的是,正则表达式很难改变,因为复杂的正则表达式是神秘而难以理解的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。