软件构造 5-3 Maintainability Oriented Construction Techniques

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

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

在这里插入图片描述

  对于 State 模式,左为 context

在这里插入图片描述

  Memento 模式:

在这里插入图片描述

二. 语法驱动的构造

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

  文本的出现情况:

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

  匹配方法:

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

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

1. 语法

  用语法定义一类字符串

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

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

2. 语法操作符

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

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

  更简便的表示方法:

  • ? :代表 01
  • + :代表 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. MarkdownHTML

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

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.splitString.matchesjava.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);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值