您的位置:首页 > 其它

【转】 State模式在客户端软件中的应用

2010-03-10 11:28 253 查看
State模式在客户端软件中的应用

http://www.ibm.com/developerworks/cn/java/j-state/

State模式在客户端软件中的应用





文档选项


打印本页



将此页作为电子邮件发送

级别: 初级

王纲明 (gumreal@126.com), 软件工程研究所, 北京航空航天大学

2005 年 1 月 01 日

在对一个J2EE项目的重构、增加新功能的过程中,对客户端GUI程序,我们使用了State模式。结果显示,该模式的使用,不但减少了客户端GUI程序的程序规模(LOC),而且,该部分的开发及单元测试时间大大减少,同时,在集成测试中发现的缺陷数量比使用该模式前平均减少了3倍。本文就该项目中使用State模式的方式进行介绍。

引言

在分层软件体系结构中,服务端程序关注于实现业务逻辑,客户端程序则包含用户界面。服务端程序由客户端程序调用,其请求、响应模式在设计时已经确定,运行时出现问题的概率较小。相反,客户端程序与用户直接交互,虽然有正确规定的操作顺序或模式,但是用户的操作是不可预知的,程序必须处理各种操作错误、加上数据输入有效验证等要求,使得客户端程序的开发成本上升。

因而,一旦有经过充分测试的、甚至是通过验收的用户交互程序GUI,应该尽可能的重用该GUI,以提高软件的可靠性、可维护性。

在对一个J2EE项目的重构、增加新功能的过程中,对客户端GUI程序,我们使用了State模式。结果显示,该模式的使用,不但减少了客户端GUI程序的程序规模(LOC),而且,该部分的开发及单元测试时间大大减少,同时,在集成测试中发现的缺陷数量比使用该模式前平均减少了3倍。本文就该项目中使用State模式的方式进行介绍。








回页首
1. State模式

首先,先简单介绍一下State模式。

该模式是指在对象的内部状态改变时改变对象的行为【1】。其结构如图1所示。

图1 State模式结构




模式中各个参与者职责简介如下:

Context:用户对象,拥有一个State类型的成员,以标识对象的当前状态;

State:接口或基类,封装与Context的特定状态相关的行为;

ConcreteState:接口实现类或子类,实现了一个与Context某个状态相关的行为。

运行时,Context将与状态相关的请求委托给当前的ConcreteState对象处理。关于State模式更详尽的介绍,请参阅参考文献1。








回页首
2. 客户端应用

本模式的目标是分离客户端软件中的变化部分与不变部分,以使得变化的部分可独立于不变的部分,有利于扩充新的功能,也有利于维护。

在项目中,对于客户端GUI的重用有两种方式。

方式1适用于:相同数据集合,不同操作模式;此时,在GUI中定义客户端数据处理验证逻辑,不同的状态对象封装了不同的操作模式;

方式2适用于:不同数据集合,相同操作模式;此时,在状态对象中定义客户端数据处理验证逻辑,不同的状态对象封装了不同的数据集合操作。

2.1 类型1: Read-Only & Normal

2.1.1 动机

客户端GUI接受用户输入,经过数据有效性验证,然后将数据传输到服务端,服务端检查业务逻辑有效性,保存数据;但是在特定情况下(比如数据已经存在、且只能经历一次输入),依据客户端GUI所操作数据的状态,业务逻辑要求数据为只读,即客户端只具有显示数据的功能,并不能改变服务器上的数据。

一般地,编程实现时,会在GUI程序中加入判断数据是否应该为只读的逻辑判断语句。如果针对相同的数据集合(Model),有多个客户端GUI(View),就需要在每个程序中加入该判断。如此降低了程序的可维护性,决定数据是否为只读的这样一个业务逻辑将分散在程序中多处,不易维护,在业务逻辑发生改变时,易造成不一致。

我们可以将变化部分(在Normal和Read-Only状态下不同的部分)从GUI中抽取出来,分别用不同的类来表示,这样,当GUI的状态发生改变时,只需要改变其对状态对象的引用即可,状态相关操作委托给状态对象去完成。

2.1.2 适用性

本类型适用环境:相同数据集合,不同操作模式。即特定的GUI根据操作上下文环境,改变其响应模式,根据数据是否可编辑,分为两种状态Read-Only State、 Normal State。

2.1.3 结构(图2)

图2 相同数据集合,不同操作模式




2.1.4 参与者

ClientStateChangeable:客户端GUI提供给State对象的操作接口,使得State对象可以访问GUI的成员,以完成该状态相关操作;

getChangeableComponents():返回在状态转换为Read-Only时,不可编辑的控件Collection;

saveChangeToServer():在可编辑状态时,将客户端数据保存到服务端。

AClientGUI:完成图1中的Context功能,即其状态要进行改变的客户端GUI;维护ClientState的一个实例(state);

init():将this引用作为参数,调用state.setComponents(this) 以设置可变控件的初始状态;

okButtonActionPerformed(e:ActionEvent):用户完成操作后,本方法可作为 "OK"按钮控件的ActionListener方法,调用state.action(this) 以完成本操作。

ClientState:State接口,封装与客户端GUI某个特定状态相关的行为;

setComponents(gui:ClientStateChangeable):设置参数gui指定的客户端GUI的控件状态;

action(gui:ClientStateChangeable):完成数据保存功能。

ClientNormalState:可编辑状态子类,实现ClientState接口;

setComponents(gui:ClientStateChangeable):调用参数gui指定的客户端GUI的getChangeableComponents()获取控件Collection,然后遍历设置各控件状态为可编辑;

action(gui:ClientStateChangeable):调用参数gui指定的客户端GUI的saveChangeToServer()完成数据保存功能。

ClientReadOnlyState:只读状态子类,实现ClientState接口;

setComponents(gui:ClientStateChangeable):调用参数gui指定的客户端GUI的getChangeableComponents()获取控件Collection,然后遍历设置各控件状态为ReadOnly;

action(gui:ClientStateChangeable):空方法,本状态下无数据变化,无须保存。

2.1.5 代码示例

ClientStateChangeable接口:

public interface ClientStateChangeable {	
	/**
	 * To get all changeable components in the GUI object. 
	 * @return Collection contains Component objects. 
	 */
	Collection getChageableComponents();
	
	/**
	 * To save data to server.
	 */
	void saveChangeToServer();
}

AClientGUI类:

public class AClientGUI extends JPanel implements ClientStateChangeable{
	//… 	
	private ClientState state = null;
public DesignStep1View(ClientState state){
//…			
this.state = state;
this.state.setComponents(this);
	}
	
	private void okButton_actionPerformed(ActionEvent e){
		state.action(this);	//save data
	}
	public Collection getChageableComponents() {
		Collection dataComponents = new ArrayList();		
		dataComponents.add(jComboBoxESEPattern);
		//…			
		return dataComponents;
	}
	public void saveChangeToServer() {
		//…	
	}
}

ClientState接口:

public interface ClientState { /** * To set components' state in the GUI. * @param gui a GUI object which implements StateChangeable interface. */ void setComponents(ClientStateChangeable gui); /** * when user click OK-Button in a GUI, the GUI will call this method. * @param gui a GUI object which implements StateChangeable interface. */ void action(ClientStateChangeable gui); }

ClientNormalState类:

public class ClientNormalState implements ClientState {
	/**
	 * 正常状态下, 各个控件默认为可编辑的, 所以不用做任何更改
	 */
	public void setComponents(ClientStateChangeable gui) {}
	/** 
	 * 正常状态下, 需要将用户所作修改保存到服务端 
	 */
	public void action(ClientStateChangeable gui) {
		gui.saveChangeToServer();
	}
}

ClientReadOnlyState类:

public class ClientReadOnlyState implements ClientState {
	/**
	 * 设置GUI的数据控件为Read-Only
	 */
public void setComponents(ClientStateChangeable gui) {
		Collection components = gui.getChageableComponents();
		Iterator iter = components.iterator();
		while(iter.hasNext()){
			JComponent jc = (JComponent)iter.next();
			jc.setEnabled(false);			
			String toolTip = jc.getToolTipText();
			String addedTip = "只读状态";
			if(toolTip == null)toolTip = addedTip;
			else toolTip += ". " + addedTip;
			jc.setToolTipText(toolTip); 
		}
	}
	/**
	 * GUI处于Read-Only状态, 无需将数据保存到server端	
	 */
	public void action(ClientStateChangeable gui) {}
}

2.2 类型2:(Reuse GUI)

2.2.1 动机

当多个客户端GUI布局、控件类型很相似,所完成的任务也相似时,只需要经过精心设计,将这些GUI的展示形式统一起来,同一个GUI可以用到多个场景中,达到重用的目的。此时,这些不同任务需要操作不同的数据集合。

可以在GUI类中实现这些不同数据集合的操作,但是这会给程序维护带来麻烦。首先,属于不同逻辑的数据操作出现在同一类文件中,造成逻辑混乱、程序规模增大,不易于调试;其次,要将GUI用于新的数据集合时,只能在相同文件中增加新的代码,此时,该程序的可维护性降低,尤其是新的工作由其他程序员完成时,要理解原有代码是很费力的。

和2.1.1节中提到的解决方法类似:将变化的部分和不变部分分离开来,使得变化的部分可以独立修改、扩充。具体地,则是将数据集合相关操作从GUI程序中抽取出来,定义一个所有数据集合操作的接口(即:状态接口),不同地数据集合操作作为该接口的一个实现类存在。这样,每个数据集合都独立的封装于一个状态对象内;而且,要对新的数据使用该GUI,只需要定义新的状态接口实现类即可,无须修改已有类,甚至不关心已有的状态。

2.2.2 适用性

本类型适用环境:不同的数据集合,相同的操作模式。即不变化的客户端GUI,将不同的数据集合操作委托给变化的状态对象去完成。

2.2.3 结构(图3)

图3 不同数据集合,相同操作模式




2.2.4 参与者

InvariableGUI:本身不发生变化的客户端GUI类,维护一个对VariableDataState的引用state,将数据相关操作委托给该引用对象 ;

saveChangeToServer():调用state.processData(this)完成数据相关操作;

VariableDataState:State接口,封装与数据相关操作的行为;

ProcessData(gui:InvariableGUI):调用参数gui的成员获取数据并完成处理;

DataState1、DataState2、 … … DataStateN:状态子类,实现VariableDataState接口,封装了特定某一类数据集合的操作,可以根据不同的数据集合定义多个VariableDataState接口的状态类,从而实现了对InvariableGUI的重用。

本类型的实现代码在这里就不列出了,参照2.1.5节中的代码,很容易的就可实现本类型的结构。

2.3 综合以上两种类型

可以将以上两种类型结合起来使用,即实现了客户端软件的数据集合方面对GUI的重用,也实现了操作模式方面对GUI的重用。

程序实现时,可以由GUI类分别维护一个ClientState的引用和一个VariableDataState的引用:

初始化GUI类时,GUI的构造器调用ClientState对象的setComponents()方法设置控件的状态;

用户提交操作时,GUI调用ClientState对象的action()方法,如图2所示,该方法使用传递的gui参数回调GUI的saveChangeToServer()方法,而saveChangeToServer()方法则按照图3所示,调用VariableDataState引用的状态对象的processData()方法完成数据操作。








回页首
3 总结

本文介绍的State模式应用于多类型数据、多操作模式的客户端软件,可以取得明显的效果;但如果客户端类和状态都很少时,使用本模式,反而增加了客户端类数量,增加了体系结构的复杂性,此时,可以使用继承方式的类体系来实现重用,无须使用State状态对象的委托操作和回调操作。

参考资料

Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides "Design Patterns: Elements of Reusable Object-Oriented Software", Addison-Wesley, 1995

Bruce Echel, "Thinking in Java", Third Edition, President, MindView, Inc. 2002

关于作者





王纲明,北京航空航天大学,软件工程研究所

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: