基于状态的编程使用有限状态机来定义程序的行为、使用状态来控制程序的执行。
根据当前状态,决定下一步要执行什么操作、执行操作之后要转移到什么新的状态。
核心思想:将程序看作是一个有限状态自动机,侧重于对“状态”及“状态转换”的抽象和编程。
程序的执行被分解为一组自动执行的步骤,各步骤之间的通讯通过“状态变量”进行。
程序执行就可看作是各自动步骤的不断循环,使用枚举类型 enum 定义状态,使用二维数组定义状态转换表。这种实现方式一般会使用 is/else 结构。
State transition[][] = { { State.Initial, State.Final, State.Error }, { State.Final, State.Initial, State.Error } };然而这种实现方式使得对程序进行修改的工程量变大。
最好不要使用 if/else 结构在 ADT 内部实现状态转换(考虑将来的扩展和修改)。可以使用使用 delegation ,将状态转换的行为委派到独立的 state 对象去完成。
下图是状态转换的 UML 图:
如下程序是一种状态转换模式的应用,其状态转换图如下:
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(); }//Delegate 到当前状态的 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);//给 ADT 初始状态 for (int i = 0; i < args.length; i++) { context.move(args[i]); if(context.accept()) break; //根据输入的一组字符,每次用一个字符使状态发生变迁,直到达到最终状态,程序结束。 } }上述程序使用了单例模式 ( singleton 模式)。State1 类中,无法通过私有的( private )构造函数构造它的对象。单例模式就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。
单例模式要求:
构造函数私有 (private)有静态方法(对外提供获取实例的静态方法,如静态工厂方法)、成员变量(持有自己类型的属性),最好不要把成员变量变为 public 类型State 一般需要使用单例模式。
记住对象的历史状态,以便于“回滚”。
备忘录模式需要实现:
需要“备忘”的类添加 originator 的备忘记录和恢复备忘录,记录 originator 对象的历史状态//Memento:非常简单的类,只记录一个历史状态 class Memento { private State state; public Memento(State state) { this.state = state; } public State getState() { return state; } } class Originator { private State state; // ADT 原本的状态转换功能,可能更复杂(例如 State 模式) public void setState(State state) { System.out.println("Originator: Setting state to " + state.toString()); this.state = state; } //保存历史状态, delegate 到 memento 去实现 public Memento save() { System.out.println("Originator: Saving to Memento."); return new Memento(state); } //利用传入的 Memento 对象来恢复历史状态 public void restore(Memento m) { state = m.getState(); System.out.println("Originator: State after restoring from Memento: " + state); } } class Caretaker { private List<Memento> mementos = new ArrayList<>();//保留一系列历史状态 //添加一个新的历史状态 public void addMemento(Memento m) { mementos.add(m); } //取出需要回滚的状态 public Memento getMemento() { return mementos.get(?);//如果要恢复最近备份的状态,这里应该是? } } 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()); //如何 rollback 两步、三步、... } } /* 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 */
State 与 Memento 模式均为两棵继承树,两层次 delegation 的设计样式。
对于 State 模式,左为 context :
Memento 模式:
有一类应用,从外部读取文本数据,在应用中做进一步处理。
文本的出现情况:
输入文件有特定格式,程序需读取文件并从中抽取正确的内容从网络上传输过来的消息,遵循特定的协议用户在命令行输入的指令,遵循特定的格式内存中存储的字符串,也有格式需要匹配方法:
使用 grammar 判断字符串是否合法,并解析成程序里使用的数据结构通常是递归的数据结构程序能接受的语法就是正则表达式。Java 根据语法,开发一个它的解析器,用于后续的解析。
用语法定义一类字符串。
语法包含两类型的节点。将语法表示为树形的结构:
终止节点是语法解析树的叶子结点,无法再向下扩展产生式结点是非终止节点,可以被扩展。最初的非终结符叫根二元操作符优先级低于一元操作符。
更简便的表示方法:
? :代表 0 或 1+ :代表 1 或多个[...]:选择。从方括号中选择适合的字符。[^...]:不从方括号包含的字符选择。也可以不递归实现:
url ::= 'http://' hostname (':' port)? '/' hostname ::= (word '.')+ word port ::= [0-9]+ word ::= [a-z]+但语法树不一样。
对字符串 http://mit.edu/ ,
url ::= 'http://mit.edu/'其语法树为:
url ::= 'http://' hostname '/' hostname ::= word '.' word word ::= [a-z]+其语法树为:
对字符串 http://didit.csail.mit.edu:4949/,语法:
url ::= 'http://' hostname (':' port)? '/' hostname ::= word '.' hostname | word '.' word port ::= [0-9]+ word ::= [a-z]+其语法树为:
利用特定的语法对文本进行解析。
正则语法:简化之后可以表达为一个产生式而不包含任何非终止节点。
正则表达式:去除引号和空格,从而表达更简洁但更难懂。
特殊符号:
.:单个字母\d:任意数字,同 [0-9]\s:任何空格字符,包括空格,制表符,换行符\w:任何单词字符,包括下划线,同 [a-zA-Z_0-9]\. \( \) \* \+ ...因此单单使用 <([{\^-=$!|]})?*+.> 时,需要添加转义字符 \ 。
parser :输入一段文本,与特定的语法规则建立匹配,输出结果。
parser :将文本转化为 parse tree
利用产生的 parse tree ,进行下一步的处理
Parser generator 是一个工具,根据语法规则生成一个 parser 程序。
常用的类 String.split , String.matches , java.util.regex.Pattern 。
对于 java.util.regex :
Pattern :是对 regex 正则表达式进行编译之后得到的结果Matcher :利用 Pattern 对输入字符串进行解析匹配public boolean matches(String regex);//查看是否与正则表达式能否匹配上 Pattern.matches(regex, str); public String[] split(String regex, int limit) limit);//切分的次数 Pattern.compile(regex).split(str, n); public String[] split(String regex); public String replace(CharSequence target,CharSequence replacement);