软件构造 5-3 Maintainability Oriented Construction Techniques

    技术2022-07-11  102

    5.3 面向可维护性的构造技术

    一. 基于状态的编程

      基于状态的编程使用有限状态机来定义程序的行为、使用状态来控制程序的执行。

      根据当前状态,决定下一步要执行什么操作、执行操作之后要转移到什么新的状态。

    1. 基于自动机的编程

      核心思想:将程序看作是一个有限状态自动机,侧重于对“状态”及“状态转换”的抽象和编程。

      程序的执行被分解为一组自动执行的步骤,各步骤之间的通讯通过“状态变量”进行。

      程序执行就可看作是各自动步骤的不断循环,使用枚举类型 enum 定义状态,使用二维数组定义状态转换表。这种实现方式一般会使用 is/else 结构。

    State transition[][] = { { State.Initial, State.Final, State.Error }, { State.Final, State.Initial, State.Error } };

      然而这种实现方式使得对程序进行修改的工程量变大。

    2. 状态 State 模式

      最好不要使用 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 一般需要使用单例模式。

    3. Memento Pattern 备忘录模式

      记住对象的历史状态,以便于“回滚”。

      备忘录模式需要实现:

    需要“备忘”的类添加 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 */

    4. 设计模式共性样式 2

      State 与 Memento 模式均为两棵继承树,两层次 delegation 的设计样式。

      对于 State 模式,左为 context :

      Memento 模式:

    二. 语法驱动的构造

      有一类应用,从外部读取文本数据,在应用中做进一步处理。

      文本的出现情况:

    输入文件有特定格式,程序需读取文件并从中抽取正确的内容从网络上传输过来的消息,遵循特定的协议用户在命令行输入的指令,遵循特定的格式内存中存储的字符串,也有格式需要

      匹配方法:

    使用 grammar 判断字符串是否合法,并解析成程序里使用的数据结构通常是递归的数据结构

      程序能接受的语法就是正则表达式。Java 根据语法,开发一个它的解析器,用于后续的解析。

    1. 语法

      用语法定义一类字符串。

      语法包含两类型的节点。将语法表示为树形的结构:

    终止节点是语法解析树的叶子结点,无法再向下扩展产生式结点是非终止节点,可以被扩展。最初的非终结符叫根

    2. 语法操作符

    连接:二元操作符 x ::= y z重复:一元操作符 x ::= y*选择:二元操作符 x ::= y | z

      二元操作符优先级低于一元操作符。

      更简便的表示方法:

    ? :代表 0 或 1+ :代表 1 或多个[...]:选择。从方括号中选择适合的字符。[^...]:不从方括号包含的字符选择。

    3. 递归

    url ::= 'http://' hostname (':' port)? '/' hostname ::= word '.' hostname | word '.' word port ::= [0-9]+ word ::= [a-z]+

      也可以不递归实现:

    url ::= 'http://' hostname (':' port)? '/' hostname ::= (word '.')+ word port ::= [0-9]+ word ::= [a-z]+

      但语法树不一样。

    4. Parse 语法树

      对字符串 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]+

      其语法树为:

    5. Markdown 和 HTML

      利用特定的语法对文本进行解析。

    6. 正则语法与正则表达式

      正则语法:简化之后可以表达为一个产生式而不包含任何非终止节点。

      正则表达式:去除引号和空格,从而表达更简洁但更难懂。

      特殊符号:

    .:单个字母\d:任意数字,同 [0-9]\s:任何空格字符,包括空格,制表符,换行符\w:任何单词字符,包括下划线,同 [a-zA-Z_0-9]\. \( \) \* \+ ...

      因此单单使用 <([{\^-=$!|]})?*+.> 时,需要添加转义字符 \ 。

    7*. Parsers

       parser :输入一段文本,与特定的语法规则建立匹配,输出结果。

       parser :将文本转化为 parse tree

      利用产生的 parse tree ,进行下一步的处理

      Parser generator 是一个工具,根据语法规则生成一个 parser 程序。

    8. Java 中的正则表达式

      常用的类 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);
    Processed: 0.014, SQL: 9