﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-jacky Blog-文章分类-Design Patten</title><link>http://www.blogjava.net/jacky/category/399.html</link><description>技术创造个人价值
Technology Creates The Values Of Person</description><language>zh-cn</language><lastBuildDate>Wed, 28 Feb 2007 02:47:29 GMT</lastBuildDate><pubDate>Wed, 28 Feb 2007 02:47:29 GMT</pubDate><ttl>60</ttl><item><title>State模式在客户端软件中的应用</title><link>http://www.blogjava.net/jacky/articles/1038.html</link><dc:creator>jacky</dc:creator><author>jacky</author><pubDate>Sat, 05 Feb 2005 12:09:00 GMT</pubDate><guid>http://www.blogjava.net/jacky/articles/1038.html</guid><wfw:comment>http://www.blogjava.net/jacky/comments/1038.html</wfw:comment><comments>http://www.blogjava.net/jacky/articles/1038.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jacky/comments/commentRss/1038.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jacky/services/trackbacks/1038.html</trackback:ping><description><![CDATA[<BLOCKQUOTE>在对一个J2EE项目的重构、增加新功能的过程中，对客户端GUI程序，我们使用了State模式。结果显示，该模式的使用，不但减少了客户端GUI程序的程序规模（LOC），而且，该部分的开发及单元测试时间大大减少，同时，在集成测试中发现的缺陷数量比使用该模式前平均减少了3倍。本文就该项目中使用State模式的方式进行介绍。</BLOCKQUOTE>
<P><A name=IDAZBG2><SPAN class=atitle2>引言</SPAN></A><BR>在分层软件体系结构中，服务端程序关注于实现业务逻辑，客户端程序则包含用户界面。服务端程序由客户端程序调用，其请求、响应模式在设计时已经确定，运行时出现问题的概率较小。相反，客户端程序与用户直接交互，虽然有正确规定的操作顺序或模式，但是用户的操作是不可预知的，程序必须处理各种操作错误、加上数据输入有效验证等要求，使得客户端程序的开发成本上升。</P>
<P>因而，一旦有经过充分测试的、甚至是通过验收的用户交互程序GUI，应该尽可能的重用该GUI，以提高软件的可靠性、可维护性。</P>
<P>在对一个J2EE项目的重构、增加新功能的过程中，对客户端GUI程序，我们使用了State模式。结果显示，该模式的使用，不但减少了客户端GUI程序的程序规模（LOC），而且，该部分的开发及单元测试时间大大减少，同时，在集成测试中发现的缺陷数量比使用该模式前平均减少了3倍。本文就该项目中使用State模式的方式进行介绍。</P>
<P><A name=IDABCG2><SPAN class=atitle2>1. State模式</SPAN></A><BR>首先，先简单介绍一下State模式。</P>
<P>该模式是指在对象的内部状态改变时改变对象的行为【1】。其结构如图1所示。</P>
<P><A name=IDAJCG2><B>图1 State模式结构</B></A><BR><IMG height=254 alt="图1 State模式结构" src="http://www-900.ibm.com/developerWorks/cn/java/j-state/images/image001.png" width=540 border=0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></P>
<P>模式中各个参与者职责简介如下：</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>Context：用户对象，拥有一个State类型的成员，以标识对象的当前状态； 
<LI>State：接口或基类，封装与Context的特定状态相关的行为； 
<LI>ConcreteState：接口实现类或子类，实现了一个与Context某个状态相关的行为。</LI></UL>
<P>运行时，Context将与状态相关的请求委托给当前的ConcreteState对象处理。关于State模式更详尽的介绍，请参阅参考文献1。</P>
<P><A name=IDA2CG2><SPAN class=atitle2>2. 客户端应用</SPAN></A><BR>本模式的目标是分离客户端软件中的变化部分与不变部分，以使得变化的部分可独立于不变的部分，有利于扩充新的功能，也有利于维护。</P>
<P>在项目中，对于客户端GUI的重用有两种方式。</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>方式1适用于：相同数据集合，不同操作模式；此时，在GUI中定义客户端数据处理验证逻辑，不同的状态对象封装了不同的操作模式； 
<LI>方式2适用于：不同数据集合，相同操作模式；此时，在状态对象中定义客户端数据处理验证逻辑，不同的状态对象封装了不同的数据集合操作。</LI></UL>
<P><A name=IDAGDG2><SPAN class=atitle3>2.1 类型1： Read-Only &amp; Normal</SPAN></A><BR><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2.1.1 动机</B></P>
<P>客户端GUI接受用户输入，经过数据有效性验证，然后将数据传输到服务端，服务端检查业务逻辑有效性，保存数据；但是在特定情况下（比如数据已经存在、且只能经历一次输入），依据客户端GUI所操作数据的状态，业务逻辑要求数据为只读，即客户端只具有显示数据的功能，并不能改变服务器上的数据。</P>
<P>一般地，编程实现时，会在GUI程序中加入判断数据是否应该为只读的逻辑判断语句。如果针对相同的数据集合（Model），有多个客户端GUI（View），就需要在每个程序中加入该判断。如此降低了程序的可维护性，决定数据是否为只读的这样一个业务逻辑将分散在程序中多处，不易维护，在业务逻辑发生改变时，易造成不一致。</P>
<P>我们可以将变化部分（在Normal和Read-Only状态下不同的部分）从GUI中抽取出来，分别用不同的类来表示，这样，当GUI的状态发生改变时，只需要改变其对状态对象的引用即可，状态相关操作委托给状态对象去完成。</P>
<P><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2.1.2 适用性</B></P>
<P>本类型适用环境：相同数据集合，不同操作模式。即特定的GUI根据操作上下文环境，改变其响应模式，根据数据是否可编辑，分为两种状态Read-Only State、 Normal State。</P>
<P><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2.1.3 结构（图2）</B></P>
<P><A name=IDAWDG2><B>图2 相同数据集合，不同操作模式</B></A><BR><IMG height=548 alt="图2  相同数据集合，不同操作模式" src="http://www-900.ibm.com/developerWorks/cn/java/j-state/images/image003.png" width=825 border=0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></P>
<P><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2.1.4 参与者</B></P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>ClientStateChangeable：客户端GUI提供给State对象的操作接口，使得State对象可以访问GUI的成员，以完成该状态相关操作； 
<UL>
<LI>getChangeableComponents()：返回在状态转换为Read-Only时，不可编辑的控件Collection； 
<LI>saveChangeToServer()：在可编辑状态时，将客户端数据保存到服务端。</LI></UL>
<LI>AClientGUI：完成图1中的Context功能，即其状态要进行改变的客户端GUI；维护ClientState的一个实例（state）； 
<UL>
<LI>init()：将this引用作为参数，调用state.setComponents(this) 以设置可变控件的初始状态； 
<LI>okButtonActionPerformed(e:ActionEvent)：用户完成操作后，本方法可作为 "OK"按钮控件的ActionListener方法，调用state.action(this) 以完成本操作。</LI></UL>
<LI>ClientState：State接口，封装与客户端GUI某个特定状态相关的行为； 
<UL>
<LI>setComponents(gui:ClientStateChangeable)：设置参数gui指定的客户端GUI的控件状态； 
<LI>action(gui:ClientStateChangeable)：完成数据保存功能。</LI></UL>
<LI>ClientNormalState：可编辑状态子类，实现ClientState接口； 
<UL>
<LI>setComponents(gui:ClientStateChangeable)：调用参数gui指定的客户端GUI的getChangeableComponents()获取控件Collection，然后遍历设置各控件状态为可编辑； 
<LI>action(gui:ClientStateChangeable)：调用参数gui指定的客户端GUI的saveChangeToServer()完成数据保存功能。</LI></UL>
<LI>ClientReadOnlyState：只读状态子类，实现ClientState接口； 
<UL>
<LI>setComponents(gui:ClientStateChangeable)：调用参数gui指定的客户端GUI的getChangeableComponents()获取控件Collection，然后遍历设置各控件状态为ReadOnly； 
<LI>action(gui:ClientStateChangeable)：空方法，本状态下无数据变化，无须保存。</LI></UL></LI></UL>
<P><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2.1.5 代码示例</B></P>
<P>ClientStateChangeable接口：</P><A name=IDACFG2><B></B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
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();
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>AClientGUI类：</P><A name=IDAKFG2><B></B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
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() {
		//…	
	}
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>ClientState接口：</P>
<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); }</P>
<P>ClientNormalState类：</P><A name=IDAUFG2><B></B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" border=1 bgc<link rel="stylesheet" href="/developerWorks/cn/css/ie1.css" type="text/css">olor="#CCCCCC"&gt; 
<TBODY>
<TR>
<TD><PRE><CODE>
public class ClientNormalState implements ClientState {
	/**
	 * 正常状态下, 各个控件默认为可编辑的, 所以不用做任何更改
	 */
	public void setComponents(ClientStateChangeable gui) {}

	/** 
	 * 正常状态下, 需要将用户所作修改保存到服务端 
	 */
	public void action(ClientStateChangeable gui) {
		gui.saveChangeToServer();
	}
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>ClientReadOnlyState类：</P><A name=IDA2FG2><B></B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
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) {}
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P><A name=IDADGG2><SPAN class=atitle3>2.2 类型2：（Reuse GUI）</SPAN></A><BR><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2.2.1 动机</B></P>
<P>当多个客户端GUI布局、控件类型很相似，所完成的任务也相似时，只需要经过精心设计，将这些GUI的展示形式统一起来，同一个GUI可以用到多个场景中，达到重用的目的。此时，这些不同任务需要操作不同的数据集合。</P>
<P>可以在GUI类中实现这些不同数据集合的操作，但是这会给程序维护带来麻烦。首先，属于不同逻辑的数据操作出现在同一类文件中，造成逻辑混乱、程序规模增大，不易于调试；其次，要将GUI用于新的数据集合时，只能在相同文件中增加新的代码，此时，该程序的可维护性降低，尤其是新的工作由其他程序员完成时，要理解原有代码是很费力的。</P>
<P>和2.1.1节中提到的解决方法类似：将变化的部分和不变部分分离开来，使得变化的部分可以独立修改、扩充。具体地，则是将数据集合相关操作从GUI程序中抽取出来，定义一个所有数据集合操作的接口（即：状态接口），不同地数据集合操作作为该接口的一个实现类存在。这样，每个数据集合都独立的封装于一个状态对象内；而且，要对新的数据使用该GUI，只需要定义新的状态接口实现类即可，无须修改已有类，甚至不关心已有的状态。</P>
<P><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2.2.2 适用性</B></P>
<P>本类型适用环境：不同的数据集合，相同的操作模式。即不变化的客户端GUI，将不同的数据集合操作委托给变化的状态对象去完成。</P>
<P><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2.2.3 结构（图3）</B></P>
<P><A name=IDATGG2><B>图3 不同数据集合，相同操作模式</B></A><BR><IMG height=256 alt="图3 不同数据集合，相同操作模式" src="http://www-900.ibm.com/developerWorks/cn/java/j-state/images/image005.png" width=700 border=0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></P>
<P><B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2.2.4 参与者</B></P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>InvariableGUI：本身不发生变化的客户端GUI类，维护一个对VariableDataState的引用state，将数据相关操作委托给该引用对象 ； 
<UL>
<LI>saveChangeToServer()：调用state.processData(this)完成数据相关操作；</LI></UL>
<LI>VariableDataState：State接口，封装与数据相关操作的行为； 
<UL>
<LI>ProcessData(gui:InvariableGUI)：调用参数gui的成员获取数据并完成处理；</LI></UL>
<LI>DataState1、DataState2、 … … DataStateN：状态子类，实现VariableDataState接口，封装了特定某一类数据集合的操作，可以根据不同的数据集合定义多个VariableDataState接口的状态类，从而实现了对InvariableGUI的重用。</LI></UL>
<P>本类型的实现代码在这里就不列出了，参照2.1.5节中的代码，很容易的就可实现本类型的结构。</P>
<P><A name=IDANHG2><SPAN class=atitle3>2.3 综合以上两种类型</SPAN></A><BR>可以将以上两种类型结合起来使用，即实现了客户端软件的数据集合方面对GUI的重用，也实现了操作模式方面对GUI的重用。</P>
<P>程序实现时，可以由GUI类分别维护一个ClientState的引用和一个VariableDataState的引用：</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>初始化GUI类时，GUI的构造器调用ClientState对象的setComponents()方法设置控件的状态； 
<LI>用户提交操作时，GUI调用ClientState对象的action()方法，如图2所示，该方法使用传递的gui参数回调GUI的saveChangeToServer()方法，而saveChangeToServer()方法则按照图3所示，调用VariableDataState引用的状态对象的processData()方法完成数据操作。</LI></UL>
<P><A name=IDAXHG2><SPAN class=atitle2>3 总结</SPAN></A><BR>本文介绍的State模式应用于多类型数据、多操作模式的客户端软件，可以取得明显的效果；但如果客户端类和状态都很少时，使用本模式，反而增加了客户端类数量，增加了体系结构的复杂性，此时，可以使用继承方式的类体系来实现重用，无须使用State状态对象的委托操作和回调操作。</P><img src ="http://www.blogjava.net/jacky/aggbug/1038.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jacky/" target="_blank">jacky</a> 2005-02-05 20:09 <a href="http://www.blogjava.net/jacky/articles/1038.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>