可维修性的构造技术
学了这么多OO设计模式,不外乎都是 delegation + subtying,万变不离其宗。
除了OO,还有什么其他能够提升软件可维护性的构造技术?——本节从委派+子类型跳出来,学习以下三个方面:
(1) 基于状态的构造技术 (2) 表驱动的构造技术 (3) 基于语法的构造技术
目录
- 基于状态的构造技术
- 表驱动的构造技术
- 基于语法的构造技术
基于状态的构造技术
基于状态的构造技术使用有限状态机来定义程序的行为、控制程序的执行。根据当前状态,决定下一步要执行什么操作、执行操作之后要转移到什么新的状态。
基于自动机的编程(Automata-based programming)
核心思想:将程序看作是一个有限状态自动机,侧重于对“状态”及“状态转换” 的抽象和编程。
程序的执行被分解为一组自动执行的步骤。各步骤之间的通讯通过“状态变量”进行。程序执行就可看作是各自动步骤的不断循环。 使用枚举类型enum定义状态 ,使用二维数组定义状态转换表。
State transition[][] = {
{ State.Initial, State.Final, State.Error },
{ State.Final, State.Initial, State.Error }
};
应用领域:
- 高可靠性系统:军事应用、航空航天工业、汽车工业
- 嵌入式系统
- 移动系统
- 可视化系统
- Web应用
- 客户端-服务器应用
状态模式 (behavioral pattern)
示例 - 有限状态机:
main函数
备忘录模式 (behavioral pattern)
记住对象的历史状态,以便于“回滚”。
备忘录设计模式定义了三种不同的角色:
- Originator:需要“备忘”的类
- Caretaker:添加originator的备忘记录和恢复
- Memento:备忘录,记录originator对象的历史状态
例子:
Originator类:
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() {
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);
}
}
Memento类:
class Memento {
private State state;
public Memento(State state) {
this.state = state;
}
public State getState() {
return state;
}
}
Caretaker类:
class Caretaker {
private ArrayList<Memento> mementos = new ArrayList<>();
public void addMemento(Memento m) {
mementos.add(m);
}
public Memento getMemento() {
return mementos.get(1);
}
}
主函数:
public class Demonstration {
public static void main(String[] args) {
Caretaker caretaker = new Caretaker();
Originator originator = new Originator();
originator.setState("State1");
originator.setState("State2");
caretaker.addMemento( originator.save() );
originator.setState("State3");
caretaker.addMemento( originator.save() );
originator.setState("State4");
originator.restore( caretaker.getMemento() );
}
}
结果:
Originator: Setting state to State1
Originator: Setting state to State2
Originator: Saving to Memento.
Originator: Setting state to State3
Originator: Saving to Memento.
Originator: Setting state to State4
Originator: State after restoring from Memento: State3
表驱动的构造技术(Table-driven construction)
表驱动编程的核心思想:将代码中复杂的if-else
和switch-case
语句从代码中分离出来,通过“查表”的方式完成,从而提高可维护性。
Direct Access Tables(直接访问表)
只需简单地通过一个或多个索引“查找”。
Indexed Access Tables(索引访问表)
有时直接索引是一个问题,特别是如果可能的值域很大。例如,如果您想使用产品ID(8位数字),并制作一张映射200个产品的表格。
Stair-Step Access Tables(阶梯访问表)
表格中的条目对数据范围有效,而不适用于不同的数据点。
语法驱动的构造(Grammar-based construction )
有一类应用,从外部读取文本数据, 在应用中做进一步处理。
具体来说,读取的一个字节或字符序列可能是:
- 输入文件有特定格式,程序需读取文件并从中抽取正确的内容。
- 从网络上传输过来的消息,遵循特定的协议。
- 用户在命令行输入的指令,遵顼特定的格式。
- 内存中存储的字符串,也有格式需要。
对于这些类型的序列,语法的概念是设计的一个好选择:
- 使用grammar判断字符串是否合法,并解析成程序里使用的数据结构 。
- 通常是递归的数据结构 。
正则表达式是一种广泛使用的工具。
语法成分
一个语法由一组产生式节点描述,其中每个产生式节点定义一个非终止节点。
一个非终止节点遵循特定规则,利用 操作符、终止节点和其他非终止节点,构造新的字符串。
语法中的操作符
表达中最重要的三个操作符是:
- 连接,不是通过一个符号,而是一个空间:
x ::= y z //an x is a y followed by a z
- 重复,以*表示:
x ::= y* //an x is zero or more y
- 联合,也称为交替,如图所示 | :
x ::= y | z //an x is a y or a z
三个基本操作符的组合:
- 可选(0或1次出现),由?表示:
x ::= y? //an x is a y or is the empty string
- 出现1次或多次:以+表示:
x ::= y+ //an x is one or more y,equivalent to x ::= y y*
- 字符类[…],表示长度的字符类,包含方括号中列出的任何字符的1个字符串:
x ::= [abc] is equivalent to x ::= 'a' | 'b' | 'c‘
- 否定的字符类[^…],表示长度,包含未在括号中列出的任何字符的1个字符串:
x ::= [^abc] //is equivalent to x ::= 'd' | 'e' | 'f' | ... (all other characters in Unicode)
例子:
x ::= (y z | a b)* //an x is zero or more y-z or a-b pairs
m ::= a (b|c) d //an m is a, followed by either b or c, followed by d
Example 1: URL
写一个语法表示URL,下面是两个URL
http://stanford.edu/
http://google.com/
我们可以这样写来匹配上面类型的URL
url ::= 'http://' [a-z]+ '.' [a-z]+ '/'
如果我们使用新的非终止节点,将会更容易理解:
hostname可以有两个以上的部分,并且可以有一个可选的端口号:
http://didit.csail.mit.edu:4949/
为了处理这样的字符串,语法可以这样写:
Example 2: Markdown and HTML
下面是Markdown和HTML的语法:
Markdown:
This is _italic_.
HTML:
Here is an <i>italic</i> word.
正则语法和正则表达式(Regular Grammars and Regular Expressions)
正则语法:简化之后可以表达为一个产生式而不包含任何非终止节点。
第三个语法中含有html非终止节点,因此不是正则语法。
正则表达式去除引号和空格,从而表达更简洁(更难懂)。下面介绍一些正则表达式的基本语法。
Parsers
parser:输入一段文本,与特定的语法规则建立匹配,输出结果 ,parser文本转化为parse tree, 利用产生的parse tree,进行下一步的处理 。
Using regular expressions in Java
Java中提供了java.util.regex
类来使用正则表达式。可以这么使用
//Match a URL
Pattern regex =
Pattern.compile("http://([a-z]+\\.)+[a-z]+(:[0-9]+)?/");
Matcher m = regex.matcher(string);
if (m.matches()) { // then string is a url }
//Extract part of an HTML tag:
Pattern regex = Pattern.compile("<a href=\"([^\"]*)\">");
Matcher m = regex.matcher(string);
if (m.matches()) {
String url = m.group(1);
// Matcher.group(n) returns the nth parenthesized part of
// the regex
}
Character Classes:
Predefined Character Classes:
Quantifiers:
Boundary Matchers:
解释器模式( Interpreter)
给定一种语法,定义该语法的程序内部表示,形成该语法的解释器,将遵循语法规则的文本解释成程序内部的表示(例如一组object) 。
解释语法的“引擎”:遵循语法的文本->OO表示 。
Implementation:
- 因为语法通常形成树结构,故使用composite模式来表达遵循语法的内容。
- 针对该层次化树形结构,定义了一组行为来处理结构中的不同类型节点。
Interpreter vs. grammar + parser