<script language= "javascript" src= "http://www.blogbao.com/script.aspx?userid=48676&AdType=1&AdstyleID=49574" charset=utf-8 ></script>
 
 
1.       程序开始运行,第一次程序提示用户可以通过“help”命令获得帮助。<?XML:NAMESPACE PREFIX = O />

2.       然后提示用户现在可用的数据库有哪几种类型。

3.       然后用户选择数据库类型。

4.       程序获得到用户的命令后,根据命令为CommandManager类的Executor属性创建具体执行者类的实例,如说是FileExecutor的实例。

5.       然后再提示用户提供这些数据库的地址,用户名,密码等,如果是把txt文件当作数据库来使用,可以直接连接文件,文件的路径直接写在程序里面。

6.       用户输入设置命令,程序获得设置命令,调用Executor属性的setDBConnectionString)方法,通过参数连接数据库。

7.       程序再次提示用户,连接成功可以数据操作命令

8.       用户输入select name=zhangsan

9.       程序调用Executor属性的selectNextPageString)方法查找到所有的记录,返回给用户

10.   程序再次等待用户的新的操作指令。

11.   直到用户输入exit,结束程序。

 

现在问题是怎么根据用户的选择数据库的命令为Executor创建正确的执行类的实例,怎么根据用户的指令找到正确的方法来调用。

 

上次说到了Executor接口和具体的实现类,然后又说到了Class类。

CommandManager类一个Executor类型的属性,利用Class根据用户输入的命令动态的创建Executor类的具体实现对象,这样就可以做到动态的选择命令的执行者。

但是事情并没有结束,我们怎么知道用户的命令对应那一个具体的执行者类型呢?

假如我们有一个包有一个执行类net.handsome.action.FileExecutor。我们总不能让用户输入命令的时候输入“net.handsome.action.FileExecutor”这么长的一个东西吧。

但是,我们又不能把根据用户的输入在程序里面用Map或者Set对应到类名上面来,因为这样我们的命令就和具体的执行者还是写在了程序里面。

幸好有配置文件可以选择,我就用简单的一种配置文件properties文件。利用Properties类我们可以很轻松的读取键值对,在文件里面只需要把键值对用等号连接即可。于是我定义了这样一个文件executor.map

######

## 这里是配置命令到执行数据库的映射的配置文件,如果你不是程序员或者不精通正则表达式请不要随意更改,否则可能造成程序的异常。

##

## 格式为:<前置命令符>=<执行者对应的系统类型>,<选择该数据库后需要提示的信息,通常是指提示用户设置数据库连接,如果是空则标示直接由系统自动按默认值连接数据库。>

#####

file=net.handsome.action.FileExecutor,

mysql=net.handsome.action.MysqlExecutor, 请输入连接mysql数据库的相关参数,格式:set db\={dbpath:port} table\={tablename} user\={username} pas\={password}

########

在这里只有两个键值对,filemysql。当用户键入file的时候,我们可以通过file对应到net.handsome.action.FileExecutor

当用户输入mysql的时候,我当然就对应到了net.handsome.action.MysqlExecutor类,

然后利用字符串记录这个类型的名称,然后利用Class创建这个名称对应的类的实例,然后赋值个CommandManagerExecutor类型的属性。

这样我们就选好了命令的执行者。

 

具体的代码如下:

package net.handsome.manager;

 

import java.io.BufferedReader;

……………………

 

public class CommandManager

{

     private Executor executor = null ; // 记录当前使用的执行者对象

     public static String dbstate = "" ; // 记录当前执行的数据库的种类

     private boolean isContinue = true ; // 标示是否可以继续下一步的执行。

     public CommandManager()

     {

         super ();

     }

……………………

Properties properties = loadPropertiesFile( "configure/executor.map" );

              String classNameAndMesg = null ;

              int indexDivid = 0;

              classNameAndMesg = properties.getProperty(commandString);

              try

              {

                   indexDivid = classNameAndMesg.indexOf( "," );

              } catch (NullPointerException e)

              {

                   properties = loadPropertiesFile( "configure/executor.map" );

                   Enumeration names = properties.propertyNames();

                   System. out .print( " 您的输入有误!  目前支持的数据有:" );

                   while (names.hasMoreElements())

                   {

                       System. out .print(names.nextElement().toString() + "  " );

                   }

                   this . isContinue = false ;

                   return "" ;

              }

              String executorClassName = null ;

              if (indexDivid == -1)

              {

                   executorClassName = classNameAndMesg;

              } else

              {

                   executorClassName = classNameAndMesg.substring(0, indexDivid);

              }

              Class executorClass = null ;

              try

              {

                   executorClass = Class.forName(executorClassName);

              } catch (ClassNotFoundException e)

              {

                   System. out .print( " 找不到需要的类,出现程序异常,可能是配置文件出现错误!请重新安装程序!" );

                   this . isContinue = false ;

                   return "" ;

              }

              try

              {

                   setExecutor((Executor) executorClass.newInstance());

              } catch (InstantiationException e)

              {

                   System. out .print( " 程序级异常,类型初始化异常!请重新安装程序!" );

                   this . isContinue = false ;

                   return "" ;

              } catch (IllegalAccessException e)

              {

                   // TODO

                   e.printStackTrace();

              } catch ( ClassCastException e)

              {

                   System. out .print( " 类型转换出现异常,可能是配置文件出现错误!请重新安装程序!" );

                   this . isContinue = false ;

                   return "" ;

              }

……………………

可以看到配置文件的具体类型名之后还有一段话,那个是用户选择这个执行这之后需要展示个用户的提示,告诉用户设置这个数据库需要输入的设置命令的格式。通常包括密码,用户名,服务器地址等。但是,如果提示语句为空,不如想file,那就直接连接,具体的连接方式已经写在程序里面。因为如果是file我只允许操作我指定程序目录下的一个txt文件。

 

通过操作指令得到具体的执行发放也是一样的,我有一个method.map文件,

左面写着用户输入的指令必须匹配的正则表达式,等号右边写着方法的名称。

#####

## 这里是配置命令到方法映射的配置文件,如果你不是程序员或者不精通正则表达式请不要随意更改,否则可能造成程序的异常。

#####

^insert\\s[0-9]{1,6}$   =   randominsert

^insert\\sname\=\\S{1,20}$   =   insert

^insert\\sid\=1?[0-9]{1,9}\\sname\=\\S{1,20}$   =   insert

^select\\sname\=\\S{1,20}$ = selectByName

^select\\sid\=1?[0-9]{1,9}$ = selectById

^nextpage$ = selectByName

^set\\sdb\=\\S+\\stable\=\\S+\\suser\=\\S+\\spas\=\\S+$ = setDBConnection

当用户输入select name=zhangsan的时候,会匹配到第四条正则表达式,如是通过他得到对应的值是selectByName,然后通过Class动态的利用selectByName这个字符串知道ExecutorselectByNameString)方法,得到他的Method对象,然后调用(Method.invoke)。

 

现在也就是说我们通过两个配置文件,把CommandManager到具体的Executor之间分离了,把CommandManager到命令之间分离了。如果我们要添加执行者,我们只需要实现Executor接口,就行了,然后再在配置文件中增加一个数据库到执行者的对应。如果我们要添加方法,只需要在接口中添加这个方法,然后在配置文件中增加这个调用这个方法的命令对应的正则表达式就可以了。

 

下面是我的程序的目录结构:

 

再来看看程序的UML模型把:
 

<?XML:NAMESPACE PREFIX = V />

从图上可以看到,我并没有直接让具体的执行者类实现Executor接口,而是现为Executor实现一个抽象的适配器类,为每一个方法提供一个默认的实现,这里通常是给用户一个友好的提示。然后用具体的实行者类来继承这个适配器类。这样如果我们的具体实现类没有实现某个方法,可以程序会调用适配器类的对应方法,给予友好的提示。

 

关于具体执行。

具体执行类我到现在才只完成了对file的操作,对数据库的操作已经没有激情继续写下去了,再说现在工作也多起来了。
file的性能方面我做出了一个最大主键管理的方案,这样在使用主键id的时候可能会有一些限制,但是由于文件的特殊性,这个能在很大程度上提高性能。具体的做法是,在第一次指定操作file的执行者的时候,读取文件找到最大的主键id,记录下来。在以后需要查询的时候如果id大于记录的最大主键,直接返回没有记录。在插入数据的时候不需要每次生成一个主键都对所有的记录查询一次看是否已经存在,而是直接在现有主键的基础之上加就ok了,但是带来的局限就是当用户指定一个很大的主键插入后,我们的主键空间就会受到很大的打击了…………………..
对于mysql的具体操作类我只是写出了标志性的执行语句,具体的执行没有完成,遗憾。
关于扩展。

如果需要在这个程序上添加一个其他数据库的类,只需要让他继承ExecutortAdapter类并覆盖相关方法就可以了,然后在executor.map中加入制定该数据库操作的命令和类型的映射就可以了。
如果要加入新的命令方法,可以有两种方式,一种就是增加接口中的方法,并在适配器类中增加默认的实现(通常是给用户友好的提示,当具体类没有覆盖这个方法的时候不至于使程序很尴尬),然后在具体类中实现这个处理这个方法就可以了。当然还要在我们的method.map文件中写匹配指向这个方法的命令的正则表达式和方法的映射。
另一个就是对新加入的方法在配置文件把命令统一映射到调用我们的这个extendMethod方法,这是一个专门用来准备扩展的方法,然后当他收到命令后在extendMethod再分配到特定的具体类扩展的方法,这样就不需要修改我们的“框架了”只需要修改配置文件。
不足
       1.CommandManager对命令判断匹配之后,只是简单的讲命令转发送执行者的具体方法,具体执行的方法再对参数进行解析。而其实每个执行者对命令的参数解析的过程是一样的,这个共同点没有抽出来。
       2.当我们对CommandManager指定了Executor的具体实现类后无法转重新指定其他的具体实现,比如我们指定要操作file后无法再指定mysql来操作命令。当然这个很好解决,只需要增加一个changedb to 的命令就可以了。但是即使这样解决了还是在每次转换后都需要重新设置数据库的连接信息,这对用户来说是残忍的。
其实还可以做到更好
       1.如果有下一个版本,我希望可以把我们的命令到方法的映射不仅仅只是完成到方法名称的映射,也就是在配置文件的时候,需要在命令匹配正则表达式中指出参数的位置,用{0}{1}这样的格式,然后在配置方法名的时候,一起配置方法的参数类型,在CommandManager对命令匹配成功之后再动态根据参数构造出方法的参数列表并调用方法,这样就把执行方法解析参数的过程提炼出来了统一由CommandManager完成了。这就解决了上面的第一个问题。
       2.把现有的CommandManagerExecutor属性变成一个集合类,这样我们每次转换数据库执行者的时候先查询以前是否已经设置过这样一个操作者的信息,如果有直接就可以让用户操作了。这就解决了上面的第二个问题。
好了去看程序把
只需要把jar文件和我提供的两个配置文件夹方在一起就可以用java –jar dataAdmin.jar执行运行了。温馨提示一下:如果你一直在eclipse环境下调试和执行你的程序而没有太过关注你的环境变量,请你查看一下你的环境变量是否在classpath变量里面的开头出添加了“.;”的路径,以便使得我们的程序能在当前目录下找到需要的类。具体的使用方法在程序里面会有很详细的说明。
由于properties文件没有利用工具对编码方式处理,导致有一句话输入乱码,这个就将就一下把,不影响使用,我太懒了就先不弄了。源码到我的资源下面去找把[url]http://download.csdn.net/hicsdn/yjscdb[/url]

 

总结:这是第一次系统的写出自己的学习过程,坦白的说写之前没有对具体该如何描述做好准备,导致这篇blog的可读性很差,虽然是个很简单的问题,但是基本上没有把事情说清楚,有时候讲到具体实现,有时候又到了大的设计方案上。下一次会注意,尽量使文章容易理解。