在对一个J2EE目的重构、增加新功能的过E中Q对客户端GUIE序Q我们用了State模式。结果显C,该模式的使用Q不但减了客户端GUIE序的程序规模(LOCQ,而且Q该部分的开发及单元试旉大大减少Q同Ӟ在集成测试中发现的缺h量比使用该模式前q_减少?倍。本文就该项目中使用State模式的方式进行介l?/BLOCKQUOTE>
引言
在分层Y件体pȝ构中Q服务端E序x于实C务逻辑Q客LE序则包含用L面。服务端E序由客LE序调用Q其h、响应模式在设计时已l确定,q行时出现问题的概率较小。相反,客户端程序与用户直接交互Q虽然有正确规定的操作顺序或模式Q但是用L操作是不可预知的Q程序必d理各U操作错误、加上数据输入有效验证等要求Q得客LE序的开发成本上升?/P>
因而,一旦有l过充分试的、甚x通过验收的用户交互程序GUIQ应该尽可能的重用该GUIQ以提高软g的可靠性、可l护性?/P>
在对一个J2EE目的重构、增加新功能的过E中Q对客户端GUIE序Q我们用了State模式。结果显C,该模式的使用Q不但减了客户端GUIE序的程序规模(LOCQ,而且Q该部分的开发及单元试旉大大减少Q同Ӟ在集成测试中发现的缺h量比使用该模式前q_减少?倍。本文就该项目中使用State模式的方式进行介l?/P>
1. State模式
首先Q先单介l一下State模式?/P>
该模式是指在对象的内部状态改变时改变对象的行为?】。其l构如图1所C?/P>
? State模式l构

模式中各个参与者职责简介如下:
- ContextQ用户对象,拥有一个Statecd的成员,以标识对象的当前状态;
- StateQ接口或基类Q封装与Context的特定状态相关的行ؓQ?
- ConcreteStateQ接口实现类或子c,实现了一个与Context某个状态相关的行ؓ?/LI>
q行ӞContext与状态相关的h委托l当前的ConcreteState对象处理。关于State模式更详的介绍Q请参阅参考文??/P>
2. 客户端应?/SPAN>
本模式的目标是分dL软g中的变化部分与不变部分,以得变化的部分可独立于不变的部分,有利于扩充新的功能,也有利于l护?/P>
在项目中Q对于客LGUI的重用有两种方式?/P>
- 方式1适用于:相同数据集合Q不同操作模式;此时Q在GUI中定义客L数据处理验证逻辑Q不同的状态对象封装了不同的操作模式;
- 方式2适用于:不同数据集合Q相同操作模式;此时Q在状态对象中定义客户端数据处理验证逻辑Q不同的状态对象封装了不同的数据集合操作?/LI>
2.1 cd1Q?Read-Only & Normal
2.1.1 动机
客户端GUI接受用户输入Q经q数据有效性验证,然后数据传输到服务端,服务端检查业务逻辑有效性,保存数据Q但是在特定情况下(比如数据已经存在、且只能l历一ơ输入)Q依据客LGUI所操作数据的状态,业务逻辑要求数据为只读,卛_L只具有显C数据的功能Qƈ不能改变服务器上的数据?/P>
一般地Q编E实现时Q会在GUIE序中加入判断数据是否应该ؓ只读的逻辑判断语句。如果针对相同的数据集合QModelQ,有多个客LGUIQViewQ,需要在每个E序中加入该判断。如此降低了E序的可l护性,军_数据是否为只ȝq样一个业务逻辑分散在E序中多处,不易l护Q在业务逻辑发生改变Ӟ易造成不一致?/P>
我们可以变化部分(在Normal和Read-Only状态下不同的部分)从GUI中抽取出来,分别用不同的cL表示Q这P当GUI的状态发生改变时Q只需要改变其对状态对象的引用卛_Q状态相x作委托给状态对象去完成?/P>
2.1.2 适用?/B>
本类型适用环境Q相同数据集合,不同操作模式。即特定的GUIҎ(gu)操作上下文环境,改变其响应模式,Ҏ(gu)数据是否可编辑,分ؓ两种状态Read-Only State?Normal State?/P>
2.1.3 l构Q图2Q?/B>
? 相同数据集合Q不同操作模?/B>
![? 相同数据集合Q不同操作模? src=]()
2.1.4 参与?/B>
- ClientStateChangeableQ客LGUI提供lState对象的操作接口,使得State对象可以讉KGUI的成员,以完成该状态相x作;
- getChangeableComponents()Q返回在状态{换ؓRead-OnlyӞ不可~辑的控件CollectionQ?
- saveChangeToServer()Q在可编辑状态时Q将客户端数据保存到服务端?/LI>
- AClientGUIQ完成图1中的Context功能Q即其状态要q行改变的客LGUIQ维护ClientState的一个实例(stateQ;
- init()Q将this引用作ؓ参数Q调用state.setComponents(this) 以设|可变控件的初始状态;
- okButtonActionPerformed(e:ActionEvent)Q用户完成操作后Q本Ҏ(gu)可作?"OK"按钮控g的ActionListenerҎ(gu)Q调用state.action(this) 以完成本操作?/LI>
- ClientStateQState接口Q封装与客户端GUI某个特定状态相关的行ؓQ?
- setComponents(gui:ClientStateChangeable)Q设|参数gui指定的客LGUI的控件状态;
- action(gui:ClientStateChangeable)Q完成数据保存功能?/LI>
- ClientNormalStateQ可~辑状态子c,实现ClientState接口Q?
- setComponents(gui:ClientStateChangeable)Q调用参数gui指定的客LGUI的getChangeableComponents()获取控gCollectionQ然后遍历设|各控g状态ؓ可编辑;
- action(gui:ClientStateChangeable)Q调用参数gui指定的客LGUI的saveChangeToServer()完成数据保存功能?/LI>
- ClientReadOnlyStateQ只ȝ态子c,实现ClientState接口Q?
- setComponents(gui:ClientStateChangeable)Q调用参数gui指定的客LGUI的getChangeableComponents()获取控gCollectionQ然后遍历设|各控g状态ؓReadOnlyQ?
- action(gui:ClientStateChangeable)Q空Ҏ(gu)Q本状态下无数据变化,无须保存?/LI>
2.1.5 代码CZ
ClientStateChangeable接口Q?/P>
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();
}
|
AClientGUIc:
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接口Q?/P>
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); }
ClientNormalStatec:
olor="#CCCCCC">
public class ClientNormalState implements ClientState {
/**
* 正常状态下, 各个控g默认为可~辑? 所以不用做M更改
*/
public void setComponents(ClientStateChangeable gui) {}
/**
* 正常状态下, 需要将用户所作修改保存到服务?
*/
public void action(ClientStateChangeable gui) {
gui.saveChangeToServer();
}
}
|
ClientReadOnlyStatec:
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 cd2Q(Reuse GUIQ?/SPAN>
2.2.1 动机
当多个客LGUI布局、控件类型很怼Q所完成的Q务也怼Ӟ只需要经q精心设计,这些GUI的展CŞ式统一hQ同一个GUI可以用到多个场景中,辑ֈ重用的目的。此Ӟq些不同d需要操作不同的数据集合?/P>
可以在GUIcM实现q些不同数据集合的操作,但是q会l程序维护带来麻烦。首先,属于不同逻辑的数据操作出现在同一cL件中Q造成逻辑混ؕ、程序规模增大,不易于调试;其次Q要GUI用于新的数据集合Ӟ只能在相同文件中增加新的代码Q此Ӟ该程序的可维护性降低,其是新的工作由其他E序员完成时Q要理解原有代码是很费力的?/P>
?.1.1节中提到的解x法类|变化的部分和不变部分分d来,使得变化的部分可以独立修攏V扩充。具体地Q则是将数据集合相关操作从GUIE序中抽取出来,定义一个所有数据集合操作的接口Q即Q状态接口)Q不同地数据集合操作作ؓ该接口的一个实现类存在。这P每个数据集合都独立的装于一个状态对象内Q而且Q要Ҏ(gu)的数据用该GUIQ只需要定义新的状态接口实现类卛_Q无M改已有类Q甚至不兛_已有的状态?/P>
2.2.2 适用?/B>
本类型适用环境Q不同的数据集合Q相同的操作模式。即不变化的客户端GUIQ将不同的数据集合操作委托给变化的状态对象去完成?/P>
2.2.3 l构Q图3Q?/B>
? 不同数据集合Q相同操作模?/B>
![? 不同数据集合Q相同操作模? src=]()
2.2.4 参与?/B>
- InvariableGUIQ本w不发生变化的客LGUIc,l护一个对VariableDataState的引用stateQ将数据相关操作委托l该引用对象 Q?
- saveChangeToServer()Q调用state.processData(this)完成数据相关操作Q?/LI>
- VariableDataStateQState接口Q封装与数据相关操作的行为;
- ProcessData(gui:InvariableGUI)Q调用参数gui的成员获取数据ƈ完成处理Q?/LI>
- DataState1、DataState2???DataStateNQ状态子c,实现VariableDataState接口Q封装了特定某一cL据集合的操作Q可以根据不同的数据集合定义多个VariableDataState接口的状态类Q从而实C对InvariableGUI的重用?/LI>
本类型的实现代码在这里就不列ZQ参?.1.5节中的代码,很容易的可实现本类型的l构?/P>
2.3 l合以上两种cd
可以以上两U类型结合v来用,卛_C客户端Y件的数据集合斚w对GUI的重用,也实C操作模式斚w对GUI的重用?/P>
E序实现Ӟ可以由GUIcd别维护一个ClientState的引用和一个VariableDataState的引用:
- 初始化GUIcLQGUI的构造器调用ClientState对象的setComponents()Ҏ(gu)讄控g的状态;
- 用户提交操作ӞGUI调用ClientState对象的action()Ҏ(gu)Q如?所C,该方法用传递的gui参数回调GUI的saveChangeToServer()Ҏ(gu)Q而saveChangeToServer()Ҏ(gu)则按照图3所C,调用VariableDataState引用的状态对象的processData()Ҏ(gu)完成数据操作?/LI>
3 ȝ
本文介绍的State模式应用于多cd数据、多操作模式的客L软gQ可以取得明昄效果Q但如果客户端类和状态都很少Ӟ使用本模式,反而增加了客户端类数量Q增加了体系l构的复杂性,此时Q可以用承方式的cMpL实现重用Q无M用State状态对象的委托操作和回调操作?/P>
]]>