软件构造第五章总结—Part Ⅱ

前言:第五章学习笔记:Construction Techniques for Maintainability

Part Ⅱ Construction Techniques for Maintainability

一.基于状态的构造技术

1.State Pattern:状态模式

解决的问题:应用程序中的有些对象会根据所处状态的不同做出不同的行为。传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else 语句来做状态判断,再进行不同情况的处理。但当对象的状态很多时,程序会变得很复杂。而且增加新的状态要添加新的 if-else 语句。

解决方法:允许在运行时修改对象的行为或状态,每个行为(一组方法构成)用一个状态类表达。程序的行为委托给状态对象去执行,并同时进行相应的状态转换。
在这里插入图片描述
其结构可由上面的图清晰地展现:
<1>Context:我们所要设计的ADT,其中涉及到与状态有关的行为(方法)Request(可以有多个),当ADT处于不同状态下Request执行的操作是不同的,为了实现这一点,我们可以在ADT内部维护一个state类的属性,同时对外提供一个改变State的变值方法,以便在操作执行过程中能够方便地改变状态

public class Context{
	private State state;
	public void setState(State state){
		this.state=state;
	}
}

<2>抽象状态类(接口)State:定义一个方法handle(可以有多个方法),用来完成ADT中的Request。即将Context对象中的特定状态所对应的行为封装在此。值得注意的是,handle方法的参数一般是Context类型变量,因为执行行为时往往要改变状态,那么这时就将ADT对象传进这个方法,利用Context提供的setState实现状态变化。

public abstract class State{
	public void handle(Context context);

<3>设计State的子类,将所有状态都设计为State的子类,实现State中的方法,不同子类的不同实现就表示了不同状态下ADT的行为

<4>在Context内部Request的实现,委托给state成员变量完成。根据委托时state的子类类型自动执行对应状态子类的方法。

	public void Request(){
		state.handle(this);//委托
	}

举个例子,假如我们有等待(waiting)、开始(running)、结束(ended)三个状态,而Request就是要实现一个简单地状态转化,等待状态时执行Request方法则转为开始状态,开始时调用则转为结束,结束时调用则什么也不做.那么三个状态子类中的handle方法可以这样实现。

	public class WaitingState extends State{
		@Override
		public void handle(Context context){
			context.setState(new RunningState());//操作就是状态转换 等待→开始
		}

	}
	public class RunningState extends State{
		@Override
		public void handle(Context context){
			context.setState(new EndedState());//操作就是状态转换 开始→结束
		}
	}
	public class EndedState extends State{
		@Override
		public void handle(Context context){
			return;//结束状态什么也不做
		}
	}

那么在程序执行过程中,Context的state成员变量必然属于三者之一,当某一时刻client需要执行Request,这时它委托给state变量,调用handle方法:
如果state为WaitingState,显然,它只可以调用WaitingState类下对应的handle方法,从而转化为RunningState
如果state为RunningState,显然,它只可以调用RunningState类下对应的handle方法,从而转化为EndedState
如果state为EndedState,显然,它只可以调用EndedState类下对应的handle方法,什么也不做.

就是这样,状态模式避免了大量if-else语句,我是非常喜欢这个技术的,当分支不是那么多时设计并不复杂,代码看起来也更简洁有序,if-else代码实在是太冗长了

优点:
<1>减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖
<2>有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换

缺点:
<1>状态模式的使用必然会增加系统的类与对象的个数
<2>状态子类的实现可能会比较复杂

2. Memento Pattern:备忘录模式

解决的问题:程序顺序执行过程中,client想要回滚到某个之前的状态,怎么办?

解决方法:用备忘录记住对象的历史状态,需要时回滚
在这里插入图片描述
我们可以看到,备忘录模式大致就由这三部分组成:
<1>Originator:我们程序中需要备份的ADT,它有一个成员变量state标识它的状态,状态是可以变化的,如提供setState变值器,甚至于可以像上面介绍的state模式一样进行变化。
此外,为其设置两个方法:setMemento与CreateMemento
CreateMemento就相当于备份操作,在执行过程中如果此时的状态是你需要备份的,那么你就可以调用这个函数,这时你就得到了一个备忘录,将其保存在Caretaker中

	public Memento CreateMemento(){
		return new Memento(state);
	}

setMemento就相当于回滚操作,你可以传入之前任意的一个备份的备忘录,然后Originator就会恢复到该备忘录存储的状态实现回滚。

	public void setMemento(Memento m){
		state=m.getState;
	}

<2>Memento:备忘录,非常简单的类,只记录一个创建时Originator的状态即可,即保存一个历史状态,并且外部可以获得它保存的状态是什么,但无需提供变值器.

<3>Caretaker:对一系列由CreateMemento备份而得到的Memento进行管理,client可从中取出某个历史状态(Memento)进行回滚

每次备份时都将CreateMemento返回的备忘录通过Caretaker中方法加入Caretaker中
在这里插入图片描述
然后用户通过其中提供的get函数得到某一个历史备忘录

	public Memento getMemento

这里还有细节是需要client确定的,比如得到历史备忘录是要回滚几次得到的?
解决这个问题可以为get函数加上一个int型参数i,实现回滚 i 次的状态复原

	public Memento getMemento(int i){
		if(mementos.size()-i < 0) 
			throw new RuntimeException("Cannot rollback so many back!"); 
		return mementos.get(mementos.size()-i); 
	} 

此外,回滚后是否需要删除该次回滚的备忘录,也是需要client来确定的,需要删除就可以增添一个remove操作

优点:
<1>提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态
<2>实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息

缺点:占用内存较大,保存了很多状态信息

二.基于语法的构造技术

这种构造技术的一般过程是:
对于输入字符串(一般是文本输入),我们需要它具有某种特殊结构,比如日期满足2020-05-20或2020/05/20这种形式(yyyy-mm-dd或yyyy/mm/dd)。这时利用语法对于这种结构设计出一个判断准则,对于一个字符串来检查是否符合这个准则来判断是否具有相应的结构。
并且对于符合语法准则的字符串,我们还可以将其解析成程序里使用的数据结构。

这里只介绍一些语法中的基本概念,后面再辅以一些我在实验三当中的实例进行说明。

1.终止节点:无法再往下扩展,通常表示为用引号引起的字符串,如 ’ http ’
2.产生式节点/非终止节点:遵循特定规则,利用操作符、终止节点和其他非终止节点,构造新的字符串
构造形式一般利用 ::=进行

非终止节点 ::=终端结点或非终端结点或操作符

3.操作符:
连接: x由y和z连接而成表示为 x ::= y z
重复:

  1. x由0个或多个y连接,表示为 x ::= y*
  2. x由0个或1个y连接,表示为 x ::= y?
  3. x由一个或多个y连接,表示为x::=y+

并(either y or z) x ::= y | z

优先级:* ? +优先级最高,连接次之,| 最低

4.语法中的递归定义
用自己定义自己,以下面的hostname为例
hostname ::= word ‘.’ hostname | word ‘.’ word
word ::= [a-z]+
那么hostname就是递归定义的,word是非递归定义的。因为hostname中的定义中仍包含自身,你无法将其定义转换成由全部非终结符组成的定义

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

6.正则表达式:去除一般语法语言中的引号和空格

7.正则表达式常用语法
参考博客https://blog.csdn.net/qq_18298439/article/details/88974940

语法构造的实例(lab3实例)

给定文本文件,其中由若干以下形式的字符块构成:

Flight:2020-01-01,CA1001  //航班计划项 1  
			//逗号前为航班日期,遵循 yyyy-MM-dd 格式 
			//逗号后为航班号,由两位大写字母和 2-4 位数字构成 
{ 
	DepartureAirport:Harbin   //出发机场,word 
	ArrivalAirport:Beijing    //抵达机场,word 
	DepatureTime:2020-01-01 12:00    //起飞时间 
	ArrivalTime:2020-01-01 14:00  //降落时间 
				      //这两个时间均为 yyyy-MM-dd HH:mm 的格式 
				      //起飞时间中的日期必须与第一行的日期一致 
				      //抵达时间中的日期可以与起飞日期一致,也可以晚 1 天 
				      //例如 2020-01-01 23:00 起飞,2020-01-02 01:00 降落 
Plane:B9802 //飞机编号,第一位为 N 或 B,后面是四位数字 
{   	Type:A350 //机型,大小写字母或数字构成,不含有空格和其他符号   
	Seats:300 //座位数,正整数,范围为[50,600]   
	Age:2.5   //机龄,数字,范围是[0,30],可最多 1 位小数或无小数              
		  //诸如 0、0.0、2、2.0、20.0 这样的表述都是对的              
		  //但 02.0、2.12 这样的表述则不符合规则 } 
}

那么我的思路是逐行读取,每一行分别设计正则表达式进行解析(利用上面参考博客中的语法),如果解析成功,利用match-group将其分解为各个部分的字符串进行保存,关于match-group可参考https://blog.csdn.net/qq_18298439/article/details/88974940进行学习

第一行:Flight:2020-01-01,CA1001
正则表达式:“Flight:(\d{4}-\d{2}-\d{2}),([A-Z]{2}\d{2,4})”
Flight: 部分为不变的元字符,直接保留;
双斜线表示转义字符,表明后面一个斜线是要与d组合形成\d的,即整数0-9,{4}表示要有4个\d,即4个整数, -为日期中表示连接的元字符,保留
\d{2}与\d{4}意义相近,只不过表示两个0-9整数
逗号为元字符,保留
[A-Z]{2}表示此处应具有两个A-Z的大写字母,对应例中的CA
\d{2,4}表示有2至4个0-9的整数,对应1001

这一行的语法几乎囊括了后面几行的正则表达式涉及到的语法,因此后面不再分析,只给出我对应的设计的正则表达式

第三行(省略了’{’ 和 ‘}’):DepartureAirport:Harbin
正则表达式:“DepartureAirport:(\w+)”

第四行:ArrivalAirport:Beijing
正则表达式:“ArrivalAirport:(\w+)”

第五行:DepatureTime:2020-01-01 12:00
正则表达式:“DepatureTime:(\d{4}-\d{2}-\d{2})( \d{2}:\d{2})”

第六行:ArrivalTime:2020-01-01 14:00
正则表达式:“ArrivalTime:(\d{4}-\d{2}-\d{2})( \d{2}:\d{2})”

第七行:Plane:B9802
正则表达式:“Plane:([BN]{1}\d{4})”

第九行:Type:A350
正则表达式:“Type:([A-Za-z0-9]+)”

第十行:Seats:300
正则表达式:“Seats:([0-9]+)”

第十一行:Age:2.5
正则表达式:“Age:([1-9]?[0-9]?.[0-9]?)”

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值