面向可维护性的构造技术
因为lab3和各种考试鸽了两周 希望可以补回来
状态驱动的编程
状态驱动的编程使用有限状态机来定义程序的行为,使用状态来控制程序的执行。根据当前状态,决定下一步要执行什么操作、执行操作之后要转移到什么新的状态。
基于自动机的编程
将程序视为有穷自动机,将程序的执行分解为一系列的步骤,每个步骤是一段代码片段的执行,这段代码片段只有一个入口点。可以是函数、其他路径、或仅仅是一个循环体。每一个步骤与其他步骤的联系通过改变代表“状态”的变量值完成,程序的控制流由该变量值决定。程序在任意两个时刻的状态,只能有表示“状态”的变量取值不同。程序的执行可以看作是各自动步骤的不断循环。
使用枚举类型定义状态。
使用二维数组定义状态转换表。
与控制系统的设计思想类似。侧重于对“状态”及“状态转换”的抽象和编程。
状态模式 State Pattern
behavioral pattern
class Context {
State state; //保存对象的状态
//设置初始状态
public Context(State s) {state = s;} //设置delegation关系
//接收外部输入,开启状态转换
public void move(char c) {
state = state.move(c); //将改变状态的“动作”delegate到state对象,每次状态转换之后形成新状态,替换原状态
}
//判断是否达到合法的最终状态
public boolean accept() { return state.accept(); }
public State getState() { return this.state; }
}
//状态接口
public interface State {
State move(char c);
boolean accept();
}
状态类:
class State1 implements State {
static State1 instance = new State1(); //singleton模式
private State1() {}
public State move (char c) {
switch (c) {
case ‘a’: return State2.instance; //返回新状态的singleton实例
case 'b': return State1.instance;
default: throw new IllegalArgumentException();
}
}
public boolean accept() { return false; } //该状态非可接受状态
}
class State2 implements State {
static State2 instance = new State2();
private State2() {}
public State move (char c) {
switch (c) {
case 'a': return State1.instance;
case 'b': return State1.instance;
default: throw new IllegalArgumentException();
}
}
public boolean accept() {return true;}
}
public static void main(String[] args) {
Context context = new Context(State1.instance);
for (int i = 0; i < args.length; i++) {
context.move(args[i]);
if(context.accept())
break;
}
}
每个state类可以使用singletons 方式创建实例
方便添加新的类
备忘录模式 Memento Pattern
记住对象的历史状态,以便于“回滚”
例:
class Originator {
private State state;
public void setState(State state) { //状态转换功能
System.out.println("Originator: Setting state to " + state.toString());
this.state = state;
}
public Memento save() { //保存历史状态,delegate到memento实现
System.out.println("Originator: Saving to Memento.");
return new Memento(state);
}
public void restore(Memento m) { //恢复历史状态
state = m.getState();
System.out.println("Originator: State after restoring from Memento: " + state);
}
}
class Memento {
private State state;
public Memento(State state) {
this.state = state;
}
public State getState() {
return state;
}
}
class Caretaker {
private List<Memento> mementos = new ArrayList<>();
public void addMemento(Memento m) {
mementos.add(m);
}
public Memento getMemento() {
return mementos.get(mementos.size()-1);
}
}
表驱动的编程
将代码中复杂的if-else和switch-case语句从代码中分离出来,通过“查表”的方式完成,从而提高可维护性
private static double getPrice2(MovieType movieType, int daysRented) {
final double initialCharge[] = { 2, 0, 1.5 };
final double initialDays[] = { 2, 0, 3 };
final double multiplier[] = { 1.5, 3, 1.5 };
int intType = movieType.ordinal();
double price = initialCharge[movieType];
if (daysRented > initialDays[movieType])
price += (daysRented - initialDays[movieType]) * multiplier[movieType];
return price;
}
enum MovieType { Regular, NewRelease, Childrens };
直接查表
直接用索引查
例如:
int daysPerMonth[ ] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
days = daysPerMonth[month-1];
索引查表
如果数字范围很大,直接查表就比较难。比如:用八位数的产品id做一个能查200个产品的表。
1.索引元素可以很小,值可以很大
2.多个索引查找到同样的数据(雇员信息可以通过姓名、雇佣时间、薪水映射到)
3.易于维护:查找方法相互隔离
阶梯查表
表的入口可能是某一范围内的数据而不是具体某一值的数据
// set up data for grading table
float rangeLimit[] = { 50.0, 65.0, 75.0, 90.0, 100.0 };
String grade[] = { "F", "D", "C", "B", "A" };
int maxGradeLevel = grade.length – 1;
...
// assign a grade to a student based on the student's score
int gradeLevel = 0;
String studentGrade = "A" ;
while (( studentGrade == "A" ) && ( gradeLevel < maxGradeLevel )) {
if ( studentScore < rangeLimit[ gradeLevel ] ) {
studentGrade = grade[ gradeLevel ];
}
gradeLevel = gradeLevel + 1;
}
总结:表驱动的编程可以替代复杂的逻辑判断和继承结构。如果程序的逻辑或者继承关系比较混乱,可以尝试用表驱动编程简化。一个关键点是如何查表,另一个关键点是表中的内容放什么。
语法驱动的构造
读取有特定格式的数据,从中抽取正确的内容
语法的组成
用语法定义一系列字符
语法解析树的终止节点(叶节点):无法再向下扩展,通常表示为字符串。
语法解析树的非终止节点(产生式节点):遵循特定规则,利用操作符、终止节点和其他非终止节点构造新的字符串。是语法解析树的内节点。
非终止节点 := 一个终止节点、非终止节点和操作符的表达式
非终止节点之一是根节点
语法的操作符
1.连接 用空格表示
2.重复 用“”表示 (0或多次)
3.选择 用“|”表示
4.可选 用"?"表示 出现或不出现 例x ::= y?
5.一次或多次出现 用“+”表示
6.包含的字符集合 […]
7.不包含的字符集合 [^…]
优先级:,?,+ 最高,其次是连接,最低的是 |
通过括号调整优先级
例:匹配网址,如http://mit.edu/, http://alibaba.com/
url ::= ‘http://’ hostname ‘/’
hostname ::= word ‘.’ word
word ::= [a-z]+
语法中的递归
例1:匹配网址,如http://didit.csail.mit.edu:4949/
url ::= ‘http://’ hostname (’:’ port)? ‘/’
hostname ::= word ‘.’ word | word ‘.’ hostname ?递归
port ::= [0-9]+
word ::= [a-z]+
例2:
S ::= (B C)* T
B ::= M+ | P B P
C ::= B | E+
非终止节点:S,B,C
终止节点:其余节点
递归表达式:B ::= M+ | P B P
解析树
语法树生成字符串:解析树 展示字符串的每一部分如何与语法的部分对应
先深搜索
例:
递归
非递归
Markdown 与 HTML
正则语法与正则表达式
正则语法:简化之后可以表达为一个产生式,不包含任何非终止节点
正则表达式符合正则语法
正则表达式去除了引号和空格,只包含叶节点字符,括号和操作符
正则表达式中的特殊操作符:
1."." 任意单一字符
2.“\d" 任意数字 同[0-9]
3.”\s" 任意空白字符,包括空格,tab,换行符
4."\w" 同[a-zA-Z_0-9]
5.转义符:".", “(,)” ,"*","+"
解析器 Parser
parser输入一段文本,与特定的语法规则建立匹配,输出结果。将文本转化为parse tree,利用产生的parse tree进行下一步的处理。
parser generator 是根据语法规则生成parser程序的工具
Java中的正则表达式
java.util.regex包
1.Pattern:是对regex编译后得到的结果
2.Matcher :利用Pattern对输入字符串进行解析
3.PatternSyntaxException:unchecked exception,表示正则表达式的语法错误