HIT软件构造第五章第三节知识点总结


  接着上一篇来讨论,本文章的重点是面向可维护性的构造技术。

三.面向可维护性的构造技术

1.基于状态的编程

  很多情况下我们程序中都面临着大量状态的改变。
  我们当然可以通过各种if else语句来实现对应的转换,但是这样兼容性很差,如果加入一个状态,则对应的每块代码都要去改动,可维护性很差,如下图伪代码所示。
在这里插入图片描述
  因此下面我们介绍一些模式,保证状态转移的可维护性更高。

A.基于自动机的编程

  自动机的理论不在这里细说,但是我们可以把状态转换看成DFA不断读输入的过程。
  从而,我们只需要一个二维数组来储存一个状态转移函数,这样当我们输入的时候,我们根据当前的状态,进行查表就可以返回一个新的状态。
  这样操作简单清晰,但是还有一个缺点,就是当增加状态时,我们还是要对二维数组整体上做出改变,毕竟是n*n变为了(n+1)*(n+1),要做不少操作。

B.状态模式

  以上的操作都是把匹配当前状态,和根据当前状态来得到新的状态绑定到一起,从而就是个二维的复杂度。我们能否将其分离呢?
  状态模式就是分离了这两块,把每个状态看为一个类,且由于状态是可以被共享的,它就是个属性,因此我们完全可以使用单例模式(其实就类似于全静态),然后在类中实现转移函数,这就是一个简单的switch case了。再用一个接口去联合这些状态,这样我们就可以用“当前状态”(我们甚至不需要知道目前是什么状态),只需做输入,程序就可以自动确定是哪一个类下进行转移了。
  下面我想用实验3的状态来举例子。下图是转移表,可见状态数量不少,用前面的办法会特别冗长。
在这里插入图片描述
  因此我们可以创建一个state接口,让所有的状态是它的子类,这里规定了move方法和getstate方法。
在这里插入图片描述
  这是其中一个子类,是waiting状态,可见为单例模式。这里的静态工厂方法是public的,意味着我们可以直接创建一个位于这个状态的实例,就像实验中的那样,最开始默认的就应该是这个等待状态。move方法就是通过接受字符,来返回新的状态类的对象。
在这里插入图片描述
  下图是另一个子类,其他的都差不多了,只是静态工厂方法是private的了,代表着这些类只能通过状态转移而得到。
在这里插入图片描述
  最后我们进行一个封装即可。对外部,我们只用这个Context类,前面的内容都是按照状态的转移逻辑实现好了的,这个类的操作就是用来与用户的输入做连接。
在这里插入图片描述
  最后就是实现好后的测试(测试已全部通过)。可见我们只需要调用move方法,全程并不需要显式的利用当前状态的信息,变量永远是context类的一个对象。
在这里插入图片描述

C.备忘录模式

  顾名思义,其实就是为了记录以前的状态,准备用于以后的回滚。是一个附加的功能。
  那为了实现相应的功能,就要有一个备忘录的类,可以记录某个状态和对应的信息;之后,我们在我们正常的转换类中,加入save和restore方法,用来存入备忘录和从备忘录取出即可。例子如下图所示
在这里插入图片描述
  虽然我们已经实现了功能,但我们可能要存储顺次多个状态,因此我们还要建一个类,用于存储一个备忘录列表,从而实现任意的记录,而不是只记录一个。下图的Caretaker就是一个备忘录列表。这里的getMemeto方法,如果是想返回最后存储的状态,那就是问号处为mementos.size()-1即可。

在这里插入图片描述

2.语法驱动的构造

  我们很多时候需要从外部读入一系列文本数据,抽取对应的信息。然而读入的文本总是有一定格式的,可能还有很多限制,因此我们粗暴的去解析是非常麻烦的,因此,我们主要是讲与正则表达式相关的内容。

(1).语法组成

  我们想要解析文本,首先我们要规定一个格式。其实这里的内容,就和形式语言与自动机,上下文无关语言那一章的解析逻辑相同,因此就不细说,这里就给出几个例子。比如下图为简化版的,解析英语句子的例子。
在这里插入图片描述
  下图是另一种类似的形式,是解析域名的例子。
在这里插入图片描述
  我们把可以分解的内容称为非终止节点,而不能再向下拆分的内容成为终止节点。可见我们就可以按照我们就可以构建出我们想要的拆分逻辑。但是这仅仅是我们的逻辑,机器如何解析呢?这就引出了下面的内容。

(2).正则语法与正则表达式

  根据上文所讲,我们可以把对应的内容画成树的结构。如果根节点完全可以由叶节点的组合来刻画,那就被称为是正则的(显然如果出现自递归或循环递归的可能,则这个上下文无关语言不会是正则语言)。
  如果某个语言是正则的,它就有对应的正则表达式了。比如上文的url。
  但是问题还是,对非叶节点如何解析呢。其实word的意思就是一个或多个的英文字母,我们如何让计算机去匹配呢?因此,我们需要一个parser用于解析我们所写的正则表达式。例如我们可以自己规定,a*意思就是字母a的克林闭包,那我们实现的parser中就要实现对应的解析操作,这里就不细说了。

(3).java中正则表达式的parser

  之所以不用细说自己构建parser的事情了,是因为java中已经自带了一种解析方式,被称为regex,我们不需要理解最底层的解析方式。
  当然java中正则表达式的语法规定很多,但是我们只需要使用的时候去查询它的规定即可。这里只给出部分的内容。详细的内容和使用语法可咨询:我是传送门
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
  根据这套体系,我们要匹配一个英文单词,我们就使用[a-z]+即可(完全为叶节点,也就是没有它识别不了的),也就是说我们已知了这套体系,我们再写对应的内容,这也就是上文的例子中各种奇形怪状符号的由来。现在就实现了判断输入是否匹配的效果。

(4).总结

  其实就如下图所示,当parser已经确定好的时候,我们就列出我们的语法树,之后根据parser对符号的解析来确定是否符合要求(解析的过程不需要掌握)。其中正则表达式更好解析,因为其实就是一串由parser中定义好的符号组成,而普通的上下文无关语法会更麻烦,要通过语法树的叶子来顺次分析。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值