作者:进 Lyon,转载请注明作者名字以及出处 。
程序的状态处理几乎是每个应用系统都需要做的事情,而且在逻辑控制中处于非常重要的角色,一般来讲,设计得好的系统,都会有一套自己的状态维护机制——状态机,状态机无非就是一个switch case的封装,以及log记录,随着状态的逐渐复杂以及需求的改变,最开始设计的状态机可能会面临面目全非的修改,通过一个简单的配置,可视化的检查,是提高编程效率、降低风险的好办法。SMC就是提供一个配置文件——UML状态图——JAVA(或其他语言)代码生成的软件。
按照官方的说法,SMC目前支持11种语言的代码自动生成:
SMC currently supports ten programming languages: 1. C, 2. C++, 3. C#, 4. Java, 5. Lua, 6. Objective-C, 7. Perl, 8. Python, 9. Ruby, 10. [incr Tcl] and 11. VB.Net.
SMC官方网站:http://smc.sourceforge.net/
下载最新的SMC包:smc_5_0_0.zip,设置环境变量:
+ Add the full path to .../Smc/bin to your PATH environment variable. + Add the full path to statemap.jar to your CLASSPATH environment variable. + Add the full path to .../Smc/lib to your TCLLIBPATH environment variable.
Sample:
1、就拿SMC提供的地铁自动轧机的状态例程作为案例,状态图如下:
2、在这里我们将状态转变修改了一下,状态变成unLock后,如果调用pass命令,那么根据用户的性别判断是执行doWork1()还是doWork2()动作,并且执行doLock动作。
3、编写SM(?)文件Turnstile.sm——描述状态关系变化的文本文件;
%class Turnstile %package test.smc
%start MainMap::Locked
%map MainMap %% Locked{ coin UnLocked {unlock();} pass(sex:int) nil {alarm(sex);} }
UnLocked{ pass(sex:int)[ctxt.isBoy(sex)] Locked {doWork1();lock();} pass(sex:int)[ctxt.isGirl(sex)] Locked {doWork2();lock();} coin nil {thankyou();} }
%%
官方的注释(注意:代码跟上面的例程不一样):
4、将smc_5_0_0.zip包中的smc.jar与Turnstile.sm放在一起,编译:
java -jar Smc.jar -java Turnstile.sm
5、生成TurnstileContext.java,手工编写Turnstile.java:
package test.smc;
import test.smc.TurnstileContext.TurnstileState;
public class Turnstile implements ITurnstileActions { TurnstileContext _fsm; ITurnstileActions _actions;
public Turnstile(ITurnstileActions actions) { this._actions = actions; this._fsm = new TurnstileContext(this); }
public TurnstileState getCurrentState() { return this._fsm.getState(); }
public void coin() { _fsm.coin(); }
public void pass(int sex) { _fsm.pass(sex); }
public boolean isBoy(int sex) { return sex==0; }
public boolean isGirl(int sex) { return sex==1; }
public void alarm(int sex) { _actions.alarm(sex); }
public void lock() { _actions.lock(); }
public void thankyou() { _actions.thankyou(); }
public void unlock() { _actions.unlock(); } public void doWork1(){ _actions.doWork1(); } public void doWork2(){ _actions.doWork2(); } }
ITurnstileActions.java
package test.smc;
public interface ITurnstileActions { public void alarm(int sex); public void lock(); public void thankyou(); public void unlock(); public void doWork1(); public void doWork2(); }
TurnstileActionsImpl.java
package test.smc;
import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory;
public class TurnstileActionsImpl implements ITurnstileActions { private static Log logger = LogFactory.getLog(TurnstileActionsImpl.class); public void alarm(int sex) { logger.info("____________alarm"); }
public void lock() { logger.info("____________lock"); }
public void thankyou() { logger.info("____________thankyou"); }
public void unlock() { logger.info("____________unlock"); } public void doWork1(){ logger.info("____________Hello boy!"); } public void doWork2(){ logger.info("____________Hello girl...!"); } }
6、编写测试代码:
package test.smc;
import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory;
import junit.framework.TestCase;
public class TestTurnstile extends TestCase { private static Log logger = LogFactory.getLog(TestTurnstile.class); private Turnstile turnstile ; public void setUp(){ turnstile = new Turnstile(new TurnstileActionsImpl()); } public void testCoin1(){ System.out.println(); System.out.println(); System.out.println("Start testCoin1"); logger.info("State msg: " + turnstile.getCurrentState().getId() + ": " + turnstile.getCurrentState().getName()); turnstile.coin(); System.out.println(); logger.info("State msg: " + turnstile.getCurrentState().getId() + ": " + turnstile.getCurrentState().getName()); turnstile.pass(0); System.out.println("End testCoin1"); } public void testCoin2(){ turnstile = new Turnstile(new TurnstileActionsImpl()); System.out.println(); System.out.println(); System.out.println("Start testCoin2"); turnstile.pass(1); turnstile.coin(); turnstile.coin(); turnstile.coin(); turnstile.pass(1); System.out.println("End testCoin2"); } }
7、执行结果:
Start testCoin1 2007-12-9 0:34:48 test.smc.TestTurnstile testCoin1 信息: State msg: 0: MainMap.Locked 2007-12-9 0:34:49 test.smc.TurnstileActionsImpl unlock 信息: ____________unlock
2007-12-9 0:34:49 test.smc.TestTurnstile testCoin1 信息: State msg: 1: MainMap.UnLocked 2007-12-9 0:34:49 test.smc.TurnstileActionsImpl doWork1 信息: ____________Hello boy! 2007-12-9 0:34:49 test.smc.TurnstileActionsImpl lock 信息: ____________lock End testCoin1
Start testCoin2 2007-12-9 0:34:49 test.smc.TurnstileActionsImpl alarm 信息: ____________alarm 2007-12-9 0:34:49 test.smc.TurnstileActionsImpl unlock 信息: ____________unlock 2007-12-9 0:34:49 test.smc.TurnstileActionsImpl thankyou 信息: ____________thankyou 2007-12-9 0:34:49 test.smc.TurnstileActionsImpl thankyou 信息: ____________thankyou 2007-12-9 0:34:49 test.smc.TurnstileActionsImpl doWork2 信息: ____________Hello girl...! 2007-12-9 0:34:49 test.smc.TurnstileActionsImpl lock 信息: ____________lock End testCoin2
8、查看测试结果可知,在未有调用任何动作之前,状态的初始值为:MainMap.Locked,也就是%start MainMap::Locked 指定的初始值,执行turnstile.coin()之后,状态变为MainMap.UnLocked,在其中我们没有看到任何状态的处理机制——这些状态变化、记录已经在TurnstileContext.java中解决了。看看没有执行coin方法而直接调用pass的结果——alarm ,看来状态的逻辑判断也对了。
分析SM配置文件:
Locked{ coin UnLocked {unlock();} pass(sex:int) nil {alarm(sex);} }
UnLocked{ pass(sex:int) [ctxt.isBoy(sex)] Locked {doWork1();lock();} pass(sex:int) [ctxt.isGirl(sex)] Locked {doWork2();lock();} coin nil {thankyou();} }
在执行pass命令时,需要传入sex,类型为int,ctxt是当前Turnstile实例的名字,是代码自动生成的,不用修改成其他名字,执行ctxt.isBoy(sex)或isGirl(sex)不需要执行函数入口参数的类型,因为pass、isBoy、isGirl方法的调用关系是:
protected void pass(TurnstileContext context, int sex) { Turnstile ctxt = context.getOwner();
if (ctxt.isBoy(sex)) {
……
} else if (ctxt.isGirl(sex)) {
……
} else { super.pass(context, sex); }
return; }
其中,pass函数,在调用时,系统会自动传入TurnsitleContext。
9、SMC的特色在于,可以根据SM文件反向生成UML状态图图片(注意,是图片)。这是需要借助第三方工具——graphviz,软件的官方网站是:
http://www.graphviz.org/
Logo比较有意思:
下载graphviz-2.16.static.exe。
在SM目录执行命令
java -jar smc.jar -graph -glvel 2 Turnstile.sm
-glvel有3个等级,0、1、2,数字越大,生成的状态图就越详细,在这里我们传入2。生成文件:Turnstile_sm.dot,在graphviz中打开它,执行run,生成的图形如下:
与预期设计的UML图进行比较,发现基本类似:
摘录一段关于SMC对象内部关系的图: